C プログラマである Gopher にとって、マップ タイプは、スライスやインターフェイスと同様、Go 言語の進歩を実感できる重要な文法要素です。マップ型は、Go 言語で最も一般的に使用されるデータ型の 1 つでもあります。
go ではマップはどのように動作しますか?
Go 言語に関する中国語のチュートリアルや翻訳の中には、マップを辞書またはハッシュ テーブルとして参照しているものもありますが、ここでは翻訳せずにマップを直接使用することにしました。Map は Go 言語によって提供される抽象データ型で、順序のないキーと値のペア (キーと値、ここではキーと値をそれぞれ直接表すためにキーと値を使用します) のセットを表します。
マップ タイプは「利用可能なゼロ値」をサポートしていません。初期値が明示的に割り当てられていないマップ タイプ変数のゼロ値は nil です。値がゼロのマップ変数を操作すると、実行時パニックが発生します。
var m map[string]int // m = nil
m["key"] = 1 // panic: assignment to entry in nil map
簡単に言えば、無視することはできず、単にキーに値を割り当てることはできません。
マップ タイプ変数を使用する前に、明示的に初期化する必要があります。
スライスと同様に、マップ タイプ変数を作成するには 2 つの方法があります。
- 1 つは複合リテラルを使用することです。
1)使用复合字面值创建map类型变量
// $GOROOT/src/net/status.go
var statusText = map[int]string{
StatusOK: "OK",
StatusCreated: "Created",
StatusAccepted: "Accepted",
...
}
- 1 つは、事前に宣言された組み込み関数 make を使用する方法です。
2)使用make创建map类型变量
// $GOROOT/src/net/client.go
icookies = make(map[string][]*Cookie)
// $GOROOT/src/net/h2_bundle.go
http2commonLowerHeader = make(map[string]string, len(common))
スライスと同様に、マップも参照型です。マップ型変数を関数パラメータとして渡しても大きなパフォーマンスの低下は発生せず、次の例のように、関数内のマップ変数への変更は関数の外部にも表示されます。
func foo(m map[string]int) {
m["key1"] = 11
m["key2"] = 12
}
func main() {
m := map[string]int{
"key1": 1,
"key2": 2,
}
fmt.Println(m) // map[key1:1 key2:2]
foo(m)
fmt.Println(m) // map[key1:11 key2:12]
}
マップの基本操作
1. データを挿入する
nil 以外のマップ型変数に直面すると、マップ型定義に準拠する任意のキーと値のペアをその変数に挿入できます。
Go ランタイムはマップ内のメモリ管理を担当するため、システム メモリが使い果たされない限り、マップに挿入されるデータの量について心配する必要はありません。
m := make(map[K]V)
m[k1] = v1
m[k2] = v2
m[k3] = v3
キーがマップにすでに存在する場合、挿入操作により古い値が新しい値で上書きされます。
m := map[string]int {
"key1" : 1,
"key2" : 2,
}
m["key1"] = 11 // 11会覆盖掉旧值1
m["key3"] = 3 // map[key1:11 key2:2 key3:3]
2.データ数を取得する
スライスと同様に、map は組み込み関数 len を通じて現在保存されているデータの数を取得することもできます。
m := map[string]int {
"key1" : 1,
"key2" : 2,
}
fmt.Println(len(m)) // 2
m["key3"] = 3
fmt.Println(len(m)) // 3
3.検索とデータ読み込み
マップ タイプは、検索やデータ読み取りの状況でよく使用されます。いわゆる検索とは、特定のキーが特定のマップに存在するかどうかを判断することです
。できること使用“comma ok”惯用法来进行查找
:
_, ok := m["key"]
if !ok {
// "key"不在map中
}
ここでは、特定のキーに対応する値は気にせず、特定のキーがマップ内にあるかどうかだけを気にします。そのため、空の識別子を使用して、
返される可能性のあるデータ値を無視し、ok の値が正しいかどうかだけを気にします。は
true (マップで表現)。
キーに対応する値を読み取りたい場合は、次のコードを記述します。
m := map[string]int
m["key1"] = 1
m["key2"] = 2
v := m["key1"]
fmt.Println(v) // 1
v = m["key3"]
fmt.Println(v) // 0
キーがマップ内に存在する場合 (「key1」など)、上記のコードは問題ありません。しかし、キーが
マップ内に存在しない場合 (「key3」など)、v には依然として「正当な」値 0 (
値型 int のゼロ値) が割り当てられていることがわかります。この場合、この 0 が「key3」に対応する値なのか、
「key3」が存在しないために返されたゼロ値なのかは判断できません。このためには、「comma ok」というイディオムも利用する必要があります。
m := map[string]int
v, ok := m["key"]
if !ok {
// "key"不在map中
}
fmt.Println(v)
キーがマップ内に存在するかどうかを判断するには、ok の値を使用する必要があります。ok = true の場合にのみ、取得された値が
必要なものとなります。要約すると、Go 言語のベスト プラクティスは、常に「comma ok」イディオムを使用してマップ内の値を読み取ることです。
4.データの削除
組み込み関数 delete を使用してマップからデータを削除します。
m := map[string]int {
"key1" : 1,
"key2" : 2,
}
fmt.Println(m) // map[key1:1 key2:2]
delete(m, "key2")
fmt.Println(m) // map[key1:1]
なお、削除対象のデータがマップ上に存在しない場合でも削除してもパニックにはなりません。
5. データを走査する
スライスの場合と同じように、for range ステートメントを通じてマップ内のデータをトラバースできます。
func main() {
m := map[int]int{
1: 11,
2: 12,
3: 13,
}
fmt.Printf("{ ")
for k, v := range m {
fmt.Printf("[%d, %d] ", k, v)
}
fmt.Printf("}\n")
}
同じマップを複数回走査すると、走査される要素の順序が同じではないことがわかります。これは、Go ランタイムが
マップ イテレーターを初期化するときに開始位置をランダム化するためです。したがって、マップを走査することによって得られる要素の順序に決して依存しないでください。
安定した走査順序が必要な場合、より一般的なアプローチは、スライスなど、別のデータ構造を使用して
必要な順序でキーを保存することです。
import "fmt"
func doIteration(sl []int, m map[int]int) {
fmt.Printf("{ ")
for _, k := range sl {
// 按切片中的元素次序迭代
v, ok := m[k]
if !ok {
continue
}
fmt.Printf("[%d, %d] ", k, v)
}
fmt.Printf("}\n")
}
func main() {
var sl []int
m := map[int]int{
1: 11,
2: 12,
3: 13,
}
for k, _ := range m {
sl = append(sl, k) // 将元素按初始次序保存在切片中
}
for i := 0; i < 3; i++ {
doIteration(sl, m)
}
}
$go run map_stable_iterate.go
{
[1, 11] [2, 12] [3, 13] }
{
[1, 11] [2, 12] [3, 13] }
{
[1, 11] [2, 12] [3, 13] }