千家信息网

slice是如何扩容的

发表于:2025-01-31 作者:千家信息网编辑
千家信息网最后更新 2025年01月31日,本篇内容介绍了"slice是如何扩容的"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!问题1,slic
千家信息网最后更新 2025年01月31日slice是如何扩容的

本篇内容介绍了"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是如何扩容的"的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注网站,小编将为大家输出更多高质量的实用文章!

0