深入理解go的slice和到底什麼時候該用slice?

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

前言

用過go語言的親們都知道,slice(中文翻譯為切片)在編程中經常用到,它代表變長的序列,序列中每個元素都有相同的類型,類似一個動態數組,利用append可以實現動態增長,利用slice的特性可以很容易的切割slice,它們是怎麼實現這些特性的呢?現在我們來探究一下這些特性的本質是什麼。

先瞭解一下slice的特性

  • 定義一個slice
    s := []int{1,2,3,4,5}    fmt.Println(s)  // [1 2 3 4 5]

一個slice類型一般寫作[]T,其中T代表slice中元素的類型;slice的文法和數組很像,只是沒有固定長度而已。

  • slice的擴容
    s := []int{1,2,3,4,5}    s = append(s, 6)    fmt.Println(s)  // [1 2 3 4 5 6]

內建append函數在現有數組的長度 < 1024 時 cap 增長是翻倍的,再往上的增長率則是 1.25,至於為何後面會說。

  • slice的切割
    s := []int{1,2,3,4,5,6}    s1 := s[0:2]    fmt.Println(s1)  // [1 2]    s2 := s[4:]    fmt.Println(s2)  // [5 6]    s3 := s[:4]    fmt.Println(s3)  // [1 2 3 4]
  • slice作為函數參數
    package main    import "fmt"    func main() {        slice_1 := []int{1, 2, 3, 4, 5}        fmt.Printf("main-->data:\t%#v\n", slice_1)        fmt.Printf("main-->len:\t%#v\n", len(slice_1))        fmt.Printf("main-->cap:\t%#v\n", cap(slice_1))        test1(slice_1)        fmt.Printf("main-->data:\t%#v\n", slice_1)        test2(&slice_1)        fmt.Printf("main-->data:\t%#v\n", slice_1)    }    func test1(slice_2 []int) {        slice_2[1] = 6666               // 函數外的slice確實有被修改        slice_2 = append(slice_2, 8888) // 函數外的不變        fmt.Printf("test1-->data:\t%#v\n", slice_2)        fmt.Printf("test1-->len:\t%#v\n", len(slice_2))        fmt.Printf("test1-->cap:\t%#v\n", cap(slice_2))    }    func test2(slice_2 *[]int) { // 這樣才能修改函數外的slice        *slice_2 = append(*slice_2, 6666)    }

結果:

main-->data:    []int{1, 2, 3, 4, 5}main-->len: 5main-->cap: 5test1-->data:   []int{1, 6666, 3, 4, 5, 8888}test1-->len:    6test1-->cap:    12main-->data:    []int{1, 6666, 3, 4, 5}main-->data:    []int{1, 6666, 3, 4, 5, 6666}

這裡要注意注釋的地方,為何slice作為值傳遞參數,函數外的slice也被更改了?為何在函數內append不能改變函數外的slice?要回da這些問題就得瞭解slice內部結構,詳細請看下面.

slice的內部結構

其實slice在Go的執行階段程式庫中就是一個C語言動態數組的實現,在$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};

這個結構有3個欄位,第一個欄位表示array的指標,就是真實資料的指標(這個一定要注意),所以才經常說slice是數組的引用,第二個是表示slice的長度,第三個是表示slice的容量,注意:len和cap都不是指標

現在就可以解釋前面的例子slice作為函數參數提出的問題:函數外的slice叫slice_1,函數的參數叫slice_2,當函數傳遞slice_1的時候,其實傳入的確實是slice_1參數的複製,所以slice_2複製了slise_1,但要注意的是slice_2裡儲存的數組的指標,所以當在函數內更改數組內容時,函數外的slice_1的內容也改變了。在函數內用append時,append會自動以倍增的方式擴充slice_2的容量,但是擴充也僅僅是函數內slice_2的長度和容量,slice_1的長度和容量是沒變的,所以在函數外列印時看起來就是沒變。

append的運作機制

在對slice進行append等操作時,可能會造成slice的自動擴容。其擴容時的大小增長規則是:

  • 如果新的slice大小是當前大小2倍以上,則大小增長為新大小
  • 否則迴圈以下操作:如果當前slice大小小於1024,按每次2倍增長,否則每次按當前大小1/4增長。直到增長的大小超過或等於新大小。
  • append的實現只是簡單的在記憶體中將***舊slice***複製給***新slice***

至於為何會這樣,你要看一下golang的源碼就知道了:https://github.com/golang/go/blob/master/src/runtime/slice.go

    newcap := old.cap    if newcap+newcap < cap {        newcap = cap    } else {        for {            if old.len < 1024 {                newcap += newcap            } else {                newcap += newcap / 4            }            if newcap >= cap {                break            }        }    }

為何不用動態鏈表實現slice?

  • 首先拷貝一斷連續的記憶體是很快的,假如不想發生拷貝,也就是用動態鏈表,那你就沒有連續記憶體。此時隨機訪問開銷會是:鏈表 O(N), 2倍增長塊鏈 O(LogN),二級表一個常數很大的O(1)。問題不僅是演算法上開銷,還有記憶體位置分散而對緩衝高度不友好,這些問題i在連續記憶體方案裡都是不存在的。除非你的應用是狂append然後只順序讀一次,否則最佳化寫而犧牲讀都完全不 make sense. 而就算你的應用是嚴格順序讀,快取命中率也通常會讓你的綜合效率比拷貝換連續記憶體低。
  • 對小 slice 來說,連續 append 的開銷更多的不是在 memmove, 而是在分配一塊新空間的 memory allocator 和之後的 gc 壓力(這方面對鏈表更是不利)。所以,當你能大致知道所需的最大空間(在大部分時候都是的)時,在make的時候預留相應的 cap 就好。如果所需的最大空間很大而每次使用的空間量分布不確定,那你就要在浪費記憶體和耗 CPU 在 allocator + gc 上做權衡。
  • Go 在 append 和 copy 方面的開銷是可預知+可控的,應用上簡單的調優有很好的效果。這個世界上沒有免費的動態增長記憶體,各種實現方案都有設計權衡。

什麼時候該用slice?

在go語言中slice是很靈活的,大部分情況都能表現的很好,但也有特殊情況。當程式要求slice的容量超大並且需要頻繁的更改slice的內容時,就不應該用slice,改用list更合適。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.