[翻譯]Go 資料結構

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

關於 Go 的記憶體結構在 Go 記憶體模型 中已經有介紹,但是內容相對簡單,許多細節也一帶而過。Ross Cox 的這篇文章 Go Data Structure 講解得比較系統也很全面的一篇。翻譯至此,希望能對大家有協助。

2009 年的舊文,發現自己當時沒有翻譯完。所以再次做了增補和修改。如果我沒記錯,應該已經有人在 OSC 上發表過同一篇文章的翻譯了。大家對照參考閱讀吧。

————翻譯分隔線————

Go 資料結構

每當給新手介紹 Go 的時候,我發現為了建立起關於哪個操作成本更加高昂的正確觀念,將 Go 如何為其值分配記憶體說明清楚會很有協助。本文介紹了基礎類型、結構體、數組和切片(slice)。

基本類型

先來看看幾個簡單的例子:

變數 i 的類型是 int,在記憶體中表現為一個 32 位的字。(所有圖展示的都為 32 位記憶體結構;在當前的實現裡,在 64 位元的架構中只有指標會變大,int 仍然還是 32 位,不過也可能選擇 64 位元來作為替代實現。)

由於顯式的轉換,變數 j 的類型是 int32。雖然 ij 有相同的記憶體布局,但是它們是不同的類型:賦值 i = j 會產生一個類型錯誤,因此必須顯式的進行轉換:i = int(j)

變數 f 的類型是 float,當前的實現是 32 位的浮點類型。它的記憶體佔用與 int32 一樣,但內部布局不同。

結構體與指標

接下來,變數 bytes 的類型是 [5]byte,一個有 5 位元組的數組。它的記憶體表現就是這 5 個位元組,跟 C 的數組一樣一個個挨著。類似的 primes 是一個有 4 個 int 的數組。

Go,更接近 C 而不是 Java,它為程式員提供了是不是指標的權力。例如,這個類型定義:

type Point struct { X, Y int }

定義了一個叫做 Point 的簡單的結構類型,在記憶體中表現為兩個相鄰的 int

複合文法語句 Point{10, 20}Point 進行了初始化。對一個複合文法進行取地址表示了一個指向剛剛分配並初始化的 Point 的指標。前者在記憶體中是兩個字;後者是一個指向兩個字的記憶體的指標。

結構體中的欄位在記憶體中是一個挨一個的排布的。

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

Rect1,一個有兩個 Point 欄位的結構體,表達成一行有兩個 Point,或者說四個 intRect2,一個有兩個 *Point 欄位的結構體,表達成兩個 *Point。

那些使用過 C 的程式員可能不會對 Point 欄位和 *Point 欄位之間的區別感到驚訝,而哪些僅僅使用過 Java 或 Python(以及其他……)可能對決定使用哪種而感到詫異。通過為程式員提供了基本的記憶體布局控制能力,Go 提供了對一組資料結構的整體大小、分配數量和記憶體訪問模式進行控制的能力。所有都是構建能夠良好啟動並執行系統的關鍵。

字串

有了前面這些鋪墊,我們可以繼續瞭解那些更加有趣的資料類型了。

(灰色箭頭表示存在於實現中,但是無法在程式中直接看到的指標。)

一個 string 在記憶體中表現為雙字結構體,包含指向字串資料的指標和其長度。由於 string 是不可變的,因此多個字串共用同一儲存空間是安全的。那麼如果對 s 進行切,片使其成為一個新的雙字結構體,會在內部產生另一個指標和長度,但仍然指向相同的位元組序列。這意味著切片可以在不進行任何分配和複製的情況下完成,因此切片同指定序號輪尋字串同樣有效率。

(從另一方面來說,在 Java 和其他語言中將字串切片到更小的片段時,有一個眾所周知的問題,即便是只有一個小片段被使用的情況下,原始的引用都將在記憶體中保留整個原始字串。Go 也有同樣的問題。我們已經嘗試但拒絕了一個使用分配和複製的替代方案,這個方案會讓字串切片的成本更加高昂,大多數程式都希望避免這一情況。)

slice

一個 slice 是指向一個數組的某個片段的引用。在記憶體中,它是一個三字結構體,包含了指向首元素的指標、slice 的長度和容量。長度是類似 x[i] 這樣的索引操作的上限,而容量是 x[i:j] 這樣的切片操作的上限。

與對字串切片一樣,對數組切片也不會產生複製:它僅僅建立一個新的用於儲存不同的指標、長度和容量的結構體。在這個例子中,複合文法 []int{2, 3, 5, 7, 11} 建立了一個包含有五個值的新數組,然後設定了 slice x 的欄位來描述這個數組。slice 運算式 x[1:3] 沒有分配任何資料:它只是填充了一個指向相同底層儲存的新的 slice 結構體。在例子中,長度為 2,y[0]y[1] 是唯一合法的序號;而容量是 4,y[0:4] 是一個合法的 slice 運算式。(參閱 Effective Go 瞭解更多關於 slice 長度和容量,以及如何使用的內容。)

由於 slice 是一個多字結構體,在沒有指標的情況下,切片操作不需要分配記憶體,甚至是 slice 頭也不需要,它通常儲存在棧上。這使得 slice 的使用與在 C 中傳遞指定的指標和長度的成本一樣低廉。Go 最初將 slice 作為一個指向上面展示的結構體的指標,但是這樣的話意味著每一個切片操作都會分配新的記憶體對象。即便使用快速分配也為記憶體回收行程產生了許多額外的工作。我們發現了這一情況,就像前面在字串部分已經提及的,這種情況下程式可能會避免切片操作而使用輪尋。移除了這些間接量與記憶體配置,使得 slice 的成本已經足夠低廉,在大多數情況下都不需要輪尋了。

new 和 make

Go 有兩個資料結構建立函數:newmake。它們的區別最初可能引起混淆,不過很快就會感到正常。最基本的區別是 new(T) 返回一個 *T,一個 Go 程式可以隱式拋棄的指標(圖中黑色箭頭),但 make(T, args) 返回一個原始的 T 而不是指標。通常 T 有其內部隱式實現的指標(圖中灰色的箭頭)。new 返回一個指向空值填充的記憶體,而 make 返回一個複雜的結構體。

有一種辦法可以將這兩種情況統一起來,不過可能會顛覆從 C 和 C++ 而來的傳統:定義 make(*T) 來返回一個指向新分配的 T 的記憶體,那麼當前 new(Point) 可以寫為 make(*Point)。我們對此嘗試了幾天,但是覺得這與人們通常希望的記憶體配置函數實在大相徑庭。

即將來臨

這已經夠長了。介面值、map 和 channel 將只能等待以後的文章了。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.