001-Golang1.17源码分析之slice

Golang1.17源码分析之slice-001

Golang1.17 学习笔记001

一、slice

核心概念:切片和数组之间、切片和切片之间都是共享内存的。只要切片不发生扩容,那么都是操作的同一个地址

arr := [5]int{1,2,3,4,5}
s1 := arr[2:5]
arr[2] = 9
fmt.Println(s1) // [9,4,5] -- 共享内存


s1 = append(s1,6) // 发生扩容,不再共享内存
arr[3] = 10
fmt.Println(s1) // [9,4,5,6]
fmt.Println(arr) // [1,2,9,4,5]

源码包:runtime/slice.go

数据结构:

type slice struct {
	array unsafe.Pointer // 指向底层数组的指针
	len   int           // 长度
	cap   int           // 容量
}

创建:这里创建规则跟 go 的内存管理有关,建议看完内存管理之后再看这里

  • 小于 32kb 的对象从 per-p 缓存空闲列表中创建

  • 大于 32kb 的对象从堆中进行创建

  • 使用 new 创建 slice 无法直接使用 slice[0] 这种来赋值,需要使用 append 添加,此时才会给 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()
		}
		panicmakeslicecap()
	}

	return mallocgc(mem, et, true)
}

// Small objects are allocated from the per-P cache's free lists.
// Large objects (> 32 kB) are allocated straight from the heap.
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
    if size <= maxSmallSize { // maxSmallSize = _MaxSmallSize  = 32768
        size = uintptr(class_to_size[sizeclass])
		spc := makeSpanClass(sizeclass, noscan)
		span = c.alloc[spc]
		v := nextFreeFast(span)
		if v == 0 {
			v, span, shouldhelpgc = c.nextFree(spc)
		}
		x = unsafe.Pointer(v)
    } else {
        span, isZeroed = c.allocLarge(size, needzero && !noscan, noscan)
		span.freeindex = 1
		span.allocCount = 1
		x = unsafe.Pointer(span.base())
    }
}

复制:

为什么 copy(小,大) 可以不报错,原因如下

func slicecopy(toPtr unsafe.Pointer, toLen int, fromPtr unsafe.Pointer, fromLen int, width uintptr) int {
	if fromLen == 0 || toLen == 0 {
		return 0
	}

	n := fromLen
	if toLen < n {
		n = toLen
	}	
	
	if size == 1 { // common case worth about 2x to do here
		// TODO: is this still worth it with new memmove impl?
		*(*byte)(toPtr) = *(*byte)(fromPtr) // known to be a byte pointer
	} else {
		memmove(toPtr, fromPtr, size)
	}
	return n
}

扩容:

  • 如果新容量大于 2 倍原容量,那么就扩容至新容量
  • 如果新容量小于 2 倍原容量,且原容量小于 1024,那么新容量扩容至 2 倍原容量
  • 如果新容量小于 2 倍原容量,且原容量大于 1024,那么新容量一直按原容量的 1/4 进行扩容,直到当前容量大于传入的新容量
func growslice(et *_type, old slice, cap int) slice {
	newcap := old.cap
	doublecap := newcap + newcap
	if cap > doublecap {
		newcap = cap
	} else {
		if old.cap < 1024 {
			newcap = doublecap
		} else {
			// Check 0 < newcap to detect overflow
			// and prevent an infinite loop.
			for 0 < newcap && newcap < cap {
				newcap += newcap / 4
			}
			// Set newcap to the requested cap when
			// the newcap calculation overflowed.
			if newcap <= 0 {
				newcap = cap
			}
		}
	}
	
	memmove(p, old.array, lenmem)
	return slice{p, old.len, newcap}
}

参考文献:《GO专家编程》之 slice 篇、Golang1.17源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值