LoginSignup
13
3

More than 3 years have passed since last update.

Golangのスライス:makeがよくわからなかった件

Last updated at Posted at 2019-11-14

Golangにはスライスってものがあるようです。
JavaでいうとArrayListに当たるのかな?
可変長配列らしいです。

初期化の方法としては、最初から値を入れるパターンと、入れないパターンとあると思うのですけど。

初期化
   /* 初期値ありの場合 */
   retArray := []string{"a", "b"}
   /* 初期値なしの場合 */
   retArray := make([]string, 0, 0)

問題は、この「make」の引数。
公式を探そうにも、どう探したらいいかがよくわからない、、、
っていうか、GolangのAPI見にくい。。。

ってわけで、自分でいろいろ検証してみました。

定義

参考にした元記事にはこんな感じに書いてました。

スライスは配列へのポインタ、長さ、キャパシティの3つの情報から構成されている。長さは要素数で、キャパシティはスライスの最初の要素から数えたときの参照先の配列の要素数。長さ、キャパシティはそれぞれlen、cap関数で取得することができる。
https://qiita.com/rock619/items/db44507d02814e490902

makeの定義?
   変数名 := make([]入れる型(Generics的な?), len(長さ), cap(キャパシティ))

、、、正直、ちんぷんかんぷんです。

結論

先に結論を述べてしまうと、こんな感じ。
スクリーンショット 2019-11-14 12.14.05.png

  • lenは初期で作成される要素の数、capは確保される容量の数。
  • len < cap になるような設定はダメ。(決められた容量を超えた要素は作れない)
  • 容量を超えたデータを追加すると、元の倍の容量が自動で確保される。 スクリーンショット 2019-11-14 13.13.30.png スクリーンショット 2019-11-14 13.15.09.png

検証

len、capのところに、いろんな値を入れてみました。
で、デバッグで確かめてみたので、ちょっと見にくいですが、左がデバッグ、右がコードですー。
※ついでに、appendした時も見てみた。

make([]string, 0, 0)

make後
中身はなんもない。
スクリーンショット 2019-11-14 11.29.58.png
append後
[0]に値が追加された。
スクリーンショット 2019-11-14 11.32.19.png

make([]string, 0, 1)

make後
中身はなんもない。
スクリーンショット 2019-11-14 11.35.52.png
append後
[0]に値が追加された?
スクリーンショット 2019-11-14 11.37.11.png

make([]string, 0, 3)

make後
中身はなんもない。
スクリーンショット 2019-11-14 11.38.56.png
append後
[0]に値が追加された?
スクリーンショット 2019-11-14 11.39.23.png

make([]string, 2, 0)

怒られた。
len < cap になるのはダメらしい。
スクリーンショット 2019-11-14 11.41.16.png

make([]string, 2, 2)

make後
既に箱が作られてる。
スクリーンショット 2019-11-14 11.42.55.png
append後
[2]に値が追加された。(次のインデックス?)
スクリーンショット 2019-11-14 11.43.11.png
 なんでcapが2増えたのか?については、直前のcapが「2」だったので、倍の「4」になったっぽい。

これはGo言語の仕様によるもので、容量オーバーしたら基本的に2倍(※追記参照)の値を確保します。
appendするたびに一々メモリ領域を確保してコピーする手順を踏むのは効率が悪いため
このような動作になっています。
https://qiita.com/Kashiwara/items/e621a4ad8ec00974f025

2019/11/15 追記 (c-yan様、ありがとうございます!) 

正確には1024未満の時は2倍、それ以上の時は1.25倍とのことです。
ソース見たら、確かに。。。ソースもちゃんと見て仕様確認しないとですね:sweat:
https://github.com/golang/go/blob/master/src/runtime/slice.go#L100
ただ、このソースの「func growslice」がどこから呼ばれてるのか、はまだ確認中です。。。
appendで呼ばれてそうな気もするんだけど、、、実装が見つからない。。。

slice.go(抜粋)
    newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {
        newcap = cap
    } else {
        if old.len < 1024 {
            newcap = doublecap
        } else {
            // Check 0 < newcap to detect overflow
            // and prevent an infinite loop.
            for 0 < newcap && newcap < cap {
                newcap += newcap / 4
            }
            // Set newcap to the requested cap when
            // the newcap calculation overflowed.
            if newcap <= 0 {
                newcap = cap
            }
        }
    }

make([]string, 2, 3)

検証のため、やってみた。
makeでは、要素2で作られた。
スクリーンショット 2019-11-14 12.02.11.png
1個の用を追加したけど、容量内なので、capはそのまま
スクリーンショット 2019-11-14 12.05.20.png
もう1個追加したことで、容量オーバーに。
で、元の容量の倍の「6」が容量に設定された。
スクリーンショット 2019-11-14 12.02.54.png

まとめ

使い方がなんとなくわかった。
capとかでメモリの管理もしているあたり、C言語がベース、って感じがする。
Javaらーにはまだまだ概念としてわかりにくい部分がきっとありそうだけど、
出会ったら検証していきますっ!

13
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
3