崩潰 golang入坑系列

早上(11.30)收到郵件,Vultr東京機房網絡故障。當時搭建SS時,考慮到了機房故障。因此特地分出了日本和香港兩條線路。但千算萬算,忘記數據庫還在東京機房中。 如今網絡故障,SS服務器沒法讀取數據庫中的帳號信息。因而乎,主備兩條線同時宕了。哭笑兩聲,沒錢去作異地雙活,訪問量又不大,就這麼湊合吧。 我就不信Vultr網絡故障是大機率事件。若是很頻繁的出故障,用戶會用腳去投票的。golang

鐵路警察各管一段,Vultr的故障讓Vultr的運維去背鍋吧。咱們言歸正傳,繼續聊Golang。數據庫

在<擼袖子>那節,咱們提到了數組。 其中用了一個不多的篇幅說了一下數組的近親-切片。當時說到數組使用起來不方便,Golang提供了一種更方便的數組使用方式,就是切片。這節中,咱們就先來講切片。數組

先來複習數組的概念,就是一組相同數據類型的集合。在說數組的時候,沒有什麼動態擴展的方法。只能實現規定好這個數組有多少個元素,而後按照下標進行增刪改查。在真實環境中,有很大的侷限性。 切片做爲數組的近親,就彌補了這種缺陷。Golang所提供的切片,內置了不少方法來達到數組的動態擴容/縮容。服務器

切片既然是數組的近親,那聲明方式基本上長得很像:網絡

var name []type

name天然是切片名稱,type就是數據類型。僅此而已,就完成了一個切片的聲明。和數組的聲明最大的不一樣,就在於沒有長度限制。這是最經常使用的聲明方式,還有一種文縐縐的聲明方式,以下:數據結構

name := make([]type, length, capacity)

一瞅就有種學院派的做風。 多了兩個參數,length和capacity。這兩個概念理解很差,這就是一個大坑。Golang爲了讓切片有很高的讀效率和又不容易出現指針越界,就創造了length和capacity兩個屬性。app

capacity指的是此切片當前指向內存的數據大小。而length指的是當前切片的容量大小,從邏輯上來看,知足這個條件: 0<=length<=capacity。運維

爲何說這是一個坑? 若是打算用切片操做目標內存的時候,必須當心別append過頭,不然就操做到新開啓的內存塊去了,也要當心別意外覆蓋了原slice的值。好比下面:函數

s := []int{10} //建立一個legnth = capacity = 1 的切片,而且初始化爲10
s = append(s,11) //容量不夠,翻倍擴容。legnth = capacity = 2,如今是10,11
s = append(s,12) //容量又不夠了,再次擴容。legnth =3, capacity = 4,如今是10,11,12
x := append(s, 13) //容量夠了,不擴容。legnth = capacity = 4,如今是10,11,12,13
坑來了
y := append(s, 14) //容量夠了,不擴容。legnth = capacity = 4,如今是10,11,12,14

但若是你將上面代碼輸出一下,會看到x和y的值是相同的,都是10.11.12.14。這裏麪包含了切片的本質。 在Golang官方文檔中說起,對切片單獨進行append操做,並不會修改切片的內容(也就是單獨執行append(s,12)),每每須要將append後的數據從新賦值給源切片,也就是s = append(s,12),這是Golang官方所推薦的用法。 上面的例子中,在x和y那兩行,由於s沒有發生變化,length=3.因此後面append的值會直接添加到末尾。而返回的又都是同一塊內存地址,因此x和y其實指的是同一塊內存,所以其內部值也是相同的。 能夠來一段代碼,把x,y和s的內存地址都輸出出來,結果就一目瞭然了。指針

若是嫌麻煩,那就用最簡單的方式:

var s []int
s = append(s,xxx)

而若是想輸出當前的length和capacity,就直接使用len()和cap()兩個內置函數。

數組容許存在空數據,切片也固然容許存在空切片。當直接聲明一個切片的時候,此時此刻,length = capacity = 0.

var numbers []int
此時此刻 len = 0 cap = 0 slice = []

又該如何判斷切片是否爲空呢?可使用length和capacity屬性,但不如使用nil來的簡單:

numbers == nil

true就表示是空切片,false表示是非空切片。

切片同數組相比,最靈活的方面在於切分子切片。例如能夠在代碼中,根據業務須要,隨時將一個大切片取出任意元素組成一個子切片。看下面:

numbers := []int{0,1,2,3,4,5,6,7,8}
number2 := numbers[:2] // 從0到2,但不包括2.因此是0,1
number3 := numbers[2:5] // 從2到5,但不包括5.因此是2,3,4
number4 := numbers[5:] // 從5到末尾,包括末尾。

上面number2, number3和number4都是子切片,在使用時,須要記住這些子切片都是指向了源切片某一塊內存,什麼意思?也就是說源切片元素髮生了變化,那麼子切片也會發生變化。不信? 在上面代碼中聲明子切片後,任意修改numbers的元素,在看看結果。

若是不想受源切片影響怎麼辦?使用copy()函數。顧名思義,也就是把從新建立一個切片,自立山頭唄。

number5 := make([]int, 2)
copy(number5, numbers[:2])

輸出地址以後,就能夠看到二者已經徹底脫離父子關係,想幹嗎就幹嗎去吧。

說到最後,須要看一下切片的數據結構了。 我想看到數據結構,上面那些所謂的坑應該就能看明白了。

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}
src/runtime/slice.go

能夠看到slice,包含一個指針,一個len變量和一個cap變量。當須要獲取length和capacity時,是直接讀取的len和cap變量值,不須要再遍歷一遍,因此獲取長度和容量效率很是高。 而array指向了一塊內存,進行append操做時,若是len == cap,則擴容。若是len < cap,那麼就是array[len+1]操做。由於golang默認都是值傳遞,雖然len已經變成len+1了,但原始的slice的len仍然沒有變。所以golang才建議,用源切片來接受返回值,這樣源切片的len和cap就會同步發生變化。

說實話,這部分腦子裏面清楚,但用文字表述的效果欠佳。因此遇到切片時刻記住,用源切片來接受返回值。若是須要子切片,首要須要考慮,是否是須要用copy來複制生成。

轉載請保留聯繫方式 ztao8607@gmail.com

相關文章
相關標籤/搜索