This is a creation in Article, where the information may have evolved or changed.
Map
The Golang map is implemented using a hash table. We know that the hash mapping must solve a problem: how to effectively avoid the hash collision. There are some ways to avoid hash collisions: Open address method, link address method, and create a public overflow area. The hash table uses a chain address to solve the collision problem.
Let's look at the underlying data structure defined by Golang for map:
type hmap struct { count int flags uint8 B uint8 noverflow uint16 hash0 uint32 buckets unsafe.Pointer oldbuckets unsafe.Pointer nevacuate uintptr overflow *[2]*[]*bmap}
The size of the hash table is determined by the B field, and the size is: 2^B
. count
is provided to len()
the use. There is one and one in the struct that buckets
oldbuckets
is used to achieve incremental expansion. The normal use of the case oldbuckets
is empty, only when the expansion of the time is not empty. When you expand, oldbuckets
point to buckets
the address that you pointed to earlier, and then buckets
point to the address after the re-expansion, which is twice times the size of the last time.
struct Bucket{ uint8 tophash[BUCKETSIZE]; // hash值的高8位....低位从bucket的array定位到bucket Bucket *overflow; // 溢出桶链表,如果有 byte data[1]; // BUCKETSIZE keys followed by BUCKETSIZE values};
Here is a map of maps that is generally drawn:
Map.png
The key of the hash (key) low 8 bits as the bucket position index information, hit the corresponding subscript, the use of high 8 bits in the first bucket inside the inside tophash
match key is within the Bucke, if not in the next bucket inside the search, If the value is removed, return it. As for the Hashtable expansion problem, the Golang source package gives a table of expansion parameters:
LOAD %overflow bytes/entry hitprobe missprobe4.00 2.13 20.77 3.00 4.004.50 4.05 17.30 3.25 4.505.00 6.85 14.77 3.50 5.005.50 10.55 12.94 3.75 5.506.00 15.27 11.67 4.00 6.006.50 20.90 10.79 4.25 6.507.00 27.14 10.15 4.50 7.007.50 34.03 9.73 4.75 7.508.00 41.10 9.40 5.00 8.00
Slice
The same different slices of the underlying array are references to this data, so changing the underlying data of the slice will follow the change, and if you don't want the underlying data to change, you can copy the tiles out and then modify the array after copy (remember).
The structure of the Golang source package in Slice is as follows:
struct Slice{ byte* array; uintgo len; uintgo cap;};
The most common operations that involve slice are: append. In this process will be related to slice expansion, the principle of expansion is:
1. 如果新的大小是当前大小2倍以上,则大小增长为新大小2. 否则循环以下操作:如果当前大小小于1024,按每次2倍增长,否则每次按当前大小1/4增长。直到增长的大小超过或等于新大小。
Slice also has a common problem: the difference between new and make. We all know that initializing or instantiating a slice variable, you can use new or make, and when to use new, when do you use make?
New (T) returns a *t that returns a pointer that can be implicitly de-referenced. and make (t, args) returns a normal T. In general, there are implicit pointers inside T. In a word, new returns a pointer to the cleared 0 memory, and make returns a complex structure.
Channel
Channel is a top citizen in the go language, everything can be loaded, parameters and return values and other types of data. The go language has a classic quote: "To allow communication to share memory instead of shared memory to communicate", let's look at the structure of the channel:
struct Hchan{ uintgo qcount; // 队列q中的总数据数量 uintgo dataqsiz; // 环形队列q的数据大小 uint16 elemsize; bool closed; uint8 elemalign; Alg* elemalg; uintgo sendx; // 发送index uintgo recvx; // 接收index WaitQ recvq; // 因recv而阻塞的等待队列 WaitQ sendq; // 因send而阻塞的等待队列 Lock;};
Where Recvq is a goroutine queue that is blocked by reads, SENDQ is a goroutine queue that is blocked by writing. The head of the queue is:
struct WaitQ{ SudoG* first; SudoG* last;};
The structure of the member variables in the queue is:
struct SudoG{ G* g; // g and selgen constitute uint32 selgen; // a weak pointer to g SudoG* link; int64 releasetime; byte* elem; // data element};
When sending data, determine the type of channel, if there is a buffer, determine if the channel has space, and then from the waiting channel to obtain the receiver in the channel, if the receiver, the object is passed directly to the receiver, Then the receiver's go into the run G queue where p is located, the sending process is completed, if not taken to the receiver, then the sender enqueue to send the channel, the sender into the blocking state, the buffer channel needs to first determine whether the channel buffer has space, If the buffer space is full, the sender is enqueue to send the channel, the sender enters the blocking state if the buffer space is not full, then the element is copied to the buffer, then the sender will not enter the blocking state, and finally try to wake up a recipient in the waiting queue. (this summary is from: https://zhuanlan.zhihu.com/p/27295229)
Receive channel and send similar to first also determine the type of channel, and then if there is a buffer channel to determine whether there are elements in the buffer, then from the channel to obtain the recipient, if fetched, then directly from the receiver to obtain the element, and wake the sender, the reception process is complete, If the receiver is not fetched, blocking the current goroutine and waiting for the sender to wake up, if it is a buffered channel needs to determine whether there are elements in the buffer, buffering is empty, blocking the current goroutine and waiting for the sender to wake up, buffer if not empty, then take out the first element in the buffer , and then try to wake up a sender in the channel. (this summary is from: https://zhuanlan.zhihu.com/p/27295229)
Interface, select, ..., are the data structures that are worth analyzing, and then add the following ... end~