golangの日記

Go言語を中心にプログラミングについてのブログ

Go言語(golang) スライスと配列の使い方

golang.png



Go言語にはslice(スライス)とarray(配列)があります。
以下、スライスと配列それぞれの基本的な使い方や違いについて





目次



スライスの初期化

var s []int

var s []int = []int{10, 20, 30}

var s = []int{10, 20, 30}

s := []int{}

s := []int{10, 20, 30}

// 多重スライス
var s [][]int

s := [][]int{
    {10, 20, 30},
    {10, 20, 30},
}



make を使った初期化

make の引数は make([型/type], [長さ/len], [容量/cap])

長さ = len = length = レングス
容量 = cap = capacity = キャパシティ


  • 長さ が 0 、 容量 が 10 で初期化
s := make([]int, 0, 10)

fmt.Printf("%#v, len: %d, cap: %d\n", s, len(s), cap(s))
// []int{}, len: 0, cap: 10


  • 長さに 1 以上の数値を指定すると、その分がゼロ値(int型の場合は0)で初期化される
s := make([]int, 3, 10)

fmt.Printf("%#v, len: %d, cap: %d\n", s, len(s), cap(s))
// []int{0, 0, 0}, len: 3, cap: 10
package main

import "fmt"

func main() {
    // 以下は s := []int{0, 0, 0} で初期化したことと同じ
    s := make([]int, 3)

    fmt.Println(s, len(s), cap(s)) // [0 0 0] 3 3


    // 当然、値を追加すると末尾に追加される
    s = append(s, 100)
    fmt.Println(s, len(s), cap(s)) // [0 0 0 100] 4 6
}


  • 容量に長さより小さい数値を指定した場合はエラー
s := make([]int, 5, 3)
// error len larger than cap in make([]int)


  • 容量は省略可能でその場合は長さと同じになる
s = make([]int, 3)

fmt.Printf("%#v, len: %d, cap: %d\n", s, len(s), cap(s))
// []int{0, 0, 0}, len: 3, cap: 3


  • 容量(cap)

以下のように、長さは追加した要素分増えていくのに対して、容量はそれを超えたときに倍の容量が自動で確保される

package main

import "fmt"

func main() {
    s := make([]int, 0, 5)

    for i := 1; i >= 11; i++ {
        s = append(s, i)

        fmt.Printf("%2d, len: %2d, cap: %2d\n", i, len(s), cap(s))
        //     1, len:  1, cap:  5
        //     2, len:  2, cap:  5
        //     3, len:  3, cap:  5
        //     4, len:  4, cap:  5
        //     5, len:  5, cap:  5
        //     6, len:  6, cap: 10
        //     7, len:  7, cap: 10
        //     8, len:  8, cap: 10
        //     9, len:  9, cap: 10
        // 10, len: 10, cap: 10
        // 11, len: 11, cap: 20
    }
}



makeで長さと容量を指定するメリット

スライスに値を100個入れたベンチマーク

package main

import "testing"

const N = 100

// make を使わない
func BenchmarkA(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var a []int
        for j := 0; j < N; j++ {
            a = append(a, j)
        }
    }
}

// make を使って 0 を指定する
func BenchmarkB(b *testing.B) {
    for i := 0; i < b.N; i++ {
        a := make([]int, 0, 0)
        for j := 0; j < N; j++ {
            a = append(a, j)
        }
    }
}

// make を使って容量のみ指定する
func BenchmarkC(b *testing.B) {
    for i := 0; i < b.N; i++ {
        a := make([]int, 0, N)
        for j := 0; j < N; j++ {
            a = append(a, j)
        }
    }
}

// make を使って長さ、容量を指定する
func BenchmarkD(b *testing.B) {
    for i := 0; i < b.N; i++ {
        a := make([]int, N, N)
        for j := 0; j < N; j++ {
            a[j] = j
        }
    }
}


ベンチマーク結果

              実行回数   一回の実行時間  メモリサイズ  メモリ再割当て
BenchmarkA-2   1486426   795 ns/op    2040 B/op   8 allocs/op
BenchmarkB-2   1534741   790 ns/op    2040 B/op   8 allocs/op
BenchmarkC-2  17227060  70.1 ns/op       0 B/op   0 allocs/op
BenchmarkD-2  30811446  39.3 ns/op       0 B/op   0 allocs/op

make を使って長さと容量を指定した場合(D)が最も処理速度が速く、その次に make を使って容量のみ(C)。他は同じで A と B は遅い
予め数がわかっている場合は make を使って長さと容量を指定して初期化したほうがいい。



値の変更

package main

import "fmt"

func main() {
    s := []int{10, 20, 30}

    fmt.Println(s) // [10 20 30]

    s[0] = 100 // 0 番目の値 10 を 100 に変更

    // 1 番目の値 20 を 200 に変更
    // 2 番目の値 30 を 300 に変更
    s[1], s[2] = 200, 300

    fmt.Println(s) // [100 200 300]
}



値の取得

package main

import "fmt"

func main() {
    s := []string{"A", "B", "C", "D", "E"}

    fmt.Println(s[0]) // 0 番目の値 A を取得
    fmt.Println(s[1]) // 1 番目の値 B を取得
    fmt.Println(s[2]) // 2 番目の値 C を取得
    fmt.Println(s[3]) // 3 番目の値 D を取得
    fmt.Println(s[4]) // 4 番目の値 E を取得

    // 範囲を指定して取得する。[start:end]
    fmt.Println(s[1:4]) // 1 番目から 3 番目まで [B C D]
    fmt.Println(s[2:])  // 2 番目から 最後(4番目まで) [C D E]
    fmt.Println(s[:3])  // 0 番目から 2 番目まで [A B C]
    fmt.Println(s[:])   // 最初から最後まで [A B C D E]
    
    // end に start 位置より小さい数を指定した場合はエラー
    fmt.Println(s[2:1]) // invalid slice index: 2 > 1
}


  • 先頭の値を取り出して残りを元のスライスに入れる
package main

import "fmt"

func main() {
    s := []string{"A", "B", "C", "D", "E"}

    v, s := s[0], s[1:]

    fmt.Println(v, s) // A [B C D E]
}


  • 末尾の値を取り出して残りを元のスライスに入れる
package main

import "fmt"

func main() {
    s := []string{"A", "B", "C", "D", "E"}

    v, s := s[len(s)-1], s[:len(s)-1]

    fmt.Println(v, s) // E [A B C D]
}


  • 先頭、末尾以外の値を取り出して残りを元のスライスに入れる
package main

import "fmt"

func main() {
    s := []string{"A", "B", "C", "D", "E"}

    // 0 から数えて 2 番目 C を抜き出す
    v := s[2]
    // それ以外を元のスライスに入れる
    s = append(s[:2], s[3:]...)

    fmt.Println(v, s) // C [A B D E]
}



値の削除

package main

import "fmt"

func main() {
    s := []string{"A", "B", "C", "D", "E"}

    // 先頭を削除
    s = s[1:]

    fmt.Println(s) // [B C D E]


    // 末尾を削除
    s = s[:len(s)-1]

    fmt.Println(s) // [B C D]


    // 途中の値を削除
    s = append(s[:1], s[2:]...)

    fmt.Println(s) // [B D]
}

容量を指定して削除

  • 通常、削除するとキャパシティは、そのスライスの容量になる。以下の場合は5
s := []string{"A", "B", "C", "D", "E"}
s = s[:1]
fmt.Println(s, len(s), cap(s)) // [A] 1 5

三番目にキャパシティを指定できる

s := []string{"A", "B", "C", "D", "E"}

// 元の容量(ここでは5)より大きい数値を指定するとエラー
s = s[:3:3]

fmt.Println(s, len(s), cap(s)) // [A B C] 3 3


スライス自体の削除

  • nilを代入することで全て削除できる
s := []int{10, 20, 30}
s = nil
fmt.Println(s, len(s), cap(s)) // [] 0 0
  • 確保しているキャパシティはそのままで削除する
s := []int{10, 20, 30}
s = s[:0]
fmt.Println(s, len(s), cap(s)) // [] 0 3



値の追加

  • 一つまたは複数の値を末尾に追加する
package main

import "fmt"

func main() {
    s := make([]string, 0, 5)
    fmt.Println(s) // []

    // 値を追加
    s = append(s, "A")
    fmt.Println(s) // [A]

    // 複数の値を追加
    s = append(s, "B", "C")
    fmt.Println(s) // [A B C]

    // スライスで追加
    s = append(s, []string{"D", "E"}...)
    fmt.Println(s) // [A B C D E]
}


  • マップと違ってスライスは初期化しなくても追加できる

map の場合は初期化しないとエラーになるけどスライスは初期化しなくてもエラーにならない

var s []string
fmt.Println(s == nil) // true

s = append(s, "A")
fmt.Println(s) // [A]


  • 値を先頭に追加する
package main

import "fmt"

func main() {
    s := []string{"A", "B", "C", "D", "E"}

    s = append([]string{"Hello", "World"}, s...)

    fmt.Println(s) // [Hello World A B C D E]
}


  • 値を挿入する
package main

import "fmt"

func main() {
    s := []string{"A", "B", "C", "D", "E"}

    s = append(s[:2], append([]string{"Hello", "World"}, s[2:]...)...)

    fmt.Println(s) // [A B Hello World C D E]
}



スライスのコピー

  • 単純に別の変数に代入するだけだと、シャローコピーなので値のアドレスは同じ
s := []int{10, 20, 30}

// 別の変数に入れる
_s := s

// 当然、変数自体のアドレスは違う
fmt.Printf("%p, %p\n", &_s, &s)       // 0xc00000c080, 0xc00000c060

// 値のアドレスは同じなので、どちらかの値を変更するともう片方も変更される
fmt.Printf("%p, %p\n", &_s[0], &s[0]) // 0xc000014420, 0xc000014420

_s[0] = 1000
fmt.Println(s[0]) // 1000


  • copy を使ったディープコピー
s := []int{10, 20, 30}

_s := make([]int, len(s))

copy(_s, s)

fmt.Printf("%p, %p\n", &_s[0], &s[0]) // 0xc0000a0020, 0xc0000a0000

_s[0] = 1000
fmt.Println(s[0]) // 10
  • appendでもディープコピーできる
s := []int{10, 20, 30}
var _s []int
_s = append(_s, s...)

fmt.Printf("%p, %p\n", &_s[0], &s[0]) // 0xc000014440, 0xc000014420



配列 (スライスとは異なる)

  • 配列の初期化

[3]int のように長さを指定するか [...]int で初期化できる

var a [3]int
fmt.Println(a, len(a), cap(a)) // [0 0 0] 3 3

var a = [2]int{}
fmt.Println(a, len(a), cap(a)) // [0 0] 2 2

var a = [4]int{10}
fmt.Println(a, len(a), cap(a)) // [10 0 0 0] 4 4

a := [...]int{10, 20, 30}
fmt.Println(a, len(a), cap(a)) // [10 20 30] 3 3

a := [3]int{10, 20, 30}
fmt.Println(a, len(a), cap(a)) // [10 20 30] 3 3


  • 追加するとエラー

値の変更はできるけど配列は固定長なので追加するとエラーになる

a := [3]int{10, 20, 30}
a = append(a, 40) // エラー


  • 配列のアドレス
package main

import "fmt"

func main() {

    // スライス
    s := []int{10, 20, 30}
    _s := s

    fmt.Println(&s[0], &_s[0]) // 0xc000096000 0xc000096000

    _s[0] = 1000
    // スライスの場合はアドレスが同じなので値が変わる
    fmt.Println(s[0], _s[0]) // 1000 1000

    
    // 配列
    a := [3]int{10, 20, 30}
    _a := a

    fmt.Println(&a[0], &_a[0]) // 0xc000014420 0xc000014440

    _a[0] = 1000
    // 配列の場合はアドレスが違うので変更されない
    fmt.Println(a[0], _a[0])   // 10 1000
}


  • 配列のスライス化
package main

import (
    "fmt"
)

func ArraySet1000(a [3]int) {
    a[0] = 1000
}

func SliceSet1000(s []int) {
    s[0] = 1000
}

func main() {
    a := [3]int{10, 20, 30}
    ArraySet1000(a)
    fmt.Println(a[0]) // 10 // 値は変わらない
    
    
    s := []int{10, 20, 30}
    SliceSet1000(s)
    fmt.Println(s[0]) // 1000 // スライスだと値は変わる


    // 配列は渡せない。エラー
    SliceSet1000(a)

    // スライス化して渡す
    SliceSet1000(a[:])
    fmt.Println(a[0]) // 1000 // 値が変わる

    // スライス化すると当然appendできる
    s = a[:]
    s = append(s, 40)
    fmt.Println(s) // [1000 20 30 40]
}