データ構造
Go 言語は構文が簡潔ですが、強力なコアデータ構造が多数組み込まれています。これらのデータ構造は Go プログラムの基本的な構築ブロックであり、その実装原理を深く理解することで、より効率的なコードを書くことができます。本記事では Go で最も一般的に使用されるいくつかのデータ構造とその底层実装について紹介します。
基本データ構造
slice スライス
スライスは Go 言語で最も一般的に使用されるデータ構造で、配列の抽象化とカプセル化です。主な特徴:
- 動的サイズ:スライスの長さは動的に拡張可能
- 参照セマンティクス:スライスは渡す際に構造体のみコピーされ、底层配列はコピーされない
- 拡張メカニズム:容量が不足すると自動的に拡張される
スライスは実行時に slice 構造体で表され、底层配列へのポインタ、長さ、容量の 3 つのフィールドを含む。
string 文字列
文字列は Go の最も基本的なデータ型の一つで、主な特徴:
- 不変性:文字列は作成後は変更できない
- UTF-8 エンコーディング:通常 UTF-8 エンコードされたテキストを表す
- ゼロ値安全:文字列のゼロ値は空文字列
""で、nilではない
文字列は実行時に stringStruct 構造体で表され、バイト配列へのポインタと長さを含む。
コンテナデータ構造
map マップ
map は Go 言語に組み込まれたキーバリューコンテナで、ハッシュテーブルとして実装されています。主な特徴:
- ハッシュテーブル実装:ハッシュ関数で要素を素早く特定
- 自動拡張:負荷因子が高くなると自動的に拡張
- 非同期安全:同時読み書きには追加の同期機構が必要
map は実行時に hmap 構造体で表され、バケット配列、ハッシュシード、要素カウントなどのフィールドを含む。
syncmap 並行マップ
sync.Map は標準ライブラリが提供する並行安全なマップで、読みが多く書きが少ないシーンに適しています。主な特徴:
- 読み書き分離:read と dirty の 2 つの map で読み書きを実現
- アトミック操作:read map はアトミック操作を使用し、ロック不要
- 段階的移行:miss カウントが閾値に達すると dirty を read に昇格
sync.Map は空間を時間で置き換える方式により、特定のシーンで map + mutex よりも優れたパフォーマンスを提供します。
並行データ構造
channel チャネル
channel は Go 言語の CSP 思想を体現する代表的なもので、ゴルーチン間の通信に使用されます。主な特徴:
- ゴルーチン通信:channel を通じてゴルーチン間のデータ転送を実現
- 同期機構:バッファなし channel はゴルーチン同期に使用可能
- ロック付きキュー:底层はロック付きの環状キュー
channel は実行時に hchan 構造体で表され、環状バッファ、待機キューなどのフィールドを含む。
select 多重化
select は複数の channel の状態を同時に監視し、多重化を実現します。主な特徴:
- ノンブロッキング:複数の channel の可用性をノンブロッキングでチェック
- ランダム選択:複数の channel が同時に使用可能な場合、ランダムに 1 つを選択して実行
- タイムアウト制御:
time.Afterと組み合わせてタイムアウト機構を実装
select は実行時に scase 構造体で各ブランチを表し、ポーリング機構で channel の状態をチェックします。
学習のアドバイス
以下の順序で学習することをお勧めします:
- まず slice スライス と string 文字列 を学び、基本データ構造を理解
- 次に map マップ を学び、ハッシュテーブルの実装を理解
- その後 channel チャネル を学び、ゴルーチン通信機構を理解
- 次に select 多重化 を学び、多重化テクニックを習得
- 最後に syncmap 並行マップ を学び、並行安全の実装方法を理解
