1. Introduction
In Go,map
is a built-in data type that provides an efficient way to store and retrieve data.map
Is an unordered set of key-value pairs where each key is associated with a value. Use map data structures to quickly find the corresponding value based on keys without traversing the entire collection.
In Go,map
is a built-in data type that can be declared and initialized in the following ways:
m := make(map[keyType]valueType)
In usemap
When we usually use the basic data type as key. However, when we need to use a custom structure as a key, we need to consider whether the structure contains fields of reference type. Reference type refers to the type of address that stores data, such as pointers, slices, dictionaries, and channels. existGo
In , the reference type has dynamic characteristics and may be modified or point to new data. This raises the question: Can custom structures containing reference types be used asmap
What about the key?
2. The basic model of map
Find out if you can use a custom structure containing a reference type asmap
We need to understand the key issue firstmap
basic model. In Go,map
Implemented using a hash table. A hash table is a data structure that stores data in key-value pairs, which maps keys to hash values by using a hash function.
The hash function is an algorithm used to map keys to hash values. It accepts keys as input and generates a fixed-length hash. Go languagemap
The internal hash function is used to calculate the hash value of the key.
And differentkey
The hash value generated by the hash function may be the same, and a hash collision occurs. Hash conflict refers to the same hash value obtained after different keys are calculated by the hash function. Since the output space of the hash function is much smaller than the input space of the key, hash collisions are inevitable. It is impossible to judge thiskey
Is it an element that already exists in the current hash table or is it because of hash conflict that different keys map to the same bucket. You need to judge these twokey
Whether it is equal or not.
Therefore, inmap
In, asmap
In-housekey
, if it needs to be ensured that it supports comparison operations, two can be comparedkey
Whether it is equal or not.
3. Requirements for map key
From abovemap
In the basic model introduction, we learned thatmap
The key in it needs to support the calculation of hash function, and the key type must support comparison operations.
existmap
In, calculatekey
The hash value ofmap
In-housekey
There are no additional requirements.
existmap
In the process, determining whether two keys are equal is by calling the equality operator of the key type (==
or!=
) to complete it, sokey
Must be sure to support this type==
operate. This requirement ismap
The implementation mechanism determines.map
The equality of keys is used internally to determine the storage location of the key and the search value. If the key types are not comparable, equality comparison cannot be performed, resulting in the inability to accurately locate the key and retrieve the values.
In Go, basic data types (such as integers, floating point numbers, strings) and some built-in types are comparable, so they can be used directly asmap
key. However, when a custom structure is used as a key, it is necessary to ensure that all fields of the structure are of comparable types. If the structure contains a field of reference type, the structure cannot be used directly asmap
keys, because the reference type does not have simple equality comparison.
Therefore, ifmap
The key in the key is a custom type and contains a reference field. It will not be used asmap
The key will fail directly to compile. The code example is as follows:
type Person struct { Name string Age int address []Address } func main() { // It will be compiled directly here without passing m := make(map[Person]int) }
The second is that the custom structure contains a field of pointer type, which is supported at this time.==
Operation, but it is done using pointer addresshash
Computation and equality comparison, it is possible that we understand that it is the samekey
, in factmap
It doesn't seem to be true, it is very easy to cause errors at this time. The examples are as follows:
type Person struct { Name string Age int address *Address } func main(){ m := make(map[Person]int) p1 := Person{Name: "Alice", Age: 30, address: &Address{city: "beijing"}} p2 := Person{Name: "Alice", Age: 30, address: &Address{city: "beijing"}} m[p1] = 1 m[p2] = 2 // Output 1 (m[p1]) // Output 2 (m[p2]) }
Here we define aPerson
Structure containing a field of pointer typeaddress
. Two objects were createdp1
andp2
,In our understanding, it is the same object, in factmap
There are two unrelated objects in the process. The main reason is to use the address to perform hash calculation and equality comparison.
To sum up, if the custom structure contains a field of reference type (the pointer is a special reference type), it will not be used asmap
Type ofkey
。
4. Why not extract hashCode and equals method interfaces and implement them by the user
currentgo
middlemap
The calculation of hash value in the hash value provides the default hash function and does not need to be implemented by the user; secondlykey
Comparison of equality is through==
The operator is implemented, and the comparison function is not customized by the user. Then we have a question, why not extract the hashCode and equals method interfaces and implement them by the user?
4.1 Simplicity and performance angles
Equalities are comparedGo
Used in language==
Operators are implemented, and hash functions are the default implementation provided by the runtime library. I understand this design choice for several reasons:
-
Simplicity: For the default hash function function, it is built into the language and does not require additional implementation and configuration from the user. This simplifies the use of maps. For equality comparison operations,
==
Comparison of operators is an intuitive and simple way. In terms of grammar,==
Operators are used to compare whether two values are equal, and this syntax simplicity makes the code more readable and understandable. -
performance: The default hash function is optimized and tested to provide good performance in most cases. Next use
==
to achieve equality comparison, because==
Operators are native operations at the language level, and the compiler can optimize them to improve the execution efficiency of the code.
4.2 Key immutable limitations
map
The immutability of bonds is also a consideration. based on==
To determine whether the objects are equal, indirectly guaranteeing the immutability of the key. at present,==
Most types of comparisons have been supported, only the reference type fields in the custom structure cannot be used directly==
Make a comparison. If there is no reference type field in the key, this means that the value put in the Map key cannot change at runtime, thus ensuring the immutability of the key at runtime.
ifkey
There is no immutable limit, then it was stored inmap
There may be problems with key-value pairs in it. Because when placing elements,map
The hash value is calculated based on the current value of the key, and the hash value is used to find the corresponding storage location. If placedmap
The value of the key in the change has changed, and it is calculated at this timehash
The values may also change, which means the data is placed in the wrong place. Even if you use it latermap
If the same value of the key in the search key is used, the data may not be found.
Here is a simple code to illustrate the variable type askey
Problems that will be caused:
type Person struct { Name string Age int SliceField []string } func main() { person := Person{Name: "Alice", Age: 25, SliceField: []string{"A", "B"}} // Assuming Person can be used as a key, it is actually not supported personMap := make(map[Person]string) personMap[person] = "Value 1" // Modify the value of SliceField in person [0] = "X" // Try to find the value through the same person (personMap[person]) // Output an empty string, the corresponding value cannot be found}
If drawnequals
Method interface is implemented by the user, at this timekey
The immutability of the user needs to implement it, and secondlygo
The language also needs to add some detection mechanisms, which first increases the burden on users, which is not in line withgo
Philosophy of language design.
4.3 Summary
To sum up, based on considerations of simplicity, performance and semantic consistency and key immutability, Go language chooses to use==
Operators perform key comparisons, and hash functions as the default implementation of runtime library is more in line withgo
Philosophy of language design.
5. Summary
In Go language, map is an unordered set of key-value pairs that provide efficient data storage and retrieval mechanisms. When using map, the basic data type is usually used as the key. However, when we want to use a custom structure as a key, we need to consider whether the structure contains fields of reference type.
Custom structure asmap
The key needs to meet some requirements. First, the key types must be comparable, that is, they support the==
Operators perform equality comparison. existGo
In this case, basic data types and some built-in types meet this requirement. However, if the structure contains a field of reference type, the structure cannot be directly used asmap
keys, because the reference type does not have simple equality comparison.
Therefore, in general, a custom structure containing a reference type field cannot be used asmap
ofkey
of.
The above is a detailed explanation of whether custom structures in Go can be used as map keys. For more information about Go maps, please follow my other related articles!