Arrays 修改列表元素中的固定大小数组
我正在使用的Golang对我的LRU缓存解决方案进行微优化。我的解决方案是使用一个Arrays 修改列表元素中的固定大小数组,arrays,list,go,interface,micro-optimization,Arrays,List,Go,Interface,Micro Optimization,我正在使用的Golang对我的LRU缓存解决方案进行微优化。我的解决方案是使用一个map[int]*list.Element,其中每个list.listlist.Element是[]int,其中[0]是键,[1]是值 我试图从[]int移动到[2]int进行优化,但是我遇到了这样一个问题:在ee:=e.Value之后修改固定大小数组([2]int)(注意固定大小数组的[2]int类型)不再修改列表中的基础值,与w/ee:=e.Value.([]int)(请注意[]int类型)的情况不同,我想这是
map[int]*list.Element
,其中每个list.list
list.Element
是[]int
,其中[0]
是键,[1]
是值
我试图从[]int
移动到[2]int
进行优化,但是我遇到了这样一个问题:在ee:=e.Value之后修改固定大小数组([2]int)
(注意固定大小数组的[2]int
类型)不再修改列表中的基础值,与w/ee:=e.Value.([]int)
(请注意[]int
类型)的情况不同,我想这是非常合理的,因为切片基于引用,而固定大小的数组基于复制的值
我尝试过像e.Value。([2]int)[1]=…
,以及与:=&e.Value…
的各种组合,以及从[2]int
转换到[]int
,但这都会导致编译器错误
问:是否无法将容器/列表
与嵌入式数组一起使用,并对所述固定大小数组进行适当修改?如您所述:
我想这很有道理,因为切片基于引用,而固定大小的数组基于复制的值
因此,如果您想实现这一点,您需要通过存储指向数组的指针来使用对固定大小数组的引用,而不是数组值
这样,您就可以通过list元素修改底层数组
请参见此处的简单示例:
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)
}
编辑
请注意,这种行为不是此列表实现特有的,而是与类型断言在语言本身中的工作方式有关
请看这里:
引用该节的内容(并加上我自己的重点):
该语法借用了打开类型开关的子句,但使用了显式类型而不是类型关键字:value.(typeName)
和结果是一个新值,该值带有静态类型typeName
使用引用类型时,复制值不会影响您,因为复制指针值最终允许您更改相同的基础引用
但是,当数组本身是一个值时,将其指定给副本是没有意义的。当您试图直接修改数组时(不将类型断言分配给变量),Go甚至会在编译时捕捉到这一点,这样您就不会分配给这个“临时”副本,这显然是一个错误。这就是您在尝试执行此操作时出现所有语法错误的原因
要克服这一点(如果您不想使用指针),一种可能是您可以实现自己的列表
,借用您正在使用的实现,但将元素
的值设置为显式的[2]int
,而不是接口
这将消除进行类型断言的需要,并且您将能够修改底层数组。还尝试了[:]
技巧-返回第38行:无效操作e.Value。([2]int)[:](无法寻址的值片段)
请注意,一旦使用*[2]int
使用指针结构没有好处:*struct{key,value int}
,该结构将使代码更清晰。这里的问题是效率。我猜指向数组的指针会比片稍快一些,但至少在概念上,它听起来确实比list.Element中的嵌入式数组慢一些,特别是因为涉及到额外的垃圾收集,imho。这个答案是否表明确实没有办法修改list.Element本身中的嵌入数组?这似乎是一个相当武断的限制,我想不出任何好的理由。@cnst:我编辑了我的答案,以进一步说明为什么它在使用非引用值时不起作用。@eugenioy,哦,我明白了-创建副本的断言,现在至少开始理解为什么解引用组合不起作用了。现在唯一剩下的部分是,是否有一种方法可以在不使用断言的情况下访问这些内容,或者以任何其他方式修改值(当然,除了制作自己的列表实现之外)?否则,这似乎仍然是一个主要的性能限制,背后没有任何好的理由。@cnst,因为它存储为“接口{}”,所以除了通过类型断言之外,没有办法将其视为数组。您可以研究反射和/或使用“不安全”包上的功能,但所有这些都可能比只实现自己的列表更复杂(甚至效率更低)。