SoFunction
Updated on 2025-03-06

Detailed explanation of the use of Go dictionary

Like many programming languages, in Go, a dictionary is a collection of key-value pairs (called key-element pairs in Go).

Storage/Find Principle

When we want to store or find a key-element pair, the hash table will first use the hash function to convert the key value into a hash value, which is generally an unsigned integer.

A hash table will contain a certain number of hash buckets. In the dictionary structure, there is an attribute B, which represents the number of buckets in the current dictionary (2^B).

	// A header for a Go map.
	type hmap struct {
	   // Note: the format of the hmap is also encoded in cmd/compile/internal/gc/.
	   // Make sure this stays in sync with the compiler's definition.
	   count     int // # live cells == size of map.  Must be first (used by len() builtin)
	   flags     uint8
	   B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
	   noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
	   hash0     uint32 // hash seed
	   buckets     // array of 2^B Buckets. may be nil if count==0.
	   oldbuckets  // previous bucket array of half the size, non-nil only when growing
	   nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)
	   extra *mapextra // optional fields
	}

For example, when B is 5, you can determine which bucket the current key-element should be stored in by obtaining the lower 5 digits of the hash value. For example, through the hash function, we get the hash value of a key-element pair:

1001011100001111011011001000111100101010001001011001010101011011

Among them, the lower 5 bits represent the position of the bucket to which it belongs, and 11011 is converted to decimal to 26, that is, the key-element pair exists in the 26th bucket. The hash bucket stores a set of "key hash value-internal structure" pairs, that is, it is stored in the way of overflowing pointers by key 1 key 2 … key 8 element 1 element 2 … element 8. It is a continuous piece of memory, and the key and element are stored in bundled. After we find the hash bucket, we can compare the key values ​​to locate the position of the key we need. Because the key-element pair is stored in bundled, finding the key is equivalent to finding the corresponding element value.

The same is true when storing, but it should be noted that each bucket can only store up to 8 key-element pairs. When there are more than 8, an overflow bucket will be generated, and the overflow pointer of the current hash bucket (the last block of the above consecutive memory) will point to the newly generated overflow bucket.

limit

In fact, from the above, we can see that the dictionary type is actually a specific implementation of a hash table. The biggest difference between keys and elements is that the keys must be hashable, while elements can be of any type, so the key types in the dictionary are limited.

Dictionary Statement

// Declare the dictionary is nil. If it is not initialized, it will report an error if it is stored directly.var s0 map[string] int
// Declare the dictionary and initialize its1 := map[string]int{}    
// Use make statements2 := make(map[string] int)
(s0, s1, s2, s3)

-------result-------------------------
map[] map[] map[]

Note: When declaring a dictionary, the type of key cannot be a function, a dictionary, or a slice. Because according to the above process of finding dictionary key-element pairs, we can know that in the end, we need to determine the position of the key-element pair by comparing whether the key in the bucket is the same as the key to be queried, but these three types do not support judgment operations, so the key types do not support these three types, and the compiler will directly report an error.

However, there is a relatively special type: interface{}, interface{} supports judgment and other operations, so the compiler will not report an error. However, because the empty interface{} is equivalent to a universal type and can accept any type of value, the following code will occur:

var s4 = map[interface{}]int{
	"1":      1,
	[]int{2}: 2,
	3:        3,
}
(s4)

------result--------------
panic: runtime error: hash of unhashable type []int

When we run, panic panic occurs. We can still adjust this error when the program runs, but when the program runs, we add such key-value pairs and cause system exceptions. It is too late to modify it. Therefore, it is best not to use interface{} as the key type, and we should give priority to types that calculate hash values ​​faster as the key type of the dictionary.

Dictionary assignment

//initializations0 := map[string]int{}
(s0)
//Add key-values0["one"] = 1
s0["two"] = 2
(s0)
//Modify the value of the specified keys0["one"] = 11
s0["two"] = 22
(s0)
//Delete the element of the specified keydelete(s0, "one")
(s0)
//Get the key-value pair(len(s0))

------result-------------------
map[]
map[one:1 two:2]
map[one:11 two:22]
map[two:22]
1

Special type modification value

If the type of value is an array or structure, then the value member cannot be modified directly.

s0 := map[string]struct {
	x int
}{}
s0["one"] = struct{ x int }{1}
s0["two"] = struct{ x int }{2}
s0["one"].x = 1 //The compiler will directly report an error here

Method 1: Get all values ​​first, modify them and reassign them

s0 := map[string]struct {
	x int
}{}
s0["one"] = struct{ x int }{1}
s0["two"] = struct{ x int }{2}
s0["one"].x = 1 //The compiler will directly report an error here// Correct way to do its1 := s0["one"]
 = 111
s0["one"] = s1 
(s0)

-----result------------------
map[one:{111} two:{2}]

Method 2: Use pointer type

* The beginning indicates that it is the pointer type

& is the address symbol, that is, obtain the address of the corresponding program entity object

// Correct way two// The type of value is a pointer type, and the pointer points to the structures0 := map[string]*struct {
	x int
}{}
//Create a structure and add pointer to the dictionarys0["one"] = &struct{ x int }{1}
(*s0["one"])
s0["one"].x = 111
(*s0["one"])

-----result------------------
{1}
{111}

Dictionary traversal

s0 := map[string]int{}
s0["one"] = 1
s0["two"] = 2
//Receive key and valuefor k, vla := range s0 {
	("%s:%d\n", k, vla)
}
("------------------------------------------------------------------------------------------------------------------------------)
//Receive keys onlyfor k := range s0 {
	("%s:%d\n", k, s0[k])
}

-----result----------------
one:1
two:2
-----Divide line-----------------------------------------------------------------------------------------------------------------------
one:1
two:2

Summary of dictionary characteristics

  • The key type of the dictionary is limited, and hashing and judgment must be supported.
  • The dictionary is unordered, and the order of each traversal may be different.
  • If the value type is a structure or an array, then the value member cannot be directly operated on
  • The nil dictionary cannot be assigned, but it can be read, and it is an empty dictionary map[]
  • Dictionary is thread-insecure, and multiple threads operate on the same dictionary will cause an error
  • You can delete or add key-element pairs during iteration

This is the end of this article about the detailed explanation of the use of Go dictionary. For more relevant Go dictionary content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!