golang學(xué)習(xí)--slice

切片定義

切片(Slice)是一個(gè)擁有相同類型元素的可變長度的序列。它是基于數(shù)組類型做的一層封裝。它非常靈活折欠,支持自動(dòng)擴(kuò)容。切片是一個(gè)引用類型,它的內(nèi)部結(jié)構(gòu)包含地址、長度和容量耳璧。切片一般用于快速地操作一塊數(shù)據(jù)集合。

數(shù)組與切片

切片的數(shù)據(jù)實(shí)際是通過數(shù)組來保存的酱床,每個(gè)切片都有三個(gè)信息:底層數(shù)組的指針、切片的長度(len)和切片的容量(cap)趟佃。
舉個(gè)栗子扇谣,底層數(shù)組a := [8]int{0, 1, 2, 3, 4, 5, 6, 7}昧捷;

  • 切片s1 := a[:5],切片和數(shù)組對(duì)應(yīng)關(guān)系:


    Go-slice-2020-05-12-15-23-13
  • 切片s2 := a[3:6]罐寨,切片和數(shù)組對(duì)應(yīng)關(guān)系:


    Go-slice-2020-05-12-15-44-43

指向同一個(gè)底層數(shù)組的切片修改值

切片是指向底層數(shù)組的引用類型靡挥,指向同一個(gè)底層數(shù)組的切片底層數(shù)據(jù)存放都是在同一個(gè)位置,修改某個(gè)切片會(huì)影響到在同一個(gè)范圍的切片

import (
    "fmt"
    "testing"
)

func TestSliceShareMemory(t *testing.T) {
    year := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
    Q2 := year[3:6]
    t.Log(Q2, len(Q2), cap(Q2))
    summer := year[5:8]
    t.Log(summer, len(summer), cap(summer))
    summer[0] = "Unkonw"
    t.Log(Q2)
    t.Log(year)
}
// === RUN   TestSliceShareMemory
//     TestSliceShareMemory: slice_test.go:36: [Apr May Jun] 3 9
//     TestSliceShareMemory: slice_test.go:38: [Jun Jul Aug] 3 7
//     TestSliceShareMemory: slice_test.go:40: [Apr May Unkonw]
//     TestSliceShareMemory: slice_test.go:41: [Jan Feb Mar Apr May Unkonw Jul Aug Sep Oct Nov Dec]
// --- PASS: TestSliceShareMemory (0.00s)
// PASS

切片表達(dá)式

切片表達(dá)式從字符串衩茸、數(shù)組芹血、指向數(shù)組或切片的指針構(gòu)造子字符串或切片。它有兩種變體:一種指定low和high兩個(gè)索引界限值的簡單的形式楞慈,另一種是除了low和high索引界限值外還指定容量的完整的形式:

  1. 切片len()是可訪問長度幔烛,容量cap()是總空間大小。通過數(shù)組生成的切片囊蓝, len為首尾索引之差饿悬,cap為從切片首索引到數(shù)組末尾長度
  2. 切片s[low:high:max],從切片s的low處到high處所獲得的切片聚霜,len=high-low狡恬,cap=max-low
func TestSliceExpression(t *testing.T) {
    a := [5]int{1, 2, 3, 4, 5}
    // b := a[1:3:7]
    b := a[1:3:5]
    fmt.Printf("b:%v len(b):%v cap(b):%v\n", b, len(b), cap(b))
}
// === RUN   TestSliceExpression
// b:[2 3] len(b):2 cap(b):4
// --- PASS: TestSliceExpression (0.00s)
// PASS

切片不能比較

兩個(gè)切片不能直接比較,會(huì)報(bào)錯(cuò):

func TestSliceCompare(t *testing.T) {
    a := []int{1, 2, 3, 4}
    b := []int{1, 2, 3, 4}
    if a == b {
        t.Log("a==b")
    }
}
// invalid operation: a == b (slice can only be compared to nil)
// FAIL go_learn/go_test/slice_test [build failed]
// FAIL

切片的append

切片通過append()添加元素時(shí)蝎宇,未超過newcap時(shí)底層數(shù)組地址不變弟劲,超過的話底層數(shù)組會(huì)申請新的內(nèi)存地址。新申請的容量大小計(jì)算分成了兩步姥芥,有關(guān)append的源碼在$GOROOT/src/runtime/slice.go兔乞,可以自己去分析。

計(jì)算邏輯newcap

  1. new cap > old * 2直接申請新容量大小;
  2. 小于2倍時(shí)凉唐,len<1024翻倍庸追,len>1024加上1/4
//$GOROOT/src/runtime/slice.go
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
    }
  }
}
  
//...略
lenmem = uintptr(old.len)
newlenmem = uintptr(cap)
capmem = roundupsize(uintptr(newcap))
overflow = uintptr(newcap) > maxAlloc
newcap = int(capmem)
//...略

實(shí)際申請內(nèi)存大小

上面先算了個(gè)邏輯上的newcap,實(shí)際申請內(nèi)存的時(shí)候台囱,由于內(nèi)存對(duì)齊的關(guān)系不會(huì)直接就用newcap淡溯。上面的代碼就是在算好了newcap后會(huì)調(diào)用roundupsize()得到實(shí)際的大小。

//$GOROOT/src/runtime/msize.go
// Returns size of the memory block that mallocgc will allocate if you ask for the size.
func roundupsize(size uintptr) uintptr {
    if size < _MaxSmallSize {
        if size <= smallSizeMax-8 {
            return uintptr(class_to_size[size_to_class8[(size+smallSizeDiv-1)/smallSizeDiv]])
        } else {
            return uintptr(class_to_size[size_to_class128[(size-smallSizeMax+largeSizeDiv-1)/largeSizeDiv]])
        }
    }
    if size+_PageSize < size {
        return size
    }
    return alignUp(size, _PageSize)
}

roundupsize()中的class_to_size簿训、size_to_class8是存了具體大小的數(shù)組咱娶,根據(jù)傳入的newcap來算出下標(biāo),拿到對(duì)應(yīng)的大小值强品。這些數(shù)組在//$GOROOT/src/runtime/sizeclasses.go豺总,這個(gè)文件又是//$GOROOT/src/runtime/mksizeclasses.go.go生成的,生成規(guī)則就先不去看了择懂。

//$GOROOT/src/runtime/sizeclasses.go
// Code generated by mksizeclasses.go; DO NOT EDIT.
//go:generate go run mksizeclasses.go
var class_to_size = [_NumSizeClasses]uint16{0, 8, 16, 32, 48, 64, 80, 96, 112, 128 ...}

var size_to_class8 = [smallSizeMax/smallSizeDiv + 1]uint8{0, 1, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25 ...}

了解了上面的內(nèi)容之后就可以理解下面的幾個(gè)例子了:

func TestSliceAppend(t *testing.T) {
    var a = make([]int, 5, 10)
    for i := 0; i < 10; i++ {
        a = append(a, i)
        fmt.Printf("a ptr: %p\n", a)
    }
    fmt.Println(a)
}
// === RUN   TestSliceAppend
// a ptr: 0xc00000c320
// a ptr: 0xc00000c320
// a ptr: 0xc00000c320
// a ptr: 0xc00000c320
// a ptr: 0xc00000c320
// a ptr: 0xc0000100a0
// a ptr: 0xc0000100a0
// a ptr: 0xc0000100a0
// a ptr: 0xc0000100a0
// a ptr: 0xc0000100a0
// [0 0 0 0 0 0 1 2 3 4 5 6 7 8 9]
// --- PASS: TestSliceAppend (0.00s)
// PASS

func TestSliceAppend2(t *testing.T) {
    s := []int{1, 2, 3, 4}
    a := make([]int, 3, 6)
    b := append(a, 10)
    a[0] = 50
    fmt.Printf("a: %v\tptr: %p\tfirst: %v\n", a, a, a[0])
    fmt.Printf("b: %v\tptr: %p\tfirst: %v\n", b, b, b[0])

    b = append(a, s...)
    a[0] = 100
    fmt.Printf("a: %v\tptr: %p\tfirst: %v\n", a, a, a[0])
    fmt.Printf("b: %v\tptr: %p\tfirst: %v\n", b, b, b[0])
}
// === RUN   TestSliceAppend2
// a: [50 0 0]  ptr: 0xc00000a330   first: 50
// b: [50 0 0 10]   ptr: 0xc00000a330   first: 50
// a: [100 0 0] ptr: 0xc00000a330   first: 100
// b: [50 0 0 1 2 3 4]  ptr: 0xc00001a4e0   first: 50
// --- PASS: TestSliceAppend2 (0.00s)
// PASS

func TestSliceAppend3(t *testing.T) {
    a1 := make([]int, 20)
    b1 := make([]int, 40)
    a1 = append(a1, b1...)
    fmt.Println(len(a1), cap(a1))

    a2 := make([]int, 20)
    b2 := make([]int, 42)
    a2 = append(a2, b2...)
    fmt.Println(len(a2), cap(a2))
}
// === RUN   TestSliceAppend3
// 60 60
// 62 64
// --- PASS: TestSliceAppend3 (0.00s)
// PASS

切片元素刪除

在要?jiǎng)h除的元素左右切兩下:a1 = append(a1[:1], a1[2:]...),刪除其實(shí)是將所刪元素后面的往前挪另玖。

func TestSliceDelete(t *testing.T) {
    a := []int{30, 31, 32, 33, 34, 35, 36, 37}
    // 要?jiǎng)h除索引為2的元素
    a = append(a[:2], a[3:]...)
    t.Log(a)
}
// === RUN   TestSliceDelete
//     TestSliceDelete: slice_test.go:98: [30 31 33 34 35 36 37]
// --- PASS: TestSliceDelete (0.00s)
// PASS

參考內(nèi)容

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末困曙,一起剝皮案震驚了整個(gè)濱河市表伦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌慷丽,老刑警劉巖蹦哼,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異要糊,居然都是意外死亡纲熏,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門锄俄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來局劲,“玉大人,你說我怎么就攤上這事奶赠∮闾睿” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵毅戈,是天一觀的道長苹丸。 經(jīng)常有香客問我,道長苇经,這世上最難降的妖魔是什么赘理? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮扇单,結(jié)果婚禮上商模,老公的妹妹穿的比我還像新娘。我一直安慰自己令花,他們只是感情好阻桅,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著兼都,像睡著了一般嫂沉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扮碧,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天趟章,我揣著相機(jī)與錄音,去河邊找鬼慎王。 笑死蚓土,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赖淤。 我是一名探鬼主播蜀漆,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼咱旱!你這毒婦竟也來了确丢?” 一聲冷哼從身側(cè)響起绷耍,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鲜侥,沒想到半個(gè)月后褂始,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡描函,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年崎苗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舀寓。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡胆数,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出基公,到底是詐尸還是另有隱情幅慌,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布轰豆,位于F島的核電站胰伍,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏酸休。R本人自食惡果不足惜骂租,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望斑司。 院中可真熱鬧渗饮,春花似錦、人聲如沸宿刮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽僵缺。三九已至胡桃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間磕潮,已是汗流浹背翠胰。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留自脯,地道東北人之景。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像膏潮,于是被迫代替她去往敵國和親锻狗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345