go语言数组、切片、map的内部实现

一、数组内部实现

  • 在 Go 语言里,数组是一个长度固定的数据类型,用于存储一段具有相同的类型的元素的连续块。数组存储的类型可以是内置类型,如整型或者字符串,也可以是某种结构类型。
  • 下图中可以看到数组的表示。灰色格子代表数组里的元素,每个元素都紧邻另一个元素。 每个元素包含相同的类型,这个例子里是整数,并且每个元素可以用一个唯一的索引(也称下标 或标号)来访问。
  • 数组是一种非常有用的数据结构,因为其占用的内存是连续分配的。由于内存连续,CPU 能把正在使用的数据缓存更久的时间。而且内存连续很容易计算索引,可以快速迭代数组里的所有元素。
  • 数组的类型信息可以提供每次访问一个元素时需要在内存中移动的距离。既然数组的每 个元素类型相同,又是连续分配,就可以以固定速度索引数组中的任意数据,速度非常快。
  • 在函数间传递数组: 根据内存和性能来看,在函数间传递数组是一个开销很大的操作。在函数之间传递变量时, 总是以值的方式传递的。如果这个变量是一个数组,意味着整个数组,不管有多长,都会完整复制,并传递给函数。将数组的地址传入函数,只需要在栈上分配 8 字节的内存给指针就可以。 这个操作会更有效地利用内存,性能也更好。不过要意识到,因为现在传递的是指针, 所以如果改变指针指向的值,会改变共享的内存。

二、切片内部实现

  • 切片可以按需自动增长和缩小。切片的动态增长是通过内置函数 append 来实现的。还可以通过对切片再次切片来缩小一个切片的大小。因为切片的底层内存也是在连续块中分配的,所以切片还能获得索引、迭代以及为垃圾回收优化的好处。
  • 切片是一个很小的对象,对底层数组进行了抽象,并提供相关的操作方法。切片有 3 个字段的数据结构,这些数据结构包含 Go 语言需要操作底层数组的元数据(如下图)。
  • 使用切片创建切片:
  • 如果切片的底层数组没有足够的可用容量,append 函数会创建一个新的底层数组,将被引用的现有的值复制到新数组里,再追加新的值,当这个 append 操作完成后,newSlice 拥有一个全新的底层数组,这个数组的容量是原来的两倍,函数 append 会智能地处理底层数组的容量增长。在切片的容量小于 1000 个元素时,总是会成倍地增加容量。一旦元素个数超过 1000,容量的增长因子会设为 1.25,也就是会每次增加 25%的容量。随着语言的演化,这种增长算法可能会有所改变,如下图:
  •  使用3个索引创建切片(slice[i:j:k]) 长度=j-i 容量=k-i,如下图:
  • 迭代切片:关键字 range 会返回两个值(第一个值是当前迭代到的索引位置,第二个值是该位置对应元素值)的一份副本。如下图:
  • 迭代返回的变量是一个迭代过程中根据切片依次赋值的新变量,所以 value 的地址总是相同的。要想获取每个元素的地址,可以使用切片变量和索引值。如下图:
  • // 创建一个整型切片
    // 其长度和容量都是 4 个元素
    slice := []int{10, 20, 30, 40}
    // 迭代每个元素,并显示值和地址
    for index, value := range slice { 
    	fmt.Printf("Value: %d Value-Addr: %X ElemAddr: %X\n",value, &value, &slice[index])
    } 
    
    Output: 
    Value: 10 Value-Addr: 10500168 ElemAddr: 1052E100
    Value: 20 Value-Addr: 10500168 ElemAddr: 1052E104
    Value: 30 Value-Addr: 10500168 ElemAddr: 1052E108
    Value: 40 Value-Addr: 10500168 ElemAddr: 1052E10C
    
  • 在函数间传递切片就是要在函数间以值的方式传递切片。由于切片的尺寸很小,在函数间复制和传递切片成本也很低。
  • 由于与切片关联的数据包含在底层数组里,不属于切片本身,所以将切片复制到任意函数的时候,对底层数组大小都不会有影响。复制时只会复制切片本身,不会涉及底层数组,如下图:

二、map内部实现

  • map是一个集合,可以使用类似处理数组和切片的方式迭代映射中的元素。但map是无序的集合,意味着没有办法预测键值对被返回的顺序。即便使用同样的顺序保存键值对,每次迭代map的时候顺序也可能不一样。无序的原因是map的实现使用了hash表,如下图。
  • 在函数间传递映射并不会制造出该映射的一个副本。实际上,当传递映射给一个函数,并对这个映射做了修改时,所有对这个映射的引用都会察觉到这个修改, 这个特性和切片类似,保证可以用很小的成本来复制映射。
原创文章 138 获赞 12 访问量 7万+

猜你喜欢

转载自blog.csdn.net/m0_38004619/article/details/105997102