A 基本數(shù)據(jù)類(lèi)型
1 布爾類(lèi)型 bool
1)Go 對(duì)于值之間的比較有非常嚴(yán)格的限制,只有兩個(gè)類(lèi)型相同的值才可以進(jìn)行比較,如果值的類(lèi)型是接口(interface),它們也必須都實(shí)現(xiàn)了相同的接口。如果其中一個(gè)值是常量,那么另外一個(gè)值的類(lèi)型必須和該常量類(lèi)型相兼容的。
2)對(duì)于布爾值的好的命名能夠很好地提升代碼的可讀性,例如以 is 或者 Is 開(kāi)頭的isSorted 、 isFinished 、 isVisivle 。
3)在格式化輸出時(shí),你可以使用 %t 來(lái)表示你要輸出的值為布爾型。
4)布爾型的值只可以是常量 true 或者 false。
2 數(shù)字類(lèi)型
##有符號(hào)整數(shù)
int8(-128 -> 127)
int16(-32768 -> 32767)
int32(-2,147,483,648 -> 2,147,483,647)
int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)
int( 32 位操作系統(tǒng)上32 位,64 位操作系統(tǒng)64 位)
##無(wú)符號(hào)整數(shù)
uint8(0 -> 255)
uint16(0 -> 65,535)
uint32(0 -> 4,294,967,295)
uint64(0 -> 18,446,744,073,709,551,615)
uint ( 32 位操作系統(tǒng)上32 位,64 位操作系統(tǒng)64 位)
##浮點(diǎn)型
float32(+- 1e-45 -> +- 3.4 * 1e38)
float64(+- 5 1e-324 -> 107 1e308)
#無(wú)float類(lèi)型
#復(fù)數(shù)
Go 擁有以下復(fù)數(shù)類(lèi)型:
complex64 (32 位實(shí)數(shù)和虛數(shù))
complex128 (64 位實(shí)數(shù)和虛數(shù))
復(fù)數(shù)使用 re+imI 來(lái)表示,其中 re 代表實(shí)數(shù)部分, im 代表虛數(shù)部分,I 代表根號(hào)負(fù) 1
var c1 complex64 = 5 + 10i
fmt.Printf("The value is: %v", c1) // 輸出: 5 + 10i
1)整型的零值為 0,浮點(diǎn)型的零值為 0.0。
2)int 和 uint 在 32 位操作系統(tǒng)上,它們均使用 32 位(4 個(gè)字節(jié)) ,在 64 位操作系統(tǒng)上,它們均使用 64 位(8 個(gè)字節(jié))。
3)uintptr 的長(zhǎng)度被設(shè)定為足夠存放一個(gè)指針即可。
4)整型的零值為 0,浮點(diǎn)型的零值為 0.0。
5)int 型是計(jì)算最快的一種類(lèi)型。
- 盡可能地使用 float64,因?yàn)?math 包中所有有關(guān)數(shù)學(xué)運(yùn)算的函數(shù)都會(huì)要求接收這個(gè)類(lèi)型。
- Go 是強(qiáng)類(lèi)型語(yǔ)言,不會(huì)進(jìn)行隱式轉(zhuǎn)換.。所以Go 中不允許不同類(lèi)型之間的混合使用,但允許常量之間的混合使用。
package main
func main() {
var a int
var b int32
a = 15
b = a + a // 編譯錯(cuò)誤
b = b + 5 // 因?yàn)?5 是常量,所以可以通過(guò)編譯
}
3 字符類(lèi)型
字符不是 Go 語(yǔ)言的一個(gè)類(lèi)型,字符只是整數(shù)的特殊用例:
1)byte 類(lèi)型是 uint8的別名:下面三個(gè)表達(dá)式等價(jià):
var ch byte = 'A'
var ch byte = 65
var ch byte = '\x41' // \x 總是緊跟著長(zhǎng)度為 2 的 16 進(jìn)制數(shù)
2) rune 是 int32 的別名。
Unicode 至少占用 2 個(gè)字節(jié),所以GO使用 int16 或者 int 類(lèi)型來(lái)表示。如果需要使用到 4 字節(jié),則會(huì)加上 \U 前綴;前綴 \u 則總是緊跟著長(zhǎng)度為 4 的 16 進(jìn)制數(shù),前綴 \U緊跟著長(zhǎng)度為 8 的 16 進(jìn)制數(shù)。
var ch int = '\u0041'
var ch2 int = '\u03B2'
var ch3 int = '\U00101234'
fmt.Printf("%d - %d - %d\n", ch, ch2, ch3) // integer
fmt.Printf("%c - %c - %c\n", ch, ch2, ch3) // character
fmt.Printf("%X - %X - %X\n", ch, ch2, ch3) // UTF-8 bytes
fmt.Printf("%U - %U - %U", ch, ch2, ch3) // UTF-8 code point
B 字符串
// $GOROOT/src/pkg/runtime/runtime.h
struct String
{
byte* str;
intgo len;
};
1)字符串是 UTF-8 字符的一個(gè)序列(當(dāng)字符為 ASCII 碼時(shí)則占用 1 個(gè)字節(jié),其它字符根據(jù)需要占用 2-4 個(gè)字節(jié))
2)字符串在Go語(yǔ)言?xún)?nèi)存模型中用一個(gè)2字長(zhǎng)的數(shù)據(jù)結(jié)構(gòu)表示。它包含一個(gè)指向字符串存儲(chǔ)數(shù)據(jù)的指針和一個(gè)長(zhǎng)度數(shù)據(jù)。

3)字符串是一種值類(lèi)型,且值不可變。所以多字符串共享同一個(gè)存儲(chǔ)數(shù)據(jù)是安全的。
4)切分操作 str[i:j] 會(huì)得到一個(gè)新的2字長(zhǎng)結(jié)構(gòu),一個(gè)可能不同的但仍指向同一個(gè)字節(jié)序列(即上文說(shuō)的存儲(chǔ)數(shù)據(jù))的指針和長(zhǎng)度數(shù)據(jù)。字符串切分不涉及內(nèi)存分配。
5)string 類(lèi)型的零值為ptr為nil,len為0的字符串,即空字符串 ""
6)獲取字符串中某個(gè)字節(jié)的地址的行為是非法的,例如: &str[i]
C slice
// $GOROOT/src/pkg/runtime/runtime.h
struct Slice
{ // must not move anything
byte* array; // actual data
uintgo len; // number of elements
uintgo cap; // allocated number of elements
};

1)一個(gè)slice是一個(gè)數(shù)組某個(gè)部分的引用。在內(nèi)存中,它是一個(gè)包含3個(gè)域的結(jié)構(gòu)體:指向slice中第一個(gè)元素的指針,slice的長(zhǎng)度,以及slice的容量。
2)切片 s 來(lái)說(shuō)該不等式永遠(yuǎn)成立: 0 <= len(s)<= cap(s)
3)在對(duì)slice進(jìn)行append等操作時(shí),可能會(huì)造成slice的自動(dòng)擴(kuò)容:
首先判斷,如果新申請(qǐng)容量(cap)大于2倍的舊容量(old.cap),最終容量(newcap)就是新申請(qǐng)的容量(cap)
否則判斷,如果舊切片的長(zhǎng)度小于1024,則最終容量(newcap)就是舊容量(old.cap)的兩倍,即(newcap=doublecap)
否則判斷,如果舊切片長(zhǎng)度大于等于1024,則最終容量(newcap)從舊容量(old.cap)開(kāi)始循環(huán)增加原來(lái)的 1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最終容量(newcap)大于等于新申請(qǐng)的容量(cap),即(newcap >= cap)
func growslice(et *_type, old slice, cap int) slice {
if raceenabled {
callerpc := getcallerpc(unsafe.Pointer(&et))
racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice))
}
if msanenabled {
msanread(old.array, uintptr(old.len*int(et.size)))
}
if et.size == 0 {
// 如果新要擴(kuò)容的容量比原來(lái)的容量還要小,這代表要縮容了,那么可以直接報(bào)panic了。
if cap < old.cap {
panic(errorString("growslice: cap out of range"))
}
// 如果當(dāng)前切片的大小為0,還調(diào)用了擴(kuò)容方法,那么就新生成一個(gè)新的容量的切片返回。
return slice{unsafe.Pointer(&zerobase), old.len, cap}
}
// 這里就是擴(kuò)容的策略
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
// 計(jì)算新的切片的容量,長(zhǎng)度。
var lenmem, newlenmem, capmem uintptr
const ptrSize = unsafe.Sizeof((*byte)(nil))
switch et.size {
case 1:
lenmem = uintptr(old.len)
newlenmem = uintptr(cap)
capmem = roundupsize(uintptr(newcap))
newcap = int(capmem)
case ptrSize:
lenmem = uintptr(old.len) * ptrSize
newlenmem = uintptr(cap) * ptrSize
capmem = roundupsize(uintptr(newcap) * ptrSize)
newcap = int(capmem / ptrSize)
default:
lenmem = uintptr(old.len) * et.size
newlenmem = uintptr(cap) * et.size
capmem = roundupsize(uintptr(newcap) * et.size)
newcap = int(capmem / et.size)
}
// 判斷非法的值,保證容量是在增加,并且容量不超過(guò)最大容量
if cap < old.cap || uintptr(newcap) > maxSliceCap(et.size) {
panic(errorString("growslice: cap out of range"))
}
var p unsafe.Pointer
if et.kind&kindNoPointers != 0 {
// 在老的切片后面繼續(xù)擴(kuò)充容量
p = mallocgc(capmem, nil, false)
// 將 lenmem 這個(gè)多個(gè) bytes 從 old.array地址 拷貝到 p 的地址處
memmove(p, old.array, lenmem)
// 先將 P 地址加上新的容量得到新切片容量的地址,然后將新切片容量地址后面的 capmem-newlenmem 個(gè) bytes 這塊內(nèi)存初始化。為之后繼續(xù) append() 操作騰出空間。
memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
} else {
// 重新申請(qǐng)新的數(shù)組給新切片
// 重新申請(qǐng) capmen 這個(gè)大的內(nèi)存地址,并且初始化為0值
p = mallocgc(capmem, et, true)
if !writeBarrier.enabled {
// 如果還不能打開(kāi)寫(xiě)鎖,那么只能把 lenmem 大小的 bytes 字節(jié)從 old.array 拷貝到 p 的地址處
memmove(p, old.array, lenmem)
} else {
// 循環(huán)拷貝老的切片的值
for i := uintptr(0); i < lenmem; i += et.size {
typedmemmove(et, add(p, i), add(old.array, i))
}
}
}
// 返回最終新切片,容量更新為最新擴(kuò)容之后的容量
return slice{p, old.len, newcap}
}
make和new
Go有兩個(gè)數(shù)據(jù)結(jié)構(gòu)創(chuàng)建函數(shù):new和make。
new返回一個(gè)指向已清零內(nèi)存的指針,而make返回一個(gè)復(fù)雜的結(jié)構(gòu)。
基本的區(qū)別是 new(T) 返回一個(gè) *T ,返回的這個(gè)指針可以被隱式地消除引用(圖中的黑色箭頭) 。而 make(T, args) 返回一個(gè)普通的T.

注意:空切片和 nil 切片是不同的,空切片指向的地址不是nil,指向的是一個(gè)內(nèi)存地址,但是它沒(méi)有分配任何內(nèi)存空間,即底層元素包含0個(gè)元素。
不管是使用 nil 切片還是空切片,對(duì)其調(diào)用內(nèi)置函數(shù) append,len 和 cap 的效果都是一樣的。
D map
map在底層是用哈希表實(shí)現(xiàn)的。
struct Hmap
{
uint8 B; // 可以容納2^B個(gè)項(xiàng)
uint16 bucketsize; // 每個(gè)桶的大小
byte *buckets; // 2^B個(gè)Buckets的數(shù)組
byte *oldbuckets; // 前一個(gè)buckets,只有當(dāng)正在擴(kuò)容時(shí)才不為空
};
struct Bucket
{
uint8 tophash[BUCKETSIZE]; // hash值的高8位....低位從bucket的array定位到bucket
Bucket *overflow; // 溢出桶鏈表,如果有
byte data[1]; // BUCKETSIZE keys followed by BUCKETSIZE values
};
// BUCKETSIZE是用宏定義的8,每個(gè)bucket中存放最多8個(gè)key/value對(duì), 如果多于8個(gè),那么會(huì)申請(qǐng)一個(gè)新的bucket,overflow指向它
1)Bucket中key/value的放置順序,是將keys放在一起,values放在一起。
2)擴(kuò)容使用的是增量擴(kuò)容:擴(kuò)容會(huì)建立一個(gè)大小是原來(lái)2倍的新的表,將舊的bucket搬到新的表中之后,并不會(huì)將舊的bucket從oldbucket中刪除,而是加上一個(gè)已刪除的標(biāo)記。當(dāng)hash表擴(kuò)容之后,需要將那些舊的pair重新哈希到新的table上,這個(gè)工作是逐步的完成(在insert和remove時(shí)每次搬移1-2個(gè)pair)
查找過(guò)程
- 根據(jù)key計(jì)算出hash值。
- 如果存在old table, 首先在old table中查找,如果找到的bucket已經(jīng)evacuated,轉(zhuǎn)到步驟3。 反之,返回其對(duì)應(yīng)的value。
- 在new table中查找對(duì)應(yīng)的value。
插入過(guò)程分析
- 根據(jù)key算出hash值,進(jìn)而得出對(duì)應(yīng)的bucket。
- 如果bucket在old table中,將其重新散列到new table中。
- 在bucket中,查找空閑的位置,如果已經(jīng)存在需要插入的key,更新其對(duì)應(yīng)的value。
- 根據(jù)table中元素的個(gè)數(shù),判斷是否grow table。
- 如果對(duì)應(yīng)的bucket已經(jīng)full,重新申請(qǐng)新的bucket作為overbucket。
- 將key/value pair插入到bucket中。
F nil
Go任何類(lèi)型在未初始化時(shí)都對(duì)應(yīng)一個(gè)零值:布爾類(lèi)型是false,整型是0,字符串是"",而指針,函數(shù),interface,slice,channel和map的零值都是nil。