Golang切片

什么是切片(slice)

简单的说,数组切片就像一个指向数组的指针,实际上它拥有自己的数据结构,而不仅仅是个指针。数组切片的数据结构可以抽象为以下3个变量:

  1>.一个指向原生数组的指针(pointer):指向数组中slice指定的开始位置;

  2>.数组切片中的元素个数(len):即slice的长度;

  3>.数组切片已分配的存储空间(cap):也就是slice开始位置到数组的最后位置的长度。
 
 
// runtime/slice.go
type slice struct {
	array unsafe.Pointer // 元素指针
	len   int // 长度 
	cap   int // 容量
}

切片的结构

ptr指向真正的array,在64位的机器上,golang切片长度24位,指针8位,长度8位,容量8位

在这里插入图片描述

切片扩容机制

  1. 切片每次新增个数不超过原来的1倍,且每次增加数不超过1024个,且增加后总长度小于1024个,这种情况下扩容后为原来的2倍
func capTest(){
	s1 := make([]int, 0)
	fmt.Printf("The capacity of s1: %d\n", cap(s1))
	for i := 1; i <= 33; i++ {
		s1 = append(s1, i)
		fmt.Printf("s1(%d): len: %d, cap: %d\n", i, len(s1), cap(s1))
	}
	fmt.Println()
}

结果:
在这里插入图片描述

  1. 切片一次新增个数超过原来1倍,但不超过1024个,且增加后总长度小于1024个,这种情况下扩容后比实际具有的总长度还要大一些。

    func capTest(){
    	s2 := make([]int, 10)
    	fmt.Printf("The capacity of s2: %d\n", cap(s2))
    	r1 := append(s2, make([]int, 5)...)
    	fmt.Printf("r1: len: %d, cap: %d\n", len(r1), cap(r1))
    	r2 := append(s2, make([]int, 11)...)
    	fmt.Printf("r2: len: %d, cap: %d\n", len(r2), cap(r2))
    	r3 := append(s2, make([]int, 21)...)
    	fmt.Printf("r3: len: %d, cap: %d\n", len(r3), cap(r3))
    	fmt.Printf("注意:像r2,r3 一次增加个数超过原容量的1倍,增加后结果比实际总长度预想的稍大一点 \n")
    	fmt.Println()
    }
    

    结果: 在这里插入图片描述

  2. 原切片长度超过1024时,一次增加容量不是2倍而是0.25倍,每次超过预定的都是0.25累乘

    // src/runtime/slice.go
    // go version 1.13
    func growslice(et *_type, old slice, cap int) slice {
    // ...省略部分
        newcap := old.cap
        doublecap := newcap + newcap
        if cap > doublecap {
            newcap = cap
        } else {
            //原切片长度低于1024时直接翻倍
            if old.len < 1024 {
                newcap = doublecap
            } else {
                // Check 0 < newcap to detect overflow
                // and prevent an infinite loop.
                //原切片长度大于等于1024时,每次只增加25%,直到满足需要的容量
                for 0 < newcap && newcap < cap {
                    newcap += newcap / 4
                }
                // Set newcap to the requested cap when
                // the newcap calculation overflowed.
                if newcap <= 0 {
                    newcap = cap
                }
            }
        }
    // ...省略部分
    }
    
    

切片扩容的坑

func SliceTest1(){

	s1:=make([]int,4)
	for i:=0;i<4;i++{
		s1[i] = i+1
	}

	s2 := s1
	fmt.Println("--------------------------------------")
	fmt.Printf("s1指向地址:%p,cap is %d\n",s1,cap(s1))
	fmt.Printf("s2指向地址:%p,cap is %d\n",s2,cap(s2))
	fmt.Printf("s1真实地址:%p\n",&s1)
	fmt.Printf("s2真实地址:%p\n",&s2)
	fmt.Println("--------------------------------------")

	s2 = append(s2, 13)
	fmt.Printf("s1指向地址:%p,cap is %d\n",s1,cap(s1))
	fmt.Printf("s2指向地址:%p,cap is %d\n",s2,cap(s2))
	fmt.Println("--------------------------------------")

	s2[2] = 9
	fmt.Println("s1=",s1)
	fmt.Println("s2=",s2)
}

执行结果:
在这里插入图片描述

可以看出来因为 s2:=s1导致s2跟s1的ptr指向了同一个地方,但是s2 append之后导致了s2扩容,扩容之后s2指向的地址改变,所以对s2的改动不会对s1有任何的影响

接下来我们做点更改:
把s1的容量设置大一点:

func SliceTest1(){

	s1:=make([]int,4,6)
	for i:=0;i<4;i++{
		s1[i] = i+1
	}

	s2 := s1
	fmt.Println("--------------------------------------")
	fmt.Printf("s1指向地址:%p,cap is %d\n",s1,cap(s1))
	fmt.Printf("s2指向地址:%p,cap is %d\n",s2,cap(s2))
	fmt.Printf("s1真实地址:%p\n",&s1)
	fmt.Printf("s2真实地址:%p\n",&s2)
	fmt.Println("--------------------------------------")

	s2 = append(s2, 13)
	fmt.Printf("s1指向地址:%p,cap is %d\n",s1,cap(s1))
	fmt.Printf("s2指向地址:%p,cap is %d\n",s2,cap(s2))
	fmt.Println("--------------------------------------")

	s2[2] = 9
	fmt.Println("s1=",s1)
	fmt.Println("s2=",s2)
}

结果如下:
在这里插入图片描述
可以看见这次没有发生扩容,因为我们设置了容量为6,对s1,s2任意一个做出改变,另一个也会更改,再s2[2]=9前面加一个s1[1] = 7,看看结果如何
在这里插入图片描述
不出意料,都改变了,说明了他们其实就是同一份,没有扩容指向的是同一个地方。

接下来我们再做一点骚操作,我们先思考一下,既然是同一个地方,那为什么s2多了一个值呢?而s1为什么还是原来的样子?
稍微改一下代码:

func SliceTest1(){

	s1:=make([]int,4,6)
	for i:=0;i<4;i++{
		s1[i] = i+1
	}

	s2 := s1
	fmt.Println("--------------------------------------")
	fmt.Printf("s1指向地址:%p,cap is %d\n",s1,cap(s1))
	fmt.Printf("s2指向地址:%p,cap is %d\n",s2,cap(s2))
	fmt.Printf("s1真实地址:%p\n",&s1)
	fmt.Printf("s2真实地址:%p\n",&s2)
	fmt.Println("--------------------------------------")

	s2 = append(s2, 13)
	s1 = append(s1,100) // 新增
	fmt.Printf("s1指向地址:%p,cap is %d\n",s1,cap(s1))
	fmt.Printf("s2指向地址:%p,cap is %d\n",s2,cap(s2))
	fmt.Println("--------------------------------------")
	
	fmt.Println("s1=",s1)
	fmt.Println("s2=",s2)
}

我们在s2 = append(s2, 13)的下一行增加了s1 = append(s1,100)再看看结果如何:
在这里插入图片描述
嗯????我s2的13怎么变成了100???
将这两行换个位置试试:
在这里插入图片描述
我的100又去哪儿了?
其实原因是s1,s2使用的是同一块内存,append也是在原来的地址后面append,所以这两次append其实改变的是底层数组的同一个位置而已,后面的修改自然会覆盖前一次的修改,
在这里插入图片描述

拿后面的一次举例,s1 append之后长度就变了但是并没有扩容:
在这里插入图片描述

其实这个时候s2也已经变了,假如你能越界访问s2的第五个元素,但是会报越界错误,当你给s2 append的时候同样修改的是第五个位置,用13进行覆盖,这时两个切片的长度都是5,所以都可以访问到,而且是后面修改后的值

推荐一篇文章:https://newt0n.github.io/2016/11/07/%E5%A6%82%E4%BD%95%E9%81%BF%E5%BC%80-Go-%E4%B8%AD%E7%9A%84%E5%90%84%E7%A7%8D%E9%99%B7%E9%98%B1/#%E6%95%B0%E7%BB%84-Arrays

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值