ホームページ > 記事 > システムチュートリアル > Go でスライスの容量と長さを操作するためのヒント
クイック テスト - 次のコードは何を出力しますか?
vals := make([]int, 5) for i := 0; i <p>[0 0 0 0 0 0 1 2 3 4] と推測した方は正解です。 </p> <p> テストで間違えても心配する必要はありません。これは Go 言語に移行するときによくある間違いです。この記事では、出力が期待したものと異なる理由と、Go のニュアンスを利用してコードをより効率的にする方法について説明します。 </p> <div style="font-size: 14pt; color: white; background-color: black; border-left: red 10px solid; padding-left: 14px; margin-bottom: 20px; margin-top: 20px;"><strong>スライスと配列</strong></div><p>Go には配列とスライスの両方があります。戸惑うかもしれませんが、慣れてしまえばきっと気に入ります。お願い、私を信じて。 </p> <p> スライスと配列の間には多くの違いがありますが、この記事で焦点を当てるのは、配列のサイズがその型の一部であるのに対し、スライスは動的サイズを持つことができるということです。スライスは、スライスのラッパーであるためです。配列。 </p> <p>これは実際には何を意味するのでしょうか?したがって、配列 val a[10]int があるとします。配列のサイズは固定されており、変更できません。 len(a) を呼び出すと、このサイズは型の一部であるため、常に 10 が返されます。したがって、配列内に突然 10 個を超える項目が必要になった場合は、まったく異なるタイプの新しいオブジェクト (たとえば val b[11]int) を作成し、すべての値を a から b にコピーする必要があります。 </p> <p> コレクション サイズの配列が価値のある特定の状況がありますが、一般的に言えば、これは開発者が望んでいることではありません。代わりに、彼らは Go の配列に似た、時間の経過とともに拡張できる機能を使用したいと考えていました。大雑把な方法は、必要以上に大きな配列を作成し、その配列のサブセットを配列として扱うことです。以下のコードは例です。 </p> <pre class="brush:php;toolbar:false">var vals [20]int for i := 0; i <p>コードでは長さ 20 の配列がありますが、サブセットのみを使用しているため、コードでは配列の長さが 5 であり、新しい項目を追加した後は 6 になると想定できます。配列 。 </p> <p>これは (非常に大まかに) スライスの仕組みです。これらには、前の例のサイズ 20 の配列のように、サイズが設定された配列が含まれています。 </p> <p>また、プログラムで使用される配列のサブセットも追跡します。これは append 属性で、前の例の subsetLen 変数に似ています。 </p> <p>最後に、スライスには容量もあります。これは、前の例の配列の全長 (20) に似ています。これは、スライス配列に収まらなくなる前にサブセットのサイズが増加できることがわかるため、便利です。これが発生した場合、新しい配列を割り当てる必要がありますが、このロジックはすべて append 関数の背後に隠されています。 </p> <p>つまり、append 関数を使用してスライスを結合すると、非常に配列に似た型が得られますが、時間が経つにつれて、より多くの要素を処理できるようになります。 </p> <p> 前の例をもう一度見てみましょう。今回は配列の代わりにスライスを使用します。 </p> <pre class="brush:php;toolbar:false">var vals []int for i := 0; i <p> 配列と同じようにスライス内の要素にアクセスできますが、スライスと append 関数を使用することで、その背後にある配列のサイズを考慮する必要がなくなりました。 len 関数と cap 関数を使用することでこの問題を解決できますが、それほど心配する必要はありません。シンプルですよね? </p><div style="font-size: 14pt; color: white; background-color: black; border-left: red 10px solid; padding-left: 14px; margin-bottom: 20px; margin-top: 20px;"><strong>テストに戻る</strong></div> <p>これを念頭に置いて、前回のテストを見直し、何が問題だったのかを見てみましょう。 </p> <pre class="brush:php;toolbar:false">vals := make([]int, 5) for i := 0; i <p>make を呼び出すとき、最大 3 つのパラメーターを渡すことができます。 1 つ目は割り当てたタイプ、2 つ目はタイプの「長さ」、3 つ目はタイプの「容量」です (このパラメータはオプションです)。 </p> <p>引数 make([]int, 5) を渡すことによって、長さ 5 のスライスを作成することをプログラムに伝えます。この場合、デフォルトの容量は長さと同じです (この場合は 5)。 </p> <p>これは希望どおりに見えるかもしれませんが、ここでの重要な違いは、新しい要素を追加した後の最初の要素に 5 つの要素が必要であると仮定して、スライスに「長さ」と「容量」を 5 に設定するように指示していることです。次に、append 関数を呼び出します。これにより、容量のサイズが増加し、スライスの最後に新しい要素が追加されます。 </p> <p>Println() ステートメントをコードに追加すると、容量の変化が確認できます。 </p> <pre class="brush:php;toolbar:false">vals := make([]int, 5) fmt.Println("容量は次のとおりです:", cap(vals)) for i := 0; i <p>最終的には、目的の [0 1 2 3 4] ではなく、[0 0 0 0 0 0 1 2 3 4] という出力が得られます。 </p> <p>それを修正するにはどうすればよいですか?これを行うにはいくつかの方法があります。ここでは 2 つを説明します。シナリオに最も役立つ方法を選択してください。 </p> <div style="font-size: 14pt; color: white; background-color: black; border-left: red 10px solid; padding-left: 14px; margin-bottom: 20px; margin-top: 20px;"><strong>追加ではなくインデックス書き込みを直接使用します</strong></div> <p>最初の修正は、make 呼び出しを変更せずに残し、インデックスを使用して各要素を明示的に設定することです。このようにして、次のコードが得られます: </p> <pre class="brush:php;toolbar:false">vals := make([]int, 5) for i := 0; i <p> この場合、使用したいインデックスとまったく同じ値を値に設定しますが、インデックスを個別に追跡することもできます。 </p> <p>たとえば、マップのキーを取得したい場合は、次のコードを使用できます。 </p> <pre class="brush:php;toolbar:false">パッケージメイン 「fmt」をインポートします 関数 main() { fmt.Println(keys(map[string]struct{}{ "犬": 構造体{}{}、 "猫": 構造体{}{}、 })) } func キー(m マップ[文字列]構造{}) []文字列 { ret := make([]string, len(m)) 私:= 0 for key := range m { ret[i] = キー 私 } リターンレット }
これは良いことです。返されるスライスの長さがマップの長さと同じであることがわかっているので、スライスをその長さで初期化し、各要素を適切なインデックスに割り当てることができます。このアプローチの欠点は、各インデックスに設定する値を知るために i を追跡しなければならないことです。
これで 2 番目の方法がわかります...
追加する値のインデックスを追跡する代わりに、make 呼び出しを更新し、スライス タイプの後に 2 つの引数を指定できます。まず、スライスにまだ新しい要素を追加していないため、新しいスライスの長さは 0 に設定されます。次に、スライスには最終的に多くの文字列が追加されることがわかっているため、新しいスライスの容量はマップ パラメーターの長さに設定されます。
これでも、前の例と同じ配列をバックグラウンドで構築しますが、スライスの長さが 0 であるため、append を呼び出すと、配列がスライスの先頭に配置されます。
パッケージメイン 「fmt」をインポートします 関数 main() { fmt.Println(keys(map[string]struct{}{ "犬": 構造体{}{}、 "猫": 構造体{}{}、 })) } func キー(m マップ[文字列]構造{}) []文字列 { ret := make([]string, 0, len(m)) for key := range m { ret = append(ret, key) } リターンレット }
次に、「追加機能でスライスの容量を増やすことができるのであれば、なぜプログラムに容量を指示する必要があるのでしょうか?」と疑問に思うかもしれません。
実のところ、ほとんどの場合、これについてはあまり心配する必要はありません。コードがより複雑になる場合は、var vals []int でスライスを初期化し、残りを append 関数に処理させます。しかし、この状況は異なります。これは容量を宣言することの難しさを示す例ではありません。実際、提供されたマップに直接マッピングされることがわかっているため、スライスの最終容量を決定するのは簡単です。したがって、初期化するときにスライスの容量を宣言し、プログラムが不必要なメモリ割り当てを実行するのを防ぐことができます。
追加のメモリ割り当てを確認したい場合は、Go Playground で次のコードを実行します。容量が増加するたびに、プログラムはメモリを割り当てる必要があります。
パッケージメイン 「fmt」をインポートします 関数 main() { fmt.Println(keys(map[string]struct{}{ "犬": 構造体{}{}、 "猫": 構造体{}{}、 "マウス": 構造体{}{}、 「オオカミ」: 構造体{}{}、 「ワニ」: 構造体{}{}、 })) } func キー(m マップ[文字列]構造{}) []文字列 { var ret[]文字列 fmt.Println(cap(ret)) for key := range m { ret = append(ret, key) fmt.Println(cap(ret)) } リターンレット }
次に、これを同じコードと、事前に定義された容量を使用したコードと比較します。
パッケージメイン 「fmt」をインポートします 関数 main() { fmt.Println(keys(map[string]struct{}{ "犬": 構造体{}{}、 "猫": 構造体{}{}、 "マウス": 構造体{}{}、 「オオカミ」: 構造体{}{}、 「ワニ」: 構造体{}{}、 })) } func キー(m マップ[文字列]構造{}) []文字列 { ret := make([]string, 0, len(m)) fmt.Println(cap(ret)) for key := range m { ret = append(ret, key) fmt.Println(cap(ret)) } リターンレット }
最初のコード例では、容量は 0 から始まり、1、2、4、そして最後に 8 と増加しました。つまり、配列を 5 回割り当てる必要があり、最後の割り当てにはスライスした配列が保持されていました。は 8 で、最終的に必要な値よりも大きくなります。
一方、2 番目の例は同じ容量 (5) で開始および終了し、keys() 関数の先頭で 1 回割り当てるだけで済みます。また、余分なメモリの無駄を避け、この配列に適合する完璧なサイズのスライスを返します。
これはプログラムのパフォーマンスの向上に役立つだけでなく、入力のサイズと出力のサイズの関係を明示的に示すことでコードを明確にするのにも役立ちます。
以上がGo でスライスの容量と長さを操作するためのヒントの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。