Go | 結構的記憶體佈局解析
💬 簡介
在 Go 語言中,結構是用來組織資料的核心型別。不同於 Java 中的引用型別,Go 語言的結構資料在記憶體中是以連續區塊的形式存在的,這種記憶體佈局方式對效能有顯著的優勢。本文將深入解析結構的記憶體佈局,並介紹遞迴結構的應用,包括連結串列和二叉樹。
圖片來源:Gophers
🔍 結構的記憶體佈局
在 Go 語言中,結構及其成員資料是按順序連續儲存在記憶體中的。這意味著,即使結構內嵌了其他結構,這些結構的資料也會緊密排列在一起,這使得資料的存取更加高效。
相比之下,像 Java 中的物件,內部的屬性可能會分散在不同的記憶體位置,這樣的方式需要額外的間接引用,可能會降低效能。而 Go 的結構通過直接將資料以連續區塊的方式儲存,減少了這些開銷。
1️⃣ 使用 new
初始化的記憶體佈局
我們使用 new(Point)
創建了 Point
結構的指標 pPointer
。使用 new()
時,Go 會為 Point
分配記憶體並將 x
和 y
的欄位初始化為零值(對於 int
來說,零值是 0
)。但與直接創建結構實例不同的是,new()
會返回一個指向這個結構的指標,而不是直接儲存資料。
- 範例
1
2
3
4
5
6
7
8type Point struct {
x, y int
}
func main() {
pPointer := new(Point)
// pPointer 會儲存結構資料的指標,並且欄位預設為零值
}
圖片來源:Go入門指南。
2️⃣ 使用結構實例初始化的記憶體佈局
📌 結構實例初始化的記憶體佈局
這個範例中,p
是 Poin
t 結構的實例,並且它直接儲存在記憶體中的一個連續區塊中。這樣,x
和 y
會緊密排列並儲存於一個記憶體區塊
- 範例
1
2
3
4
5
6
7type Point struct {
x, y int
}
func main() {
p := Point{x: 10, y: 20}
}
📌 指標結構初始化的記憶體佈局
在這部分,當我們創建一個指向結構的指標時,指標會儲存在一個區塊,而實際的結構資料會儲存在另一個區塊。這與直接創建結構實例不同,因為指標是指向結構資料的位址。
- 範例
1
2
3
4
5
6
7type Point struct {
x, y int
}
func main() {
p := &Point{x: 10, y: 20}
}
📝 當使用指標時,
p
儲存的是指向結構資料的指標,它指向記憶體中的一個區塊,其中x
和y
的值會被儲存。在這種情況下,p
的儲存空間僅佔用指標的大小(假設為 8 字節),而結構資料會儲存在另一塊區域中。
圖片來源:Go入門指南。
3️⃣ 多層結構的存儲方式
📌 結構實例初始化的記憶體佈局
- 範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package main
import "fmt"
type Point struct {
X int
Y int
}
type Rect1 struct {
Min, Max Point
}
func main() {
// 創建一個 Rect1 實例,並初始化 Min 和 Max
r := Rect1{
Min: Point{X: 10, Y: 20},
Max: Point{X: 50, Y: 60},
}
fmt.Println("Rectangle Min:", r.Min)
fmt.Println("Rectangle Max:", r.Max)
}📝 這個範例中,
Rect1
結構包含了兩個Point
結構。在記憶體中,Min
和Max
會被直接存儲在Rect1
的連續記憶體區塊中。
📌 指標結構初始化的記憶體佈局
- 範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35package main
import "fmt"
type Point struct {
X int
Y int
}
type Rect2 struct {
Min, Max *Point
}
func main() {
// 使用 new() 創建 Point 的指標
minPoint := new(Point)
maxPoint := new(Point)
// 初始化這些指標指向的 Point 資料
minPoint.X = 0
minPoint.Y = 0
maxPoint.X = 10
maxPoint.Y = 20
// 創建 Rect2 實例,並將 Min 和 Max 設為指向相應的 Point 指標
r := Rect2{
Min: minPoint,
Max: maxPoint,
}
fmt.Println("Rectangle Min:", r.Min) // 顯示指標的地址
fmt.Println("Rectangle Max:", r.Max) // 顯示指標的地址
fmt.Println("Min X:", r.Min.X, "Min Y:", r.Min.Y) // 顯示指標指向的資料
fmt.Println("Max X:", r.Max.X, "Max Y:", r.Max.Y) // 顯示指標指向的資料
}📝 這個範例中,
Min
和Max
是Point
型別的指標,因此在記憶體中,Rect2
會儲存指標的值,而不是直接儲存Point
結構本身。
圖片來源:Go入門指南。
4️⃣ 遞迴結構與記憶體佈局
Go 語言中的結構也可以引用自身,這讓我們能夠創建遞迴結構。這在實現如連結串列和二叉樹等資料結構時尤其有用。在這些資料結構中,每個節點通常會包含指向其他節點的指標,這些指標形成了資料元素之間的鏈接。
範例:連結串列
連結串列是由一系列節點組成,每個節點指向下一個節點。這是透過在結構中使用指標來實現的。1
2
3
4type Node struct {
data float64
su *Node
}📝 這個範例中,
Node
結構包含一個data
欄位存儲資料,並且有一個指標su
指向下一個Node
節點。這種方式讓我們可以動態地創建和操作結構,並且能夠節省記憶體,因為每個節點只需要存儲一個指標。
圖片來源:Go入門指南。範例:雙向連結串列
雙向連結串列比單向連結串列多了一個指標,指向前一個節點。1
2
3
4
5type Node struct {
pr *Node
data float64
su *Node
}📝 這個範例中,
Node
結構包含了兩個指標:pr
用於指向前一個節點,su
用於指向下一個節點。這樣,節點就可以在雙向鏈接中向前或向後遍歷。範例:二叉樹
二叉樹是另一種常見的遞迴資料結構,每個節點最多有兩個子節點,分別是左子節點和右子節點。1
2
3
4
5type Tree struct {
le *Tree
data float64
ri *Tree
}📝 這個範例中,
Tree
結構有兩個指標欄位:le
用於指向左子樹,ri
用於指向右子樹。這樣的結構使得每個節點能夠指向其左右的子樹,並且實現樹狀結構。
圖片來源:Go入門指南。
🔄 補充:遞迴結構的應用場景
🔗 鏈結串列(Linked List)
鏈結串列是一種線性資料結構,其中每個節點包含資料和指向下一個節點的指標。這是使用遞迴結構的經典例子。
每個節點是 Node
類型,它包含一個資料欄位和一個指向下個節點的指標,這樣就形成了鏈結結構。
在記憶體中,每個節點包含一個資料區塊和一個指向下一個節點的指標。這樣的結構非常靈活,可以在需要時動態地增刪節點。
- 範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17type Node struct {
Data int
Next *Node
}
func main() {
// 創建一個包含資料的節點
node1 := &Node{Data: 10}
node2 := &Node{Data: 20}
// 連接兩個節點
node1.Next = node2
// 可以通過指標遍歷鏈結串列
fmt.Println("Node1 data:", node1.Data)
fmt.Println("Node2 data:", node1.Next.Data)
}
🌳 二叉樹(Binary Tree)
二叉樹是一種樹狀資料結構,每個節點最多有兩個子節點(左子節點和右子節點)。在 Go 中,這種結構也是使用遞迴結構來實現的。每個樹節點會持有指向左子節點和右子節點的指標。
在記憶體中,每個節點包含一個資料欄位以及兩個指向其他節點(左子節點和右子節點)的指標。
- 範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21type TreeNode struct {
Value int
Left *TreeNode
Right *TreeNode
}
func main() {
// 創建根節點
root := &TreeNode{Value: 10}
// 創建左子樹
root.Left = &TreeNode{Value: 5}
// 創建右子樹
root.Right = &TreeNode{Value: 15}
// 輸出根節點及其子節點的值
fmt.Println("Root:", root.Value)
fmt.Println("Left Child:", root.Left.Value)
fmt.Println("Right Child:", root.Right.Value)
}
🌍 圖(Graph)
圖是一種由節點和邊所構成的資料結構,其中邊連接著圖中的兩個節點。在 Go 中,如果要用結構來表示一個節點,並且節點間是有連結的,通常也會使用遞迴結構。這在處理複雜的圖結構(例如社交網絡、路徑搜尋等)時非常有用。
每個圖節點可能包含對其它節點的指標,並且這些指標可以是遞迴的,從而實現圖的結構。
- 範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18type GraphNode struct {
Value int
Children []*GraphNode
}
func main() {
// 創建節點
node1 := &GraphNode{Value: 1}
node2 := &GraphNode{Value: 2}
node3 := &GraphNode{Value: 3}
// 創建連結
node1.Children = []*GraphNode{node2, node3}
// 輸出節點的值及其子節點
fmt.Println("Node1:", node1.Value)
fmt.Println("Node1's Children:", node1.Children[0].Value, node1.Children[1].Value)
}
🎯 總結
- Go 語言中的結構資料是以連續區塊的形式存在於記憶體中的,這使得它們的效能優於像 Java 中的引用型別。
- 當結構包含指標型別時,它們的記憶體佈局會有所不同,會儲存指標而非直接儲存資料。
- 遞迴結構在實現複雜資料結構(如連結串列和二叉樹)時非常有用,並且能有效地使用記憶體。
最後建議回顧一下 Go | 菜鳥教學 目錄,了解其章節內容。