算法思路
- 预分配策略:在创建切片时,根据预估的追加操作次数和每次追加元素数量,预先分配足够大的容量。这样可以减少频繁扩容的次数。
- 动态扩容步长调整:当需要扩容时,不再采用固定的扩容倍数(如Go语言原生切片扩容是当前容量不足时,新容量为原容量的2倍,若原容量小于1024),而是根据已追加元素数量和当前容量的比例来动态调整扩容步长。例如,当已追加元素数量达到当前容量的80%时进行扩容,扩容步长为当前容量的50%。这样可以避免扩容过度或不足的情况。
实现步骤
- 创建切片时的预分配:在初始化切片时,通过一个函数接收预估的追加元素总数
totalExpectedAdditions
和每次追加的大致元素数 averageAdditionsPerOperation
,计算出预估的容量并进行预分配。
func createSliceWithEstimation(totalExpectedAdditions, averageAdditionsPerOperation int) []int {
estimatedCapacity := (totalExpectedAdditions + averageAdditionsPerOperation - 1) / averageAdditionsPerOperation * averageAdditionsPerOperation
return make([]int, 0, estimatedCapacity)
}
- 动态扩容函数:在追加元素时,检查当前容量是否足够。若不足,调用扩容函数。
func dynamicGrow(slice []int, newElementCount int) []int {
currentLen := len(slice)
currentCap := cap(slice)
if currentLen+newElementCount > currentCap {
newCap := currentCap
if float64(currentLen)/float64(currentCap) >= 0.8 {
newCap = currentCap + (currentCap / 2)
} else {
newCap = currentCap + newElementCount
}
newSlice := make([]int, currentLen, newCap)
copy(newSlice, slice)
return newSlice
}
return slice
}
- 追加操作:在追加元素时,先调用
dynamicGrow
函数确保有足够容量,再进行元素追加。
func appendElements(slice []int, elementsToAppend []int) []int {
slice = dynamicGrow(slice, len(elementsToAppend))
return append(slice, elementsToAppend...)
}
优势
- 减少扩容次数:预分配策略减少了初始阶段频繁扩容的开销,使得在追加操作开始时就有足够的空间容纳元素。
- 优化内存使用:动态扩容步长调整避免了过度扩容造成的内存浪费,同时也防止了扩容不足导致的频繁扩容。在保证性能的同时,提高了内存使用效率。
- 适应不同场景:该算法可以根据不同的业务场景,通过调整预分配容量和动态扩容的比例等参数,更好地适应各种追加操作模式,具有较高的灵活性。