温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

slice是如何扩容的

发布时间:2021-10-12 15:56:57 来源:亿速云 阅读:120 作者:iii 栏目:编程语言

本篇内容介绍了“slice是如何扩容的”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

问题1,slice的底层数据结构

我擦,这么直接的嘛?

我猜是数组加链表,结果猜错了0分。

翻看源码。

runtime/slice.go

type slice struct {
	array unsafe.Pointer  //数据结构是简单粗暴的数组指针
	len   int
	cap   int
}

问题2,slice是如何扩容的

又猜错了~

还是继续看源码吧

从源码找了半天,发现一个这。

growslice handles slice growth during append.

so,就是你了。

func growslice(et *_type, old slice, cap int) slice { // 第三个cap,新的最小容量
	//巴拉巴拉巴拉 一堆判断

	newcap := old.cap  //变量存储空间大小
	doublecap := newcap + newcap  //双倍空间大小
	if cap > doublecap {  //如果历史空间大于双倍的容量,新的最小容量
		newcap = cap
	} else {
		//如果长度小于 1024  新长度就是2倍老容量
		if old.len < 1024 {
			newcap = doublecap
		} else {
			//当大于1024 走公式   newcap += newcap / 4,直到newcap大于等于老cap
			for 0 < newcap && newcap < cap {
				newcap += newcap / 4
			}
			if newcap <= 0 {
				newcap = cap
			}
		}
	}

	var overflow bool
	var lenmem, newlenmem, capmem uintptr
	//对et的size做匹配,获取需要申请的空间大小
	//1 不处理直接分配
	//系统指针大小 进行计算和位运算
	//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):
		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)
	}

	//如果append一次超过过多的元素新增,直接报错,越界,超出容量大小
	if overflow || capmem > maxAlloc {
		panic(errorString("growslice: cap out of range"))
	}

	var p unsafe.Pointer //申请新的内存,并把指针指向p
	if et.ptrdata == 0 {
		p = mallocgc(capmem, nil, false)
		memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
	} else {
		p = mallocgc(capmem, et, true)
		if lenmem > 0 && writeBarrier.enabled {
			bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem)
		}
	}
	//将老的数据移动到新的数据
	memmove(p, old.array, lenmem)

	return slice{p, old.len, newcap}
}

总结

其实可以看出,golang的切片扩容是比较粗暴的,直接赋值拷贝。不过,golang区分的长度和容量两种单位计量,一般会提前分配足够的cap,可以减少maclloc的次数。

“slice是如何扩容的”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI