1. 程式人生 > >深度剖析Go語言資料結構

深度剖析Go語言資料結構

轉載地址 : http://www.open-open.com/lib/view/open1390373069882.html

當向一個新程式設計師解釋Go語言時,我發現如果解釋Go的資料是如何在記憶體中表示的,將有助於建立編寫高效程式的良好直覺。

基礎型別

讓我們從一些簡單的例子開始:
在這裡插入圖片描述

深度剖析Go語言資料結構

變數i是int型別,在記憶體中佔用一個32位的儲存單位。(上圖拿32位系統來舉例;對以上的例子,只有指標才會在64位的機器上佔用更多的空間——int始終是32位——然而我們仍然可以選擇64位的系統。)

變數j是int32型別,因為它經過了顯式的型別轉化。儘管i和j有著同樣的記憶體佈局,但它們的型別是不一樣的:像這樣的賦值i = j會產生型別異常,必須通過顯式的型別轉換:i = int(j) 。

變數f是個浮點型別,上例中它代表著佔用32位的浮點值。它的記憶體佔用跟int32一樣,但內部佈局不同。

結構與指標

變數bytes是[5]byte型別,一個具有5個位元組的陣列。它的記憶體表示就只有5個緊挨著的位元組,就像C裡的陣列一樣。相似地,primes變數是一個擁有4個int數值的陣列。

Go就像C而不像Java,它讓程式設計師決定什麼是或者不是一個指標。拿這個型別定義來舉例:

1 type Point struct { X , Y int }
定義一個叫Point的簡單的結構型別,意味著記憶體裡是兩個相鄰的int。
在這裡插入圖片描述

深度剖析Go語言資料結構

Point{ 10, 20 }這句複合語法表示一個被初始化的Point物件。而&Point{ 10, 20 }這句則表示一個指向被初始化的Point物件的指標。前者在記憶體中有兩個資料塊,而後者則存放著一個指向兩個資料塊的指標。

結構中的欄位被依次地排列在記憶體裡面。

1	type Rect1 struct { Min, Max Point }
2	type Rect2 struct { Min, Max *Point }

在這裡插入圖片描述

Rect1是一個擁有兩個Point型別欄位的結構,它的一條記錄包含了兩條Point記錄——共4個int。Rect2是一個擁有兩個Point型別指標的結構,在記憶體裡它佔兩個Point指標的空間。

用過C的程式設計師也許對Point欄位和*Point欄位的區別並不陌生,而只用過Java或者Python(或者其他)則可能為需要做出選擇而驚訝。通過 為程式設計師提供控制基礎記憶體佈局的可能,Go語言讓程式設計師可以操控所有資料結構總尺寸、所分配變數的總數和記憶體訪問的模式,這些對於建造高效能系統都至關重 要。

字串

接下來我們繼續看一些更有趣的資料型別。

在這裡插入圖片描述
(灰色箭頭意味著實現上的真實表示方式,但這在程式設計過程中是不可見的)

一個字串在記憶體中的表示被分成兩段,一個指向字串資料的指標和一個長度值。因為字串是可列舉的,所以多個字串共享同一段儲存空間也是安全的,因此 如果對s字串進行一個切片選擇,將得到一個可能不一樣的指標和長度,但它們也指向同一段位元組序列。這意味著,切片並不需要分配空間或者是複製資料,建立 切片很容易,只需要傳遞明確的下標值就行了。

(順帶一句,在Java和某些與嚴重有一個著名的缺陷,當你對一個字串進行切片並儲存其中的一小部分,引用將在記憶體中儲存原字串的完整內容,即使只有 很小的一部分是被用到的。Go也有這個缺陷。要不然(我們嘗試但最終捨棄了),我們將對切片採取昂貴的做法——分配記憶體並拷貝資料——大部分語言都避免這 種做法。

切片
在這裡插入圖片描述

切片是對陣列中一段資料的引用。在記憶體中它有三段資料組成:一個指向資料頭的指標、切片的長度、切片的容量。長度是索引操作的上界,如:x[i] 。容量是切片操作的上界,如:x[i:j] 。

跟對字串做切片一樣,對陣列進行切片也不會導致複製:它只建立一個存放指標、長度和容量的結構體。在這個例子中,語句[ ] int { 2, 3, 5, 7, 11 } 建立了一個包含5個值的新陣列,併為x切片設定了對應的值來描述那個陣列。切片表示式x[1:3]並不為資料分配記憶體:它只填充切片結構的欄位,用以複用 陣列的儲存空間。在這個例子中,長度是2,y[0]和y[1]是僅有的合法資料;但容量是4,y[0:4]是個合法的切片表示式。(檢視高效GO獲取更多關於長度、容量,以及如何使用切片的資訊。)

由於切片不是指標而是多欄位的結構,切片操作並不需要分配記憶體,即使對於切片頭也是這樣,它可以常駐在棧中。這種表示法讓切片的使用的代價很低,就像C中 傳遞精確的指標和長度一樣。Go原生地在切片中使用了指標,這也意味著每個切片操作都分配一個記憶體物件。即使有了一個更快的記憶體分配器,這為垃圾回收帶來 了不必要的工作,並且我們發現,就像字串那個例子一樣,給於精確的下標,比進行切片操作好。大多數情況下,避免不必要的間接引用和記憶體分配可以讓切片足 夠高效了。

new和make

Go有兩種建立資料結構的方法:new和make 。它們的區別是常見的早期困惑,但很快就會變得自然。基礎的區別在於,new(T)返回一個*T型別,一個可以被隱性反向引用的指標(如圖中的黑色指 針),而make(T,args)返回一個原始的T,它並不是一個指標。T中常有寫隱性的指標(如圖中的灰色指標)。new返回一個指向初始化為全0值的 指標,而make返回一個複雜的結構。
在這裡插入圖片描述

有一種方式讓兩者統一起來,它對於傳統的C和C++是一個重大的改變:定義make(*T)來返回一個指向新分配的T的指標,因此new(Point)和 make(*Point)的效果是一致的。我們用這種方法嘗試了一段日子,但最終覺得這對於一些期待一個分配函式的人來說,實在太難以接受了。

中文原文地址:http://www.zingscript.com/post/195

英文原文地址:http://research.swtch.com/godata