1. <tr id="33chb"><label id="33chb"></label></tr>
  2. <pre id="33chb"></pre>
    當前位置:首頁 > 短網址資訊 > 正文內容

    Golang Slice 的一些事

    女主宣言

    使用 Golang 編程時,常會使用到一個數據結構 —— Slice,這篇文帶大家看看 Slice 具體的數據結構以及常用手法。

    PS:豐富的一線技術、多元化的表現形式,盡在“HULK一線技術雜談”,點關注哦!

    1. Slice 數據結構



    首先,直接從源碼$YOUR_GO_DIR/src/runtime/slice.go(其中$YOUR_GO_DIR指你自己go源代碼的根目錄)中找到定義的slice結構,如下:

    type slice struct {
       // 任意類型指針(類似C語言中的 void* ), 指向實際存儲slice數據的數組    array unsafe.Pointer    len   int // length, 長度    cap   int // capacity, 容量 }

    從結構很好看出,通過make()函數(比如:make([]int, 10, 20))創建出來的slice,其實就是由兩部分組成:slice的"描述"(上面的結構體) + 存儲數據的數組(指針指向的數組)。

    備注:后文將使用SliceHeader代替上面的結構體,SliceHeader是Rob PikeGolang/slices博文中暫用來指代的名詞,這里我也借用一下。為了方便理解,把slice拆成 SliceHeader + 存儲數據的數組 兩部分。


    2. 使用 Slice 須知 



    2.1 值傳遞下的Slice

    我們知道Go的參數是值傳遞,那么這里有個問題需要考慮: 當把一個slice變量通過參數傳遞給某函數時,傳的是SliceHeader、還是整個SliceHeader+數據(存儲數據的數組)都被復制過去?

    比如,這樣的代碼:

    s := make([]string, 10)  
    saveSlice(s)

    當我們在項目中某個slice有10萬元素,如果傳參數直接復制SliceHeader + 數據,那么這是一定不能接受的。

    Rob Pike有這樣一句話定義Slice: slice不是數組,它是對數組進行了描述(A slice is not an array. A slice describes a piece of an array)。 實際上,在上面的代碼片段中saveSlice(s)接收到的是變量s的一個副本(就是一個值跟s一樣,但是是全新的變量),這個副本跟變量s一樣,有一個指向同個數組的指針、len和cap相同值。

    為什么?因為Go是值傳遞,簡單試驗就知道。

    實驗一:如果slice變量參數傳遞,是復制了數據,那么在函數中操作"被復制過來的"數據,不會對原數據造成影響。

    // 代碼片段
    data := make([]int, 10)
    fmt.Println("處理前數據: ", data)
    changeIndex0(data)
    fmt.Println("處理后數據: ", data)

    函數 changeIndex0(data []int)

    // 替換第一個元素的值
    func changeIndex0(data []int)  {
        data[0] = 99
    }

    實驗結果

    處理前數據:  [0 0 0 0 0 0 0 0 0 0]
    處理后數據:  [99 0 0 0 0 0 0 0 0 0]

    顯然,從結果中看得出,原始數據被修改了,所以可以得出結論是:傳遞slice變量時,并不是復制真正存儲數據的數組進行傳遞。

    所以,在實際項目中,直接傳遞slice變量與傳遞slice變量的指針,對內存的消耗區別并不是很大。一個SliceHeader的大小是24字節,而指針大小8字節。

    備注: SliceHeader 24字節計算方式:8字節(指針) + 8字節(整型int, len) + 8字節(整型int, cap),這是以我自己電腦為例(64位),指針大小8字節;整型int大小也跟編譯器有關,但Golang中最少是32bit,我在本機使用unsafe.SizeOf()實測是8字節。

    2.2 Slice截取和擴充

    說到底,slice由SliceHeader和數組構成。涉及到數組,避不開的問題就是定長,也就是一旦數組長度確定了就無法改變。如果非要改變長度,那只能一個辦法:重新分配一個新的數組。

    對于slice也一樣,如果一個slice已經確定了容量(capacity),那么如果要擴充該slice的容量,也必須重新分配一個存數據的數組。

    備注:slice的容量在使用make([]byte, 10, 20)時,第三個參數已經確定;第三個參數就是容量(capacity),如果不指定,默認跟第二個參數(長度len)一樣。


    i. 截取子Slice


    當基于原Slice進行截取子Slice時,實際上操作的還是原Slice的元素。也就是對子Slice的元素進行修改,都會在原Slice中體現。

    實驗 二:操作從原 Slice 截取而獲得的子 Slice

    // 代碼片段
    data := make([]int, 10)
    fmt.Println("處理前數據: ", data, len(data), cap(data))
    subSlice(data)
    fmt.Println("處理后數據: ", data, len(data), cap(data))

    函數 subSlice(data []int)

    // 截取slice
    func subSlice(data []int)  {
        data[0] = 99
        data = data[0:8]
        fmt.Println("函數中數據: ", data, len(data), cap(data))
    }

    實驗結果

    處理前數據:  [0 0 0 0 0 0 0 0 0 0] 10 10
    函數中數據:  [99 0 0 0 0 0 0 0] 8 10
    處理后數據:  [99 0 0 0 0 0 0 0 0 0] 10 10

    從實驗結果可以看出,在Slice的容量(capacity)范圍內子Slice截取,是直接使用了原Slice的數組,并沒有為該子Slice分配新的數組。

    如果我需要截取一個子Slice并且希望該子Slice有新的數組,該怎么操作?這是可以使用copy()函數。

    sub := make([]int, 2)
    copy(sub, data[3:5])
    ii. 擴充Slice


    事實上,擴充Slice的操作就是:重新創建一個更大容量的Slice,然后把原Slice中的數據復制到新的Slice里面。

    比如:常用操作append()

    fmt.Printf("append()前: len: %d, cap: %d \n", len(data), cap(data))
    data = append(data, 5)
    fmt.Printf("append()后: len: %d, cap: %d \n", len(data), cap(data))

    結果

    append()前: len: 10, cap: 10 
    append()后: len: 11, cap: 20

    append()中的操作就是新建了一個容量為原來兩倍的Slice,然后把原來的數據復制到新Slice并且把新的元素加上。

    3. Slice 常用操作函數


    Go提供了方便操作的語法糖,如: data[2:5],以此來獲取第二到第四(包括第四)個元素。

    備注: ':' 左右都可以不指定值。右邊的值不可以超過該Slice的容量大小,否則會Panic。

    3.1 copy() 復制Slice的值到另外一個Slice,上面例子也用到了,這函數會自動參考len更小的那個Slice,不會發生報slice bounds out of range的異常。

    3.2 append()給某個Slice添加元素,也是常用的,上面的例子也有體現。

    4. 其他


    4.1 關于string

    從源碼包runtimestring.go中可以看到字符串的struct。

    type stringStruct struct {
        str unsafe.Pointer
        len int
    }

    也就是,string實際上就是只讀的byte切片(Slice),只是從Golang語言層面提供的語法支持而已。因為只能讀,所以容量的存在與否都無濟于事。

    4.2 關于Slice nil

    我們知道make()方法專門用來新建Slice、map、chan,但是我們也可以用new()來建Slice,但是兩者有區別。

    // 代碼片段
    nilSlice := new([]int)
    fmt.Printf("nilSlice is nil: %v \n", *nilSlice == nil)
    emptySlice := make([]int, 0)
    fmt.Printf("emptySlice is nil: %v \n", emptySlice == nil)

    結果打印

    nilSlice is nil: true 
    emptySlice is nil: false

    也就是用new()創建后的Slice變量是零值,而make()創建一個0長度的Slice并不是nil。

    為什么?因為new() 和 make()做的事情不一樣。

    new()做了兩件事

    • 為該類型分配內存

    • 置零值(不同類型的零值不一樣,比如: bool是false,整型是0...等)

    make()也做了兩件事

    • 為該類型分配內存

    • 初始化

    以Slice為例,new([]int)得到的SliceHeader是:

    sliceHeader {
        array: nil,
        len: 0,
        cap: 0,
    }

    make([]int, 0)得到的SliceHeader應該是:

    sliceHeader {
        array: 0x8201d0140, // 指向0個元素的數組
        len: 0,
        cap: 0,
    }

    5. 結語


    從Slice的實現、使用場景進行更加全面的了解,會對在項目中的使用有更大的幫助以及盡量避免因為不知道細節而錯用。


    參考:

    掃描二維碼推送至手機訪問。

    版權聲明:本文由短鏈接發布,如需轉載請注明出處。

    本文鏈接:http://www.virginiabusinesslawupdate.com/article_316.html

    分享給朋友:

    相關文章

    短網址使用技巧之高級篇:短網址訪問統計分析模塊

    (1)時段計算剖析:時段概況計算、時段計算、日計算、月計算;(2)用戶地域計算剖析;(3)用戶來歷計算剖析;(4)用戶終端計算剖析:用戶瀏覽器、操作體系、屏幕分辨率、言語環境;  短網址以此為數據支持,為以后擬定戰略決策供給量化的依據?! ?..

    使用.net 的短鏈接源碼(短網址)

    使用.net 的短鏈接源碼(短網址)

    FT12短鏈接的小編今天分享一篇關于.net語言的短網址源碼。短鏈接大家都不陌生,例如新浪的 t.cn 、京東的 3.cn 、淘寶的 tb.cn 、最后歡迎的www.virginiabusinesslawupdate.com  等等。都已經是家喻戶曉的短鏈接域名。不知道有...

    90后再次被盯上,兩大行同時入局校園貸市場

    90后再次被盯上,兩大行同時入局校園貸市場

    [ ft12短網址導讀 ] 在2017年金融體系“去杠桿、控風險”的大背景下,幾乎所有的風險領域都在收縮,此時國有大行布局校園貸,只能有兩個解釋:一是在大行看來,校園貸屬于低風險業務;另外是擔當社會責任,配合金融治理“堵偏門、開正...

    神州優車22億領投小鵬汽車A輪

    神州優車22億領投小鵬汽車A輪

    北京時間2017年6月12日下午,FT12短網址小編獲悉,在北京神州優車總部,小鵬轎車宣布獲得神州優車領投的22億元A輪戰略出資。值得注意的是,該筆出資是神州優車發起優車工業基金后的第一筆出資。神州優車董事長兼CEO陸正耀稱,未來將在出售、...

    收益率10%,民宿眾籌到底靠譜嗎?

    收益率10%,民宿眾籌到底靠譜嗎?

    [ FT12短網址資訊 ] 民宿眾籌平臺如何賺錢?民宿眾籌是怎樣火起來的?民宿眾籌究竟靠譜嗎?民宿眾籌值得信任嗎?項目失敗了怎么辦?如何判斷一個項目是否靠譜?作為投資者和平臺方,該如何去判斷某個項目是否靠譜?圖片源自網絡葛女士是一...

    跳水女王陳若琳創業 放下5塊金牌做起護眼燈

    【億邦動力網訊】臺封王,參加三屆奧運會拿了5塊金牌,成功實現跳水"大滿貫"的陳若琳,金牌數目更超越師姐伏明霞。去年,24歲的她發了條微博,淡然宣布退役。退役后的陳若琳需要一個新的開始新的方向。陳若琳找到了同樣熱愛公益的遲...

    發表評論

    訪客

    ◎歡迎參與討論,請在這里發表您的看法和觀點。
    一本色综合网久久
    1. <tr id="33chb"><label id="33chb"></label></tr>
    2. <pre id="33chb"></pre>