Slices
Array: รากฐาน
หัวข้อที่มีชื่อว่า “Array: รากฐาน”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: มุมมองแบบ dynamic
หัวข้อที่มีชื่อว่า “Slice: มุมมองแบบ dynamic”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 len— จำนวน element ที่เข้าถึงได้ผ่าน slice ในขณะนี้cap— จำนวน element จากจุดเริ่มต้นของ slice ไปจนถึงท้ายของ backing array
สร้าง slice ด้วย make, composite literal, หรือการ slice array:
s := make([]int, 3, 5) // len=3, cap=5, ทุกค่าเป็น zerot := []int{1, 2, 3} // len=3, cap=3u := arr[1:3] // len=2, cap=2, หน้าต่างเข้าสู่ arrappend และการเติบโต
หัวข้อที่มีชื่อว่า “append และการเติบโต”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, ...)
Slicing expression และ backing array ที่แชร์
หัวข้อที่มีชื่อว่า “Slicing expression และ backing array ที่แชร์”a[low:high] สร้าง slice header ใหม่ที่แชร์ backing array เดียวกัน:
base := []int{1, 2, 3, 4, 5}a := base[1:3] // [2 3] — แชร์ backing array กับ baseb := base[1:4] // [2 3 4] — แชร์ด้วยเช่นกันa[0] = 99fmt.Println(b[0]) // 99 — การแก้ไขมองเห็นได้ผ่าน b ด้วยนี่คือพลัง (sub-slicing แบบ zero-copy) แต่อันตรายถ้าลืมเรื่องนี้
copy เพื่อ slice ที่เป็นอิสระ
หัวข้อที่มีชื่อว่า “copy เพื่อ slice ที่เป็นอิสระ”ใช้ 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] = 0fmt.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}Loading Go runtime (first run only, ~8 MB)…
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”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 subview | backing 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 คือขนาดสูงสุดก่อน reallocationcopy(dst, src)ทำ deep copy — copy ถ่ายโอน values แต่ถ้า element เป็น pointer หรือ interface ยัง share underlying data เดิม
💡 ตัวอย่างจากของจริง
Kubernetes ใช้
make([]Pod, 0, capacity)เพื่อ pre-allocate pod list และลด GC pressure ใน reconcile loopPrometheus เก็บ metric sample ใน slice พร้อม pre-allocated capacity — ช่วยลด reallocation ในระหว่าง high-frequency scrape