请参阅这个游乐场。正如你所看到的,我在结构中有一个切片。我还有一个可用于向切片添加新元素的方法。这很好用。
但现在我的问题是我想扩展该方法,以便它留下切片的
n
元素。因此,当添加新元素时,应删除“最旧的”元素并添加新元素。
我该怎么做?没有我可以使用的开箱即用的软件包吗?
例如,
addimport.go
:
package main
import (
"fmt"
"time"
)
type Statistics struct {
LastScan time.Time
Imports []Import
}
type Import struct {
text string
}
func (s *Statistics) AddImport(i Import) {
// only the last n entries are kept
const n = 2 // n > 0 and small
if len(s.Imports) >= n {
copy(s.Imports, s.Imports[len(s.Imports)-n+1:])
s.Imports = s.Imports[:n-1]
}
s.Imports = append(s.Imports, i)
}
func main() {
s := Statistics{}
fmt.Println(len(s.Imports), cap(s.Imports), s.Imports)
s.AddImport(Import{text: "myText1"})
s.AddImport(Import{text: "myText2"})
s.AddImport(Import{text: "myText3"})
fmt.Println(len(s.Imports), cap(s.Imports), s.Imports)
}
游乐场:https://play.golang.org/p/204-uB8Zls
输出:
0 0 []
2 2 [{myText2} {myText3}]
代码应该相当高效。 Go 有一个基准测试包。以下是 peterSO、Kaedys 和 gonutz 解决方案的基准测试结果。
$ go test -bench=. addimport_test.go
BenchmarkAddImport/PeterSO-4 100000 16145 ns/op 96 B/op 3 allocs/op
BenchmarkAddImport/Kaedys-4 30000 59344 ns/op 32032 B/op 502 allocs/op
BenchmarkAddImport/Gonutz-4 30000 60447 ns/op 32032 B/op 502 allocs/op
addimport_test.go
:
package main
import (
"testing"
"time"
)
type Statistics struct {
LastScan time.Time
Imports []Import
}
type Import struct {
text string
}
func (s *Statistics) AddImportPeterSO(i Import) {
// only the last n entries are kept
const n = 2 // n > 0 and small
if len(s.Imports) >= n {
copy(s.Imports, s.Imports[len(s.Imports)-n+1:])
s.Imports = s.Imports[:n-1]
}
s.Imports = append(s.Imports, i)
}
const numberToKeep = 2
func (s *Statistics) AddImportKaedys(i Import) {
s.Imports = append(s.Imports, i)
if len(s.Imports) > numberToKeep {
s.Imports = s.Imports[len(s.Imports)-numberToKeep:]
}
}
func (s *Statistics) AddImportGonutz(i Import) {
s.Imports = append(s.Imports, i)
const max = 2
if len(s.Imports) > max {
s.Imports = s.Imports[1:]
}
}
func benchmarkAddImport(b *testing.B, addImport func(*Statistics, Import)) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
var s Statistics
for j := 0; j < 1000; j++ {
addImport(&s, Import{})
}
}
}
func BenchmarkAddImport(b *testing.B) {
b.Run("PeterSO", func(b *testing.B) {
benchmarkAddImport(b, (*Statistics).AddImportPeterSO)
})
b.Run("Kaedys", func(b *testing.B) {
benchmarkAddImport(b, (*Statistics).AddImportKaedys)
})
b.Run("Gonutz", func(b *testing.B) {
benchmarkAddImport(b, (*Statistics).AddImportGonutz)
})
}
游乐场:https://play.golang.org/p/Q2X_T5Vofe
这个问题的一般形式是循环缓冲区:Circular buffer.
如果您想从切片中删除第一个元素(其中最长的元素)
s
,您可以简单地执行s = s[1:]
,这使得 s 成为对从旧切片的第一个元素开始的切片的引用.
我已经修改了你的代码来执行此操作:
https://play.golang.org/p/Eu-KLoinz0
package main
import (
"fmt"
"time"
)
type Statistics struct {
LastScan time.Time
Imports []Import
}
type Import struct {
text string
}
func (s *Statistics) AddImport(i Import) {
s.Imports = append(s.Imports, i)
const max = 2
if len(s.Imports) > max {
s.Imports = s.Imports[len(s.Imports)-max:]
}
}
func main() {
s := Statistics{}
s.AddImport(Import{text: "myText1"})
fmt.Println(s.Imports)
s.AddImport(Import{text: "myText2"})
fmt.Println(s.Imports)
s.AddImport(Import{text: "myText3"})
fmt.Println(s.Imports)
s.AddImport(Import{text: "myText4"})
fmt.Println(s.Imports)
}
子切片。语法
slice[n:m]
返回输入切片从 n 到 m-1 的部分。可以省略其中之一来分别表示 0 或 len(slice)。所以 slice[n:]
的意思是“给我从 n
到末尾的切片部分。slice[len(slice)-n:]
将为您提供切片中最后的 n
条目。
https://play.golang.org/p/4JRcRH-wc3
func (s *Statistics) AddImport(i Import) {
// How can I optimize this method so that
// only the last two entries are kept?
s.Imports = append(s.Imports, i)
if len(s.Imports) > numberToKeep {
s.Imports = s.Imports[len(s.Imports)-numberToKeep:]
}
}
请注意,这不会从内存中删除切片的开头(或允许其被垃圾收集),但当您继续添加条目时,运行时将自动分配更大的底层数组并复制内容,从而将早期的数组分配释放到被垃圾收集。目前,它以 2 为基础执行此操作(因此每次切片大小加倍时都会执行此操作),尽管这没有记录,因此不能保证保持这种方式。如果内存管理对您很重要,您可以使用内置
copy(dest, source)
函数手动将切片复制到新的后备数组。
高级选项包括使用队列系统(通常使用链表格式实现)或循环切片(其中条目从末尾“环绕”到数组开头未使用的索引,这是另一种常见的队列设计)。