在Go中使用添加前缀的机制是什么?

善行 :

假设我有一个slicetype 切片int在声明时,我将第三个参数设置为size,我相信可以size通过设置capslice 参数为至少int 保留内存

slice:=make([]int,0,size)

现在,假设我有一个整数变量value要将其添加到切片的末尾,我使用

slice=append(slice,value)

如果当前切片中的元素数少于size,则无需添加整个基础数组到新位置即可添加新元素。

另外,如果我想在前面加上valueslice,作为建议在这里这里,我用

slice=append([]int{value},slice...)

我的问题是,在这种情况下会发生什么?如果元素数仍少于size,则如何将元素存储在内存中?假设make()在调用函数时分配是连续的,是否所有现有元素都右移以释放第一个值空间?还是重新分配内存并复制所有元素?

询问的原因是我希望我的程序尽可能快,并且想知道这是否可能导致其速度降低。如果是这样,是否有其他替代方式可以使时间效率更高?

icza:

带有切片和复制

内置函数append()始终元素附加到切片。您不能单独使用它来添加元素。

话虽如此,如果您要在其前添加元素的切片容量大于长度(在其元素后有“自由”空间),则可以重新切片原始切片,将所有元素复制到更高的索引以腾出空间对于新元素,然后将元素设置为第0 索引。这将不需要新的分配。它看起来像这样:

func prepend(dest []int, value int) []int {
    if cap(dest) > len(dest) {
        dest = dest[:len(dest)+1]
        copy(dest[1:], dest)
        dest[0] = value
        return dest
    }

    // No room, new slice need to be allocated:
    // Use some extra space for future:
    res := make([]int, len(dest)+1, len(dest)+5)
    res[0] = value
    copy(res[1:], dest)
    return res
}

测试它:

s := make([]int, 0, 5)
s = append(s, 1, 2, 3, 4)
fmt.Println(s)
s = prepend(s, 9)
fmt.Println(s)
s = prepend(s, 8)
fmt.Println(s)

输出(在Go Playground上尝试):

[1 2 3 4]
[9 1 2 3 4]
[8 9 1 2 3 4]

注意:如果没有新元素的余地,由于性能现在很重要,我们不仅会这样做:

res := append([]int{value}, dest...)

因为它执行的分配和复制操作超出了所需的数量:因此为文字([]int{value}分配了一个分片,然后append()在附加dest其后分配了一个新的分片

取而代之的是,我们的解决方案仅分配一个新数组(by make(),甚至为将来的增长保留一些空间),然后将其设置value为第一个元素并进行复制dest(前面的元素)。

带链表

如果需要多次处理,则正常切片可能不是正确的选择。更快的替代方法是使用链表,在链表前添加元素无需分配切片/数组和复制,您只需创建一个新的node元素,然后将其指向旧根即可将其指定为根(第一个元素)。

标准库在container/list包中提供了常规实现

通过手动管理更大的支持阵列

坚持正常的切片和阵列,还有另一种解决方案。

如果您愿意自己管理更大的支持阵列(或片),可以通过在使用片之前保留可用空间来实现。前置时,您可以从支持的较大数组或片中创建一个新的分片值,该值从索引开始,该索引为要保留的1个元素留出空间。

没有完整性,仅用于演示:

var backing = make([]int, 15) // 15 elements
var start int

func prepend(dest []int, value int) []int {
    if start == 0 {
        // No more room for new value, must allocate bigger backing array:
        newbacking := make([]int, len(backing)+5)
        start = 5
        copy(newbacking[5:], backing)
        backing = newbacking
    }

    start--
    dest = backing[start : start+len(dest)+1]
    dest[0] = value
    return dest
}

测试/使用它:

start = 5
s := backing[start:start] // empty slice, starting at idx=5
s = append(s, 1, 2, 3, 4)
fmt.Println(s)
s = prepend(s, 9)
fmt.Println(s)
s = prepend(s, 8)
fmt.Println(s)

// Prepend more to test reallocation:
for i := 10; i < 15; i++ {
    s = prepend(s, i)
}
fmt.Println(s)

输出(在Go Playground上尝试):

[1 2 3 4]
[9 1 2 3 4]
[8 9 1 2 3 4]
[14 13 12 11 10 8 9 1 2 3 4]

分析:当backing切片中有空间可以放置时,此解决方案不进行分配,也不进行复制发生的所有事情是,它从该backing切片中创建一个新切片,该切片覆盖目标+1空间中要添加的值,并对其进行设置并返回此切片值。你真的不能比这更好。

如果没有空间,那么它将分配更大的backing切片,复制旧内容的内容,然后执行“正常”前缀。

使用棘手的切片

想法:假设您总是将元素按向后的顺序存储在切片中。

将您的元素以向后的顺序存储在切片中意味着预装成为追加!

因此,要“预填充”元素,只需使用即可append(s, value)就这样。

是的,这有其有限的用途(例如,将其追加到以相反顺序存储的切片中,其问题和复杂性与“常规”切片和预装操作相同),并且您失去了许多便利(使用for range仅举一例列出元素的能力)但是,从性能角度来看,仅使用即可胜任预取值append()

注意:迭代以向后顺序存储元素的元素必须使用向下循环,例如:

for i := len(s) - 1; i >= 0; i-- {
    // do something with s[i]
}

最后说明:所有这些解决方案都可以轻松扩展为在切片前面加上一个值。一般来说reslicing当额外的空间不是+1,但是+len(values),并没有简单的设置dst[0] = value,而是调用copy(dst, values)

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章