• Go语言技巧之正确高效使用slice(听课笔记总结--简单易懂)


    slice基本

    切片slice 相当于“动态数组”,但他并不是数组或数组指针,它通过内部指针和相关属性来引用数组片段,以实现变长的功能。

    Slice源码中的数据结构:

    type slice struct {
        array unsafe.Pointer // 指向底层数组
        len int // 长度
        cap int // 容量
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    所以,slice代表变长的序列,它的底层是数组。
    一个切片由3部分组成:指针、长度和容量。
    指针指向底层数组,长度代表slice当前的长度,容量代表底层数组的长度。
    换句话说,slice自身维护了一个指针属性,指向它底层数组的某些元素的集合

    正确使用slice

    这部分内容通过case来讲解,觉着简单的case可跳过。

    case1

    下面这段代码输出什么?

    func main() {
    	var s []int
    	for i := 0; i < 3; i++ {
    		s = append(s,i)
    	}
    	modifySlice(s)
    	fmt.Println(s)
    }
    
    func modifySlice(s []int) {
    	s[0] = 1024
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    正确输出:[1024 1 2]

    其中,
    append函数:在切片s末尾添加元素i
    fmt:是go的一个标准库,fmt包实现了类似C语言printf和scanf的格式化I/O

    case2

    下面这段代码输出什么?

    func main() {
    	var s []int
    	for i := 0; i < 3; i++ {
    		s = append(s,i)
    	}
    	modifySlice(s)
    	fmt.Println(s)
    }
    func modifySlice(s []int) {
    	s = append(s, 2048)
    	s[0] = 1024
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    正确输出:[1024 1 2]

    在这里插入图片描述
    如图所示,两个S不同,但指向同一个array地址,最后按main中S的长度输出,所以是[1024 1 2]

    case3

    下面这段代码输出的是什么?

    func main() {
    	var s []int
    	for i := 0; i < 3; i++ {
    		s = append(s, i)
    	}
    	modifySlice(s)
    	fmt.Println(s)
    }
    func modifySlice(s []int) {
    	s = append(s, 2048)
    	s = append(s, 4096)
    	s[0] = 1024
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    正确输出:[0 1 2]

    在这里插入图片描述

    切片通过 append扩容时,如果切片长度小于当前的容量,那么切片不会扩容,如果追加元素后切片长度大于当前的容量时,切片就会扩容,扩容机制如下:

    • 当扩容之后的元素长度小于 1024 时会以原切片容量的 2 倍的进行扩容;
    • 当扩容之后的元素长度大于 1024 时会以原切片容量的 1.25倍的进行扩容;

    在这里插入图片描述

    • 当不需要扩容时,append 函数返回的是原底层数组的原切片(内存地址不变);
    • 当切片需要扩容时,append 函数返回的是新底层数组的新切片(切片内存地址发生了改变);

    我们设main中s为S1,modifySlice中的s为S2

    所以,Append 4096 后 容量由原来的4扩容成8,开辟了新地址,S2与S1不再指向同一地址。
    而更改成1024的,是新地址切片。而输出是main中指向老地址的S1.
    2048实际也被写入了原始切片S1,但是println函数是根据len来打印的,所以没有打印出来。

    case4

    func main() {
    	var s []int
    	for i := 0; i < 3; i++ {
    		s = append(s, i)
    	}
    	modifySlice(s)
    	fmt.Println(s)
    }
    func modifySlice(s []int) {
    	s[0] = 1024
    	s = append(s, 2048)
    	s = append(s, 4096)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    正确输出:[1024 1 2]

    case4中,1024是更改到还未扩容到老地址上的切片S1中,所以输出的时候有1024.

    高效使用slice

    1、简单粗暴:直接定义slice,然后进行append操作(性能比较差)
    在这里插入图片描述

    2、预分配容量然后进行append操作(比第一种好)
    在这里插入图片描述

    3、容量和长度都分配完再进行append操作(最快的)
    在这里插入图片描述

    • 预先分配内存可以提升性能(CPU是比内存更加珍贵的资源)
    • 直接使用index赋值而非append可以提升性能(append其实还是一次调用函数的操作)
  • 相关阅读:
    SQL必会知识点(一)
    java基于SpringBoot+Vue的疫苗接种管理系统 element
    std::move以及右值引用等
    android user版本(不分平台+不分安卓几)实现root功能
    java108-StringBuilder连接字符串和删除操作
    【华为OD机考B卷 | 100分】统计监控、需要打开多少监控器(JAVA题解——也许是全网最详)
    类图 UML从入门到放弃系列之二
    年薪百万的程序员,上网都在看什么?
    Java并发 | 16.[基础] synchronized简述
    JUC笔记(四) --- 内存共享模型
  • 原文地址:https://blog.csdn.net/qq_45884783/article/details/126186285