mapの概要
| キーと値のペアをまとめて管理するGoの予約語 | ||
|
map 概要 |
||
|
mapの考え方をイメージで理解

基本的なmapの使い方
以下のコードは、mapを使ってキーと値を関連付ける基本的な例です。
package main
import "fmt"
func main() {
// mapの定義
ages := map[string]int{
"太郎": 25,
"花子": 30,
"次郎": 20,
}
fmt.Println("太郎の年齢:", ages["太郎"])
}
解説:
map[string]intは、キーがstring型で、値がint型のマップを定義します。ages["太郎"]で「太郎」の年齢(25)を取得します。- 要素を複数行に並べて記述する場合、最後の要素の末尾にも必ずカンマ(
,)を付与しなければならないのがGo言語の厳格な構文ルールです。
実行結果:
太郎の年齢: 25
要素の追加・削除
mapには新しい要素を追加したり、既存の要素を削除することができます。
package main
import "fmt"
func main() {
ages := make(map[string]int) // 空のmapを作成
// 要素の追加
ages["太郎"] = 25
ages["花子"] = 30
fmt.Println("追加後:", ages)
// 要素の削除
delete(ages, "太郎")
fmt.Println("削除後:", ages)
}
解説:
make(map[string]int)を使って空のマップを作成します。makeを経由せずにvar ages map[string]intとだけ宣言すると値はnilになり、その状態のまま要素を代入しようとするとプログラムがクラッシュするため、必ず初期化が必要です。ages["太郎"] = 25のようにして新しい要素を追加します。delete(ages, "太郎")で「太郎」のデータを削除します。
実行結果:
追加後: map[太郎:25 花子:30] 削除後: map[花子:30]
要素の存在確認(「カンマ ok」イディオム)
存在しないキーを指定してマップから値を取り出しようとした場合、Go言語はエラーを返さず、そのデータ型の「初期値(ゼロ値)」を返却します。そのため、キーがマップ内に存在するかどうかを確認するには、2つの戻り値を受け取ります。
package main
import "fmt"
func main() {
ages := map[string]int{"太郎": 25, "花子": 30}
age, exists := ages["次郎"]
if exists {
fmt.Println("次郎の年齢:", age)
} else {
fmt.Println("次郎のデータは存在しません")
}
}
解説:
age, exists := ages["次郎"]で、キー「次郎」が存在するかどうかを確認できます。- マップの取得式を多重代入の形式にすると、2番目の変数に「キーが存在していれば
true、なければfalse」のブール値が格納されます。これを利用しないと、「元々データとして年齢が 0 のデータ」と「登録自体がないデータ(取得結果は自動的に 0 になる)」の判別がつかなくなります。
実行結果:
次郎のデータは存在しません
マップのループ処理
for rangeを使うと、マップの全要素を走査できます。
package main
import "fmt"
func main() {
ages := map[string]int{"太郎": 25, "花子": 30, "次郎": 20}
for name, age := range ages {
fmt.Println(name, "の年齢:", age)
}
}
解説:
for name, age := range agesで、マップのキーと値を順番に取得できます。- マップの走査順序は保証されません。Goのランタイムは、ハッシュテーブルの内部構造に依存したプログラムを記述されるのを防ぐため、ループ開始時に
出力順序を故意にシャッフルする非決定的挙動を仕様として採用しています。
実行結果(順序はランダム):
太郎 の年齢: 25 花子 の年齢: 30 次郎 の年齢: 20
学術的・設計的注意点
- スレッド安全性(並行処理)の完全な欠如:
mapは単体ではマルチスレッド(並行処理)に対して安全ではありません。複数のゴルーチンが、適切な同期制御を行わずに同一のマップオブジェクトに対して同時に書き込み(あるいは一方が書き込み、もう一方が読み込み)を行うと、Goランタイムは即座に検知してfatal error: concurrent map writesと出力し、アプリケーションをその場で強制クラッシュさせます。これを回避するためには、sync.Mutexを用いてマップへのアクセスを直列化するか、あるいはsync.Mapパッケージの利用を検討する必要があります。 - メモリの解放特性(要素を削除してもメモリは縮小しない):
delete関数を使用してマップから大量の要素を削除したとしても、そのマップが確保したシステム上の内部メモリバケット(容量)は自動的には縮小(解放)されません。大量のデータを一時的にマップに詰め込んでからクリアするような処理を繰り返すと、メモリの高止まり現象を招きます。マップ全体のメモリを完全にクリーンアップしてOSに返却したい場合は、古いマップ変数を破棄して、再度makeを使って新しいマップを1から生成し直すアプローチが必要です。
よくある質問
- Q: マップのサイズを事前に設定できますか?
- A: はい、事前設定(初期容量の確保)は可能です。固定サイズにロックすることはできませんが、
make(map[string]int, 100)のように第2引数に数値を渡すことで、内部バケットの領域をあらかじめ拡張された状態で生成できます。これにより、要素追加時のメモリ再割り当てオーバーヘッドを完全に無くすことができるため、実務上のパフォーマンスチューニングとして多用されます。 - Q: マップの要素を安全に削除する方法は?
- A:
delete(map, key)を使えば安全に削除できます。指定したキーが最初からマップ内に存在しなかったとしても、ランタイムエラー等は発生せず何も処理されずにスルーされる安全な設計です。 - Q: マップの要素数を取得する方法は?
- A: 組み込みの
len(map)を使うと、現在のマップの要素数(格納されているペアの数)を取得できます。なお、スライスとは異なりcap(map)(キャパシティ判定)は存在せず、呼び出すとエラーになります。 - Q: マップのキーの型に使えないものは?
- A: スライスやマップ、関数などはキーとして使えません。Go言語の仕様では、キーの型は
比較演算子(== や !=)で評価可能な型でなければならないと厳格に定められています。構造体については、含まれるすべてのフィールドの型が比較演算子に対応している場合に限り、マップのキーとして正当に利用できます。
まとめ
mapはキーと値のペアを管理するハッシュテーブルベースの組み込みデータ構造。- 未初期化の
nilマップへの書き込みは即時クラッシュするため、make関数かリテラル構文による実体の初期化が必要。 - キーの存在を確認するには2つの戻り値(カンマ ok イディオム)を使う。
- マップのループ処理では
走査順序が毎回完全にランダム化されるため、順序に依存したアルゴリズムを組まない設計配慮が求められる。 - 並行アクセスに対する安全機構は内蔵されておらず、データ書き込みの衝突はランタイムパニックによって厳格に停止される。