范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

Golang切片原理

  在Golang语言开发过程中,我们经常会用到数组和切片数据结构,数组是固定长度的,而切片是可以扩张的数组,那么切片底层到底有什么不同?接下来我们来详细分析一下内部实现。一、内部数据结构
  首先我们来看一下数据结构type slice struct {     array unsafe.Pointer// 数据     len int             // 长度     cap int             // 容量 }
  这里的array其实是指向切片管理的内存块首地址,而len就是切片的实际使用大小,cap就是切片的容量。
  我们可以通过下面的代码输出slice:package main  import ( 	"fmt" 	"unsafe" )  func main() { 	  data := make([]int,0,3)  	  fmt.Println(unsafe.Sizeof(data),len(data),cap(data)) 	  // Output: 24,0,3  	  // 通过指针方式拿到切片内部的值 	  ptr := unsafe.Pointer(&data) 	  opt := (*[3]int)(ptr)  	  fmt.Println(opt[0],opt[1],opt[2]) 	  // Output: 824634891936,0,3  	  data = append(data, 4) 	  fmt.Println(unsafe.Sizeof(data)) 	  // Output: 24    	  shallowCopy := data[:1] 	  ptr1 := unsafe.Pointer(&shallowCopy) 	  opt1 := (*[3]int)(ptr1)    	  fmt.Println(opt1[0]) 	  // Output: 824634891936 }
  这么分析下来,我们可以了解如下内容:切片的数据结构大小是24,int占8字节,指针占8字节在不发生扩容的情况下,切片指向的首选地址不变常用的关于切片的方法有make,copy二、声明
  使用一个切片通常有两种方法:
  一种是var slice []int,称为声明;
  另一种是slice = make([]int, len, cap)这种方法,称为分配内存。三、创建make
  创建一个slice,实质上是在分配内存。func makeslice(et *_type, len, cap int) unsafe.Pointer {     // 获取需要申请的内存大小    mem, overflow := math.MulUintptr(et.size, uintptr(cap))    if overflow || mem > maxAlloc || len < 0 || len > cap {    	    mem, overflow := math.MulUintptr(et.size, uintptr(len))    	if overflow || mem > maxAlloc || len < 0 {    		  panicmakeslicelen() // 超过内存限制|超过最大分配量|长度小于0    	}    	    panicmakeslicecap() // 长度大于容量    }      // 分配内存      // runtime/malloc.go    return mallocgc(mem, et, true) }
  这里跟一下细节,math.MulUintptr是基于底层的指针计算乘法的,这样计算不会导致超出int大小,这个方法在后面会经常用到。func MulUintptr(a, b uintptr) (uintptr, bool) { 	  if a|b < 1<<(4*sys.PtrSize) || a == 0 { // sys.PtrSize=8 	  	  return a * b, false // a和b都小于32位,乘积肯定小于64位 	  } 	  overflow := b > MaxUintptr/a // MaxUintptr= ^uintptr(0),也就是64个1 	  return a * b, overflow }
  同样,对于int64的长度,也有对应的方法func makeslice64(et *_type, len64, cap64 int64) unsafe.Pointer { 	  len := int(len64) 	  if int64(len) != len64 { 	  	  panicmakeslicelen() 	  }    	  cap := int(cap64) 	  if int64(cap) != cap64 { 	  	  panicmakeslicecap() 	  }    	  return makeslice(et, len, cap) }
  而实际分配内存的操作调用mallocgc这个分配内存的函数,这个函数以后再分析。四、扩容机制
  我们了解切片和数组最大的不同就是切片能够自动扩容,接下来看看切片是如何扩容的func growslice(et *_type, old slice, cap int) slice {     // 前置条件 	  if cap < old.cap { 	  	  panic(errorString("growslice: cap out of range")) 	  }              // 如果新切片的长度为0,返回空数据,长度为旧切片的长度 	  if et.size == 0 {  	  	  return slice{unsafe.Pointer(&zerobase), old.len, cap} 	  }          // 1、先记录原先的容量 	  newcap := old.cap       // 2、尝试2倍扩容 	  doublecap := newcap + newcap 	  if cap > doublecap {             // 如果指定容量大于原有容量的2倍,则按新增容量申请 	  	  newcap = cap 	  } else {           // 3、如果指定容量小于原容量2倍,则按以下的计算方式为新容量 	  	  if old.len < 1024 { // 如果原容量小于1024,新容量是原容量的2倍 	  	  	  newcap = doublecap 	  	  } else { // 原容量大于1024,按原容量的1.25倍递增 	  	  	  for 0 < newcap && newcap < cap { 	  	  		    newcap += newcap / 4 	  	      } 	  	  	  if newcap <= 0 { // 校验容量是否溢出 	  	  		    newcap = cap 	  	  	  } 	  	  } 	  }    	  var overflow bool 	  var lenmem, newlenmem, capmem uintptr 	  // 为加速计算(不用乘除法)       // 对于2的幂,使用变位处理       // 下面的处理使内存对齐 	  switch { 	  case et.size == 1: 	  	  lenmem = uintptr(old.len) 	  	  newlenmem = uintptr(cap) 	  	  capmem = roundupsize(uintptr(newcap)) 	  	  overflow = uintptr(newcap) > maxAlloc 	  	  newcap = int(capmem) 	  case et.size == sys.PtrSize: 	  	  lenmem = uintptr(old.len) * sys.PtrSize 	  	  newlenmem = uintptr(cap) * sys.PtrSize 	  	  capmem = roundupsize(uintptr(newcap) * sys.PtrSize) 	  	  overflow = uintptr(newcap) > maxAlloc/sys.PtrSize 	  	  newcap = int(capmem / sys.PtrSize) 	  case isPowerOfTwo(et.size): // 2的幂 	  	  var shift uintptr 	  	  if sys.PtrSize == 8 { 	  	  	  // Mask shift for better code generation. 	  	  	  shift = uintptr(sys.Ctz64(uint64(et.size))) & 63 	  	  } else { 	  	  	  shift = uintptr(sys.Ctz32(uint32(et.size))) & 31 	  	  } 	  	  lenmem = uintptr(old.len) << shift 	  	  newlenmem = uintptr(cap) << shift 	  	  capmem = roundupsize(uintptr(newcap) << shift) 	  	  overflow = uintptr(newcap) > (maxAlloc >> shift) 	  	  newcap = int(capmem >> shift) 	  default: 	  	  lenmem = uintptr(old.len) * et.size 	  	  newlenmem = uintptr(cap) * et.size 	  	  capmem, overflow = math.MulUintptr(et.size, uintptr(newcap)) 	  	  capmem = roundupsize(capmem) 	  	  newcap = int(capmem / et.size) 	  }    	  // 判断是否会溢出,是否会超出可分配 	  if overflow || capmem > maxAlloc { 	  	  panic(errorString("growslice: cap out of range")) 	  }          // 内存分配 	  var p unsafe.Pointer 	  if et.ptrdata == 0 { 	  	  p = mallocgc(capmem, nil, false) 	  	  // 回收内存 	  	  memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem) 	  } else { 	  	  // Note: can"t use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory. 	  	  p = mallocgc(capmem, et, true) 	  	  if lenmem > 0 && writeBarrier.enabled { // gc 	  	  	  // Only shade the pointers in old.array since we know the destination slice p 	  	  	  // only contains nil pointers because it has been cleared during alloc. 	  	  	  bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem) 	  	  } 	  }       // 数据拷贝 	  memmove(p, old.array, lenmem)    	  return slice{p, old.len, newcap} }
  这里可以看到,growslice是返回了一个新的slice,也就是说如果发生了扩容,会发生拷贝。
  所以我们在使用过程中,如果预先知道容量,可以预先分配好容量再使用,能提高运行效率。五、深拷贝
  copy这个函数在内部实现为slicecopyfunc slicecopy(to, fm slice, width uintptr) int {     // 前置条件 	  if fm.len == 0 || to.len == 0 { 		    return 0 	  }  	  n := fm.len 	  if to.len < n { 		    n = to.len 	  }     // 元素长度为0,直接返回 	  if width == 0 { 		    return n 	  }  	  size := uintptr(n) * width     // 拷贝内存 	  if size == 1 { 		    *(*byte)(to.array) = *(*byte)(fm.array) // known to be a byte pointer 	  } else { 		    memmove(to.array, fm.array, size) 	  } 	  return n }
  还有关于字符串的拷贝func slicestringcopy(to []byte, fm string) int {     // 前置条件 	  if len(fm) == 0 || len(to) == 0 { 		    return 0 	  }  	  n := len(fm) 	  if len(to) < n { 		    n = len(to) 	  }  	  memmove(unsafe.Pointer(&to[0]), stringStructOf(&fm).str, uintptr(n)) 	  return n }
  这里显示了可以把string拷贝成[]byte,不能把[]byte拷贝成string。六、总结
  1、切片的数据结构是 array内存地址,len长度,cap容量
  2、make的时候需要注意 容量 * 长度 分配的内存大小要小于264,并且要小于可分配的内存量,同时长度不能大于容量。
  3、内存增长的过程:如果指定的容量大于原先的2倍,就按照指定的容量如果原先的容量小于1024,按2倍容量扩张如果原先的容量大于1024,就按1.25倍扩张,会小于指定的容量容量大小确定完之后,会进行内存对齐
  4、当发生内存扩容时,会发生拷贝数据的现象,影响程序运行的效率,如果可以,要先分配好指定的容量
  5、关于拷贝,可以把string拷贝成[]byte,不能把[]byte拷贝成string。

何小鹏,小鹏汽车创始人,不甘空虚和痛苦的创业者何小鹏,小鹏汽车创始人,从软件到硬件,从移动互联网到汽车,何小鹏身上充满了创业基因。1977年,何小鹏出生在湖北黄石的一个普通工人家庭,为了帮家里减轻经济负担,他很小的时候,就开始能用三年的手机?戳这里,性价比卫冕之王是它没错了眼看618就要到了,各家电商之间摩拳擦掌都预备上了,Redmi也在这两天开始凑热闹,Redmi一向喜欢打高性价比的招牌,简直就是把消费者的胃口拿捏的死死的,这不,又赶在618之前赶凭骁龙888和120W,iQOO7性能毫无敌手了?在2019年,一个将性能作为最大卖点的品牌诞生,它用一款又一款在性能上都有优于同价位手机的配置打入市场,虽然成立时间晚,但也力求在市场上分一杯羹。到了现在,它发布的产品很多,收获的RedmiK40游戏增强版性能一流,续航方面也很赞哦经过一段时间的等待,RedmiK40游戏增强版终于是来到了我们的面前,这款游戏旗舰先是在性能方面搭载了很高光的配置,且是一款搭载了67W快充的机型哦,不论是大家玩儿游戏时出现的卡顿vivo蔡司组成联合研发团队,共同发力移动影像现如今消费者在购买手机时,主要会考虑哪些因素?性能续航充电和拍照,都是大家所关注的重点。但目前同级别的手机,在性能续航和充电等方面基本处于同一水平,很难拉开明显差距。正因如此,所以12。99万起售,全新本田思域正式上市,标配1。5T四缸机9月24日晚间,东风本田第十一代思域正式上市,新车共推出6款车型,售价区间为12。9916。39万元,新车的外观和内饰都有了非常明显的变化,基础配置更丰富,并且全系标配1。5T四缸14。37万起售,雪铁龙凡尔赛C5X正式上市,预售期订单已超9000台日前,雪铁龙凡尔赛C5X正式上市,新车共推出四款配置车型,售价区间为14。3718。67万元,与此前公布的预售价格保持一致,据悉,这款新车从8月初开启预售,截止到正式上市前,它的订15。49万起售,新款日产逍客上市,2。0L自吸发动机越来越少见日前,2022款日产逍客正式上市,新车共推出四款配置车型,售价区间为15。4918。89万,新车依旧搭载2。0L四缸自然吸气发动机,这台同级别车型中越来越少见了。新车属于例行年度改顶配77。77万,上汽奥迪A7L正式预售,标配3。0TV67速双离合日前,上汽奥迪首款车型奥迪A7L正式上市,新车共推出7款车型,整体预售价格区间为59。9777。77万元,全系标配3。0TV6涡轮增压发动机48V轻混系统,传动系统匹配7速湿式双离2022款本田思域购车手册,首推240TURBOCVT劲动版2022款本田思域已经正式上市,新车共推出了6款车型,价格区间为12。9916。39万,其中搭载1。5T低功率发动机的车型有两款,搭载1。5T高功率发动机的车型有四款,那么哪款车型迈锐宝XL降4。5万,标配9AT,车长超4。9米,入门车型足够家用雪佛兰迈锐宝XL曾经也是一款十分畅销的车型,性价比高是它最大的优势,它的实际售价几乎与德系紧凑型车持平,不过当年上汽通用的三缸机战略毁了它,销量大幅下滑,不过在重新换装四缸机之后,
吉利真正的旗舰SUV,长超5米,气场不输沃尔沃XC90,带你看领克09虽然这段时间以来,受到芯片短缺的问题,包括吉利汽车在内的多家汽车品牌,整体销量都受到了影响。但它们旗下的不少明星车型,热度始终处在上涨的趋势中,正如前段时间比较火爆的吉利旗舰SUV有颜值还有实力,欧尚X7PLUS上市在即,预售7。99万起,能火吗?长安欧尚的崛起,在家用车市场中有着不少值得学习之处,而它旗下的车型之所以能够得到消费者认可,一方面原因是背靠长安汽车这棵大树,另一方面的原因则是采用了越级竞争这样的战术。现如今,长Mac字体安装的方法Mac怎么安装新字体?Mac字体安装教程现代简约细体字体(附mac字体安装教程)macdown为您带来了现代简约细体字体,这是一款手工现代字体样式,风格简约大方,干净明亮,非常适合婚礼明信片,海报,名片,杂志等!还在等什便宜也有好货!5。98万起的宝骏RC5,能跑又能装,适合工薪族都说便宜没好货,但在民族品牌的冲击之下,已经开始内卷的汽车领域里,小编其实并不认同这句话。因为哈弗H6理想ONE的相继热销,说明了低价高质的精品车也有不少。即便将视线转到10万以内ps笔刷植物蝴蝶艺术花纹笔刷分享,ps笔刷如何安装?一款非常好看的PS艺术笔刷带给大家,如果你需要制作胡铁植物等效果的设计图案,那么macdown为您提供的PS植物蝴蝶艺术笔刷,一定可以帮到您的,这套设备是为您的下一个项目添加装饰品体育运动摄影后期LR调色预设分享体育运动摄影后期LR调色预设内含13个体育运动摄影后期调色lr预设效果,每一个预设都融合了亮度,对比度,色调和色调的专业知识。通过使用此预设,您可以轻松获得所需的结果。体育运动摄影植物大战僵尸formac好玩吗?mac植物大战僵尸游戏测评分享植物大战僵尸formac好玩吗?植物大战僵尸mac版是专门针对MACOS用户研制的休闲益智游戏,植物大战僵尸mac版集成了即时战略,塔防御战和卡片收集等要素,游戏的内容就是玩家控制MyBrotherRabbit游戏攻略,mybrotherrabbit豆子怎么获取?MyBrotherRabbitforMac是一款冒险游戏,它成功地将生活的温柔与想象力融合在一起,创造出一个美丽而动人的旅程,通过充满谜题和寻找和寻找挑战的万花筒般的景观展开。孩子解读Geekbench4,Geekbench4系统检测跑分工具入门指南Geekbench4forMac(检测系统性能工具),Geekbench破解版提供了快速的基准设计一套全面和准确地衡量处理器和内存性能。旨在使基准易于运行,易于理解。这次小编就带大MateTranslate翻译软件如何在所有设备上使用同步?MateTranslate是Macos系统上一款多国语言即时翻译工具,支持103种语言之间的即时互译,还可以在你的所有设备之间轻松同步,并且直接通过你的Mac菜单栏访问和使用!Ma15种火烈鸟摄影lr预设15种火烈鸟摄影lr预设包含15种不同的摄影后期精修调色Lr预设,它们可以轻松调整以适合您的图像。预设兼容RAW和JPEG(Mac和PC),你可以使用这组lr调色预设为你的图像添加