在这里我就简单的分享一下我学习Slice的体会与困惑,作为一名即将上岸Golang开发的学子,需要99%的汗水。
首先我们得清楚什么是切片Slice,它是一个动态数组,是一个引用类型,切片的长度可以变化的,并且它就是一个结构体,分别含有三个字段:指向底层数组的首地址、切片长度len、切片容量cap。然后怎么初始化、怎么使用,怎么声明,怎么遍历以及怎么计算当前的cap这些就不必要说了,基础知识,但是强调一下:对于s[low : high]这种格式的表达式, 如果s是数组或者字符串, 则0 <= low <= high <= len(s)。如果s是切片, 则0 <= low <= high <= cap(s)。还有一种我后面才知道的,刚开始没学到,就是声明切片时:s := arr[0:3:3],最后一个数可以声明该切片的容量,但是3必须小于cap(arr),并且s的当前容量也是3 - 0 = 3。
看代码
func main() {
arr := []int{1, 2, 3, 4, 5, 6, 7, 8}
s1 := arr[2:5] // 此时切片的长度为3, 容量为6
s2 := s1[2:4] // 因为s1容量为6,所以4仍然属于合法的index
fmt.Println(len(s1), cap(s1), len(s2), cap(s2)) // 输出:3 6 2 4
fmt.Println(s1, s2) // 又因为未发生扩容行为,所以s1,s2公用底层数组, 故最后输出[3 4 5] [5 6]
}
具体的输出结果以及想法写在了上面的注释里,这里比较容易困惑的是为什么s2会有一个6,那是因为s2和s1指向底层的同一个数组,并且s1的容量是6,切s1切到4明显可以切,所以切的还是同一个arr数组,自然5后面就是6了。
再看一段代码
s := []int{2}
fmt.Println(s[1:])
这里会输出什么?空[ ] 还是报错超出可取范围?首先我们来分析一下,对于s切片很明显是一个len为1,cap也为1的切片,也就是说,根据官方文档来说:切片可以切这个范围0 <= low <= high <= cap(s),也就是说切片的low可以为len长度,并且[1:]表示切下标为1到后面所有,后面所有也就是len长度,所以就是这样切[len:len],对于这种切法我们很清楚我们得到的切片是不包含后面这个下标的元素的,所有这样切出来就是[ ]空切片,也就是说s[1:]切出一个空切片。但是如果我们进行[len+1:]来切的话就很明显报错了,超出了可以切的范围~
继续
arr := []int{1, 2, 3, 4, 5, 6, 7, 8}
s1 := arr[2:5:7]
s1 = append(s1, 999)
fmt.Println(s1, arr)
这里又会输出什么呢?
答案是:[3 4 5 999] [1 2 3 4 5 999 7 8]
首先对arr切片进行切片得到的s1是通过arr[2:5:7]进行的,那么这个7就是取原多少容量,目前取7,然后再减去从第2下标开始切,所以s1的容量就是5~,那么s1 = [3,4,5],然后再通过append动态添加新元素到s1后面,但是s1是和arr指向相同的一个底层数组,所以呢,s1的5的后面一位是有数值的并且是6,当append之后就被覆盖掉了,动态添加到了5后面,也就是6 -> 999,同时它改变了底层数组的数据,所以指向的相同底层数组的arr也会出现相同的改变。
杀手锏
a := []int{1, 2, 3, 4, 5}
b := a[0:2]
c := a[0:3:3]
b = append(b, 1)
c = append(c, 1)
fmt.Println("b:", b, "cap", cap(b))
fmt.Println("c:", c, "cap", cap(c))
fmt.Println("a:", a, "cap", cap(a))
fmt.Println("c:", c[0:6])
/*输出结果
b: [1 2 1] cap 5
c: [1 2 1 1] cap 6
a: [1 2 1 4 5] cap 5
c: [1 2 1 1 0 0]
*/
刚开始基础不扎实的时候,感觉很懵很乱
首先b切了a,并且从index=0开始切,所以b=[1 2],容量是5
c切了a,并且也是从index=0开始切,但是声明了容量是3,所以c的容量是3-0=3 ; c=[1 2 3]。
然后开始分别给b和c append一个数,由于b的容量是5所以append时,len=2,完全有足够的空间可以放,所以不会发生扩容,所以b的第三个数是1,b=[1 2 1],并且同时改动了和a指向相同的底层数组的数据,所以a会输出[1 2 1 4 5];这里重点在c,c的容量是3,并且已经满了,所以append的时候放不下了,这时候就会splice开始扩容,按照小于1024个字节就扩容一倍,所以c的容量就变成了6,是怎么计算的?3个元素int类型按照64位计算机就是8个字节,8 * 3 * 2 = 48;然后48 / 8 = 6,所以分配了6个int大小容量的新的底层数组,并且把数据copy到了新底层数组然后就可以成功append进去了。最后再让c切片指向新的底层数组,这也就是为什么c输出的结果是[1 2 1 1 0 0 ]因为后面开辟出来的数组默认零值,并且本来是在append之前原来的底层数组的3就已经被替换成了1,所以会有这样的结果。
以上就是我学习遇到的面试题~
接下来就是我自己琢磨切片的容量的扩容情况
上代码
package main
import "fmt"
func main() {
s1 := make([]byte, 1) //任何低于8个字节的扩容后都是8个字节,不是简单的扩一倍。前提是放的下。
fmt.Println(s1, "", cap(s1)) //1
s1 = append(s1, 2)
fmt.Println(s1, "", cap(s1)) //8
//在append多个并且超出8就是16
s4 := []rune("hello")
fmt.Println(s4, "", cap(s4)) //5
s4 = append(s4, 'h', 'r') //rune = int32 是4个字节
fmt.Println(s4, "", cap(s4)) //12 按内存分配的规格,分配5*4*2=40 => 直接给48个字节的内存 (特殊)按8 16 32 48 64分配,不只是单纯的扩容一倍
s2 := []int{1, 2}
s2 = append(s2, 3) //int 8 个字节 8 * 2 *2 = 32 32 / 8 = 4 所以这个时候cap=4
s2 = append(s2, 4) //由于容量是4,目前只有3个,所以还放得下; 所以这个时候仍然cap=4
s2 = append(s2, 5) //由于目前4个容量已满, 需要扩容当前容量,按一倍 => 8 * 4 * 2 = 64 ;总共需要64字节大小的内存; 64 / 8 = 8 个int大小的容量 所以下面输出8
fmt.Println("=", cap(s2)) //输出8
s3 := []int{1, 2, 3, 4}
fmt.Println(s3, "", cap(s3)) //输出4
s3 = append(s3, 3, 3, 3, 3, 3) // 4*8*2=64 64/8 = 8不够同时放入5个数,所以就按(4+1)*8*2=80个字节 80 / 8 = 10 个int容量 (特殊)
fmt.Println("=", cap(s3)) //输出10
s5 := []string{"hello"}
fmt.Println(cap(s5)) //输出1
s5 = append(s5, "world") //string是16个字节单位 16*2 = 32 32/16 = 2
fmt.Println(cap(s5)) //输出2
s5 = append(s5, "!") //16 * 2 * 2 = 64 64 /16 = 4
fmt.Println(cap(s5)) //输出4
s5 = append(s5, "渺", "!", "!", "!", "!", "!") //当同时append多个数据超出 容量*2 的时候 ,容量按 按一倍扩容后超出多少个加多少个16字节的内存 (特殊)
fmt.Println(cap(s5)) //扩容一倍=8,不够放还差1个,所以输出9,append再加cap也再加
}
以上就是我自己测试琢磨并在注释写出了它的扩容规则,注意我这里说的扩容规则是我自己在看不到append()源码的情况下多次测试并且认为的。不同数据类型会有不同的扩容规则,比如int32和int64不同~对此有啥意见的可以讨论讨论,顺便我也可以学习学习各位大牛的想法,新手上路,多多指教。
工具:Eclipse,Oracle,smartupload.jar;语言:jsp,Java;数据存储:Oracle。...
上篇文章给大家介绍了 Java正则表达式匹配,替换,查找,切割的方法 ,接下来,...
项目中用到的一些特殊字符和图标 html代码 XML/HTML Code 复制内容到剪贴板 div ...
DELETEFROMTablesWHEREIDNOTIN(SELECTMin(ID)FROMTablesGROUPBYName) Min的话保...
4月11日20:30~22:00通过腾讯会议进行了第二次在线学习讨论我把学习笔记整理一下...
错误描述: 在开发.net项目中,通过microsoft.ACE.oledb读取excel文件信息时,报...
本文实例讲述了Laravel框架源码解析之反射的使用。分享给大家供大家参考,具体如...
正则忽略大小写 – RegexOptions.IgnoreCase 例如: 复制代码 代码如下: Str = R...
复制代码 代码如下: % URL="http://news.163.com/special/00011K6L/rss_newstop....
Elasticsearch 是通过 Lucene 的倒排索引技术实现比关系型数据库更快的过滤。特...