ข้ามไปยังเนื้อหา

Slices

Array ใน Go มีขนาดคงที่ที่เป็นส่วนหนึ่งของ type [3]int และ [4]int คือ type คนละตัวกัน:

arr := [3]int{10, 20, 30}
fmt.Println(len(arr)) // 3 — เสมอ

Array เป็น value type: การ assign a2 := a1 จะ copy ทุก element มีประโยชน์สำหรับข้อมูลขนาดคงที่ แต่ยืดหยุ่นน้อยเกินไปสำหรับงานส่วนใหญ่

Slice คือ header ขนาดเบา — สาม word — ที่อธิบาย “หน้าต่าง” เข้าไปยัง array ที่อยู่ด้านล่าง:

flowchart LR
  subgraph Header["slice header"]
    P["pointer"]
    L["len = 3"]
    C["cap = 5"]
  end
  subgraph Backing["backing array"]
    A0["10"] --> A1["20"] --> A2["30"] --> A3["40"] --> A4["50"]
  end
  P --> A0
A slice header points into a backing array; len=3 is visible, cap=5 reaches the end
  • len — จำนวน element ที่เข้าถึงได้ผ่าน slice ในขณะนี้
  • cap — จำนวน element จากจุดเริ่มต้นของ slice ไปจนถึงท้ายของ backing array

สร้าง slice ด้วย make, composite literal, หรือการ slice array:

s := make([]int, 3, 5) // len=3, cap=5, ทุกค่าเป็น zero
t := []int{1, 2, 3} // len=3, cap=3
u := arr[1:3] // len=2, cap=2, หน้าต่างเข้าสู่ arr

append เพิ่ม element ให้ slice และคืนค่า slice (ที่อาจเป็นตัวใหม่):

s = append(s, 4, 5, 6)

เมื่อ len == cap, append จะ allocate backing array ใหม่ที่ใหญ่กว่า copy element ที่มีอยู่ แล้วคืน slice ที่ชี้ไป array ใหม่ slice เดิมไม่ถูกกระทบ ควร reassign ผลลัพธ์ของ append เสมอs = append(s, ...)

a[low:high] สร้าง slice header ใหม่ที่แชร์ backing array เดียวกัน:

base := []int{1, 2, 3, 4, 5}
a := base[1:3] // [2 3] — แชร์ backing array กับ base
b := base[1:4] // [2 3 4] — แชร์ด้วยเช่นกัน
a[0] = 99
fmt.Println(b[0]) // 99 — การแก้ไขมองเห็นได้ผ่าน b ด้วย

นี่คือพลัง (sub-slicing แบบ zero-copy) แต่อันตรายถ้าลืมเรื่องนี้

ใช้ built-in copy(dst, src) เพื่อตัดการแชร์ โดย copy min(len(dst), len(src)) element และคืนจำนวนที่ copy:

fresh := []int{10, 20, 30}
dst := make([]int, 3)
copy(dst, fresh)
dst[0] = 0
fmt.Println(fresh[0], dst[0]) // 10 0 — เป็นอิสระต่อกัน
package main
import "fmt"
func main() {
// Array vs slice
arr := [3]int{10, 20, 30} // fixed-size array
sl := arr[:] // slice backed by arr
sl[0] = 99
fmt.Println(arr[0], sl[0]) // 99 99 — shared backing array
// make: len and cap
s := make([]int, 3, 5)
fmt.Println(len(s), cap(s)) // 3 5
// append beyond cap allocates a new backing array
s = append(s, 1, 2, 3)
fmt.Println(len(s), cap(s)) // 6 10
// Two slices sharing the same backing array
base := []int{1, 2, 3, 4, 5}
a := base[1:3] // [2 3]
b := base[1:4] // [2 3 4]
a[0] = 99
fmt.Println(b[0]) // 99 — shared backing array
// copy to get an independent slice
fresh := []int{10, 20, 30}
dst := make([]int, 3)
copy(dst, fresh)
dst[0] = 0
fmt.Println(fresh[0], dst[0]) // 10 0 — independent after copy
}
slice header (3 words, 24 bytes on 64-bit)
├─ pointer ──────────────→ [0][1][2][3][4] ← backing array (cap=5)
├─ len: 3 ↑
└─ cap: 5 visible range (len=3)
append ภายใน cap:
s = append(s, 4) → len: 4, cap: 5 (ไม่มี allocation ใหม่)
append เกิน cap:
s = append(s, 5, 6) → Go allocates new backing array (~2x cap)
backing array เดิมยัง live ถ้ามี slice อื่นชี้อยู่
สิ่งที่ได้ประโยชน์ต้นทุน
slice header เป็น value (24 bytes)pass to function ถูกmutation ผ่าน slice copy กระทบ backing array เดิม
dynamic appendขยายได้อัตโนมัติreallocation เมื่อเกิน cap — O(n) copy
slicing [a:b]zero-copy subviewbacking array ที่แชร์ — อาจ prevent GC ของ array ใหญ่
make([]T, len, cap)pre-allocate หลีกเลี่ยง reallocต้องประมาณ cap ล่วงหน้า
  • slice และ array คือสิ่งเดียวกัน — slice คือ (pointer, len, cap) header ที่ชี้ไปยัง array; array คือ fixed-size value type
  • append ปลอดภัยเสมอและ mutate in-place — append อาจ return slice ที่ชี้ backing array ใหม่ทั้งหมด — ต้อง assign ผลลัพธ์กลับเสมอ
  • len และ cap เหมือนกัน — len คือจำนวน element ที่เข้าถึงได้ตอนนี้, cap คือขนาดสูงสุดก่อน reallocation
  • copy(dst, src) ทำ deep copy — copy ถ่ายโอน values แต่ถ้า element เป็น pointer หรือ interface ยัง share underlying data เดิม

💡 ตัวอย่างจากของจริง

Kubernetes ใช้ make([]Pod, 0, capacity) เพื่อ pre-allocate pod list และลด GC pressure ใน reconcile loop

Prometheus เก็บ metric sample ใน slice พร้อม pre-allocated capacity — ช่วยลด reallocation ในระหว่าง high-frequency scrape

คุณเขียน `s = append(s, 1)` เมื่อ `len(s) == cap(s)` Go ทำอะไร?
คุณมี `base := []int{1, 2, 3}` และ `a := base[0:2]` แล้วกำหนด `a[0] = 99` ค่าของ `base[0]` คืออะไร?
`make([]string, 4, 10)` สร้างอะไร?
วิธี idiomatic ที่ทำให้ slice สองตัวไม่แชร์ backing array คืออะไร?