欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Golang切片

程序员文章站 2024-01-29 17:52:28
...

什么是切片(slice)

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

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

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

  3>.数组切片已分配的存储空间(cap):也就是slice开始位置到数组的最后位置的长度。

切片的结构

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

Golang切片

切片扩容机制

  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()
}

结果:
Golang切片

  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()
    }
    

    结果: Golang切片

  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)
}

执行结果:
Golang切片

可以看出来因为 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)
}

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

接下来我们再做一点骚操作,我们先思考一下,既然是同一个地方,那为什么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)再看看结果如何:
Golang切片
嗯????我s2的13怎么变成了100???
将这两行换个位置试试:
Golang切片
我的100又去哪儿了?
其实原因是s1,s2使用的是同一块内存,append也是在原来的地址后面append,所以这两次append其实改变的是底层数组的同一个位置而已,后面的修改自然会覆盖前一次的修改,
Golang切片

拿后面的一次举例,s1 append之后长度就变了但是并没有扩容:
Golang切片

其实这个时候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

相关标签: Go