【问题标题】:Modify fixed-size array within List Element修改列表元素中的固定大小数组
【发布时间】:2018-12-18 21:00:57
【问题描述】:

我正在使用 https://golang.org/pkg/container/list/ 在 Golang 中对我的 LRU 缓存解决方案进行微优化。我的解决方案通过使用map[int]*list.Element,其中每个list.List list.Element[]int[0] 是关键,[1] 是价值。

我正在尝试从[]int 移动到[2]int 以进行优化,但是我遇到了在ee := e.Value.([2]int) 之后修改固定大小数组的问题(注意[2]int 类型为固定大小的数组),不再修改list.Element 中的基础值,不像ee := e.Value.([]int) 的情况(注意[]int 类型),我认为这很有意义,因为切片是基于引用的,而固定大小的数组基于复制的值。

我尝试过e.Value.([2]int)[1] = … 之类的东西,以及与:= &e.Value… 的各种组合,以及从[2]int 转换为[]int,但这都会导致编译器错误。

问:有没有办法将container/list 与嵌入式数组一起使用,并就地修改所述固定大小的数组?

【问题讨论】:

  • 还尝试了[:] 技巧——找回Line 38: invalid operation e.Value.([2]int)[:] (slice of unaddressable value)

标签: arrays list go interface micro-optimization


【解决方案1】:

正如您已经指出的:

我想这很有道理,因为切片是基于引用的,而固定大小的数组是基于复制的值

因此,如果您想完成这项工作,您需要通过将 指针 存储到数组而不是数组值来使用对固定大小数组的引用。

这样,您就可以通过列表元素修改底层数组。

这里有一个简单的例子:

package main

import (
    "container/list"
    "fmt"
)

func main() {

    l := list.New()

    // create a fixed size array and initialize it
    var arr [2]int
    arr[0] = 1
    arr[1] = 2
    // push a pointer to the array into the list
    elem := l.PushFront(&arr)
    // modify the stored array
    elem.Value.(*[2]int)[0] = 3
    // print the element from iterating the list
    for e := l.Front(); e != nil; e = e.Next() {
        fmt.Println(e.Value)
    }
    // print the underlying array, both are modified
    fmt.Println(arr)

}

编辑

请注意,此行为不是此列表实现所特有的,而是与类型断言在语言本身中的工作方式有关。

看这里:

https://golang.org/doc/effective_go.html#interface_conversions

引用该部分(并添加我自己的重点):

语法从打开类型切换的子句借用,但使用显式类型而不是 type 关键字:value.(typeName) 并且结果是具有静态类型 typeName 的新值

使用引用类型时,复制值不会影响您,因为复制指针值最终允许您更改相同的底层引用。

但是当数组本身就是一个值时,赋值给副本就没有意义了。当您尝试直接修改数组(不将类型断言分配给变量)时,Go 甚至会在编译时捕获它,这样您就不会分配给这个“临时”副本,这显然是一个错误。这就是您在尝试执行此操作时遇到所有语法错误的原因。

要克服这个问题(如果您不想使用指针),一种可能是您实现自己的list,借鉴您正在使用的实现,但使您的Element 的值显式@ 987654326@而不是接口。

这将消除进行类型断言的需要,并且您将能够修改底层数组。

【讨论】:

  • 请注意,一旦使用*[2]int,使用指针结构:*struct { key, value int } 并没有任何好处,并且该结构将使代码更清晰。
  • 这里的问题是效率。我猜指向数组的指针会比切片稍快,但是,至少从概念上讲,它听起来确实比 list.Element 中的嵌入式数组慢一点,尤其是。由于涉及额外的垃圾收集,恕我直言。这个答案是否表明真的没有办法修改 list.Element 本身的嵌入式数组?这似乎是一个相当武断的限制,没有我能想到的任何好的理由。
  • @cnst:我编辑了我的答案以扩展为什么它在使用非参考值时不起作用。
  • @eugenioy,哦,我明白了——创建副本的断言,现在这至少开始说明为什么没有解除引用的组合会起作用。现在剩下的唯一部分是是否有一种方法可以在没有断言的情况下访问其中的任何内容,或者以任何其他方式修改值(当然,除了制作您自己的列表实现)?否则,这似乎仍然是一个主要的性能限制,没有任何好的理由。
  • @cnst,因为它被存储为一个“接口{}”,所以除了类型断言之外,没有办法将它视为一个数组。您可以围绕反射和/或使用“不安全”包上的功能进行调查,但所有这些可能比仅实现您自己的列表更复杂(甚至可能效率更低)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-22
  • 1970-01-01
  • 2017-07-05
  • 1970-01-01
  • 2011-08-16
相关资源
最近更新 更多