令和最初のエントリです。
連休前に教えてもらったことについてまとめました。
このエントリに記載する内容については、github の SliceTricks · golang/go Wiki · GitHub に記載されてる内容になります。
go の sliceについて
go の slice はポインタ型です。また、go で slice を初期化する際には、通常はメモリアロケーションが走ります。
// sample a := []int{1, 2, 3} b := make([]string, 0, 0)
go の slice については Go Slices: usage and internals - The Go Blog この辺が詳しいです。
slice をメモリアロケーションなしでフィルタする
ある slice から特定の値を持ってるものだけを抜き出し、別の slice にコピーするようなユースケースを考えてみます。
素直にコードを書くと以下のようになると思います。
func main() { arr := []string{"apple", "banana", "orange"} arr2 := make([]string, 0, 0) for _, v := range arr { v := v if v == "apple" { arr2 = append(arr2, v) } } fmt.Printf("arr:%v, address: %p\n", arr, arr) fmt.Printf("arr2:%v, address: %p\n", arr2, arr2) } // 出k力結果 // arr:[apple banana orange], address: 0x43e260 // arr2:[apple], address: 0x40c128
通常、こういったある slice の中から特定の値を取り出した slice を作り直したい(slice の値を詰め直す)ときは、別に詰め直す用の slice をインスタンス化しておいて、loopで回して詰め直すのが一般的な方法だと思います。
しかし、この方法だと、ソースとなる slice と詰め直す先の slice で二重にメモリアロケーションが必要になります。
※ 出力結果でもアドレスの値は異なります。
しかし、現実世界でプロダクトを運用しているとメモリが厳しく、slice 一つとっても極力メモリ空間を使いまわして、メモリアロケーションを走らせることなく slice をフィルタリングしたいユースケースってあると思います。ソースの slice で使用されてるメモリをうまいこと再利用して slice を詰め直す方法が SliceTricks · golang/go Wiki · GitHub に書いてありました。
この gowiki に記載されてる Tips を元に実際のサンプルコードを書き直してみます。
func main() { arr := []string{"apple", "banana", "orange"} arr2 := arr[:0] // ここが異なる。 for _, v := range arr { v := v if v == "apple" { arr2 = append(arr2, v) } } fmt.Printf("arr:%v, address: %p\n", arr, arr) fmt.Printf("arr2:%v, address: %p\n", arr2, arr2) } // 出力内容 // arr:[apple banana orange], address: 0x43e260 // arr2:[apple], address: 0x43e260
ref: https://play.golang.org/p/D9ubw_22WTw
出力を確認すると、詰め直した先の slice もソースとなった slice のアドレスと同じなので、うまくメモリを再利用できています。
注意点も gowiki には記載してあります。
This trick uses the fact that a slice shares the same backing array and capacity as the original, so the storage is reused for the filtered slice. Of course, the > original contents are modified.
以下のような slice の各要素の値を変更する場合、同じアドレスで詰め替えを行うので元のソースの slice も書き換えてしまいます。
func main() { a := []string{"a", "b", "c"} b := a[:0] fmt.Printf("a: %[1]p = %+[1]v\n", a) for _, aa := range a { aa := aa b = append(b, fmt.Sprintf("1-%s", aa)) } fmt.Printf("b: %[1]p = %+[1]v\n", b) fmt.Printf("a: %[1]p = %+[1]v\n", a) } // 出力結果 // a: 0x43e260 = [a b c] // b: 0x43e260 = [1-a 1-b 1-c] // a: 0x43e260 = [1-a 1-b 1-c]