Giao diện
🧬 Arrays & Slices Internals
Slices là "The Soul of Go". Hiểu cách chúng hoạt động under the hood là điều bắt buộc để viết performant, bug-free code.
📐 Arrays: The Foundation
Arrays trong Go có fixed size và là value type:
go
// Declaration - size là PHẦN của type
var arr1 [5]int // [0, 0, 0, 0, 0]
arr2 := [3]string{"a", "b", "c"}
arr3 := [...]int{1, 2, 3} // Compiler counts: [3]int
// [5]int và [3]int là KHÁC TYPE!
// var x [5]int = [3]int{1,2,3} // ❌ Compile errorValue Semantics: The Copying Problem
🔥 Raizo's Pitfall: Array Copying
Arrays are VALUES, không phải references. Passing array = copying entire array!
go
func modifyArray(arr [1000000]int) {
arr[0] = 999 // Modifies COPY, không phải original!
}
func main() {
bigArr := [1000000]int{}
modifyArray(bigArr) // 💥 Copies 8MB of data!
fmt.Println(bigArr[0]) // 0 - unchanged
}Memory Impact:
┌─────────────────────────────────────────────────────────┐
│ FUNCTION CALL │
├─────────────────────────────────────────────────────────┤
│ Caller Stack: │
│ bigArr [1000000]int → 8,000,000 bytes │
│ │
│ modifyArray Stack: │
│ arr [1000000]int → 8,000,000 bytes (COPY!) │
│ │
│ Total: 16MB just to call one function! │
└─────────────────────────────────────────────────────────┘Đây là lý do chúng ta HIẾM KHI dùng arrays trực tiếp → Use Slices!
🔪 Slices: The Dynamic View
Slice Header Structure
🎓 Professor Tom's Deep Dive: Slice Internals
Một slice không chứa data trực tiếp. Nó là một descriptor (header) trỏ đến underlying array:
go
// runtime/slice.go
type slice struct {
array unsafe.Pointer // Pointer to underlying array
len int // Current length
cap int // Capacity (from ptr to end of array)
}Memory Layout:
┌─────────────────────────────────────────────────────────────┐
│ SLICE HEADER (24 bytes) │
├──────────────┬──────────────┬──────────────────────────────┤
│ array │ len │ cap │
│ *byte │ int │ int │
│ 8 bytes │ 8 bytes │ 8 bytes │
└──────┬───────┴──────────────┴──────────────────────────────┘
│
│ Points to element at index 0
▼
┌──────────────────────────────────────────────────────────┐
│ UNDERLYING ARRAY │
│ ┌────┬────┬────┬────┬────┬────┬────┬────┐ │
│ │ 10 │ 20 │ 30 │ 40 │ 50 │ 0 │ 0 │ 0 │ │
│ └────┴────┴────┴────┴────┴────┴────┴────┘ │
│ ◄──────── len=5 ────────► │
│ ◄──────────────── cap=8 ──────────────► │
└──────────────────────────────────────────────────────────┘Creating Slices
go
// 1. Literal
s1 := []int{1, 2, 3}
// 2. make(type, len, cap)
s2 := make([]int, 5) // len=5, cap=5
s3 := make([]int, 5, 10) // len=5, cap=10
// 3. Slicing an array
arr := [5]int{10, 20, 30, 40, 50}
s4 := arr[1:4] // []int{20, 30, 40}, len=3, cap=4
// 4. Slicing a slice
s5 := s1[1:] // Shares underlying array with s1!Slicing: Creating Views
go
arr := [8]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:5] // [2, 3, 4], len=3, cap=6
s2 := arr[2:5:5] // [2, 3, 4], len=3, cap=3 (3rd arg = cap limit)Visual:
arr: [ 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 ]
↑ ↑
s1 starts s1 ends
|←─ len=3 ─→|
|←─────── cap=6 ──────→|🔧 The append Mechanic
How append Works
go
s := make([]int, 0, 4) // len=0, cap=4
s = append(s, 1) // len=1, cap=4 - fits
s = append(s, 2) // len=2, cap=4 - fits
s = append(s, 3) // len=3, cap=4 - fits
s = append(s, 4) // len=4, cap=4 - fits
s = append(s, 5) // len=5, cap=8 - REALLOCATE! (doubled)🎓 Professor Tom's Deep Dive: Growth Strategy
Go's slice growth algorithm (Go 1.18+):
- If
cap < 256: Double the capacity - If
cap >= 256: Grow by ~1.25x (actuallycap + (cap + 3*256) / 4)
go
// Approximate growth pattern:
// cap: 0 → 1 → 2 → 4 → 8 → 16 → 32 → 64 → 128 → 256
// Then: 256 → 512 → 768 → 1024 → 1280 → ... (slower growth)Tại sao không luôn double?
- Large slices sẽ waste memory (1GB → 2GB là quá nhiều)
- 1.25x growth giảm memory overhead ở scale lớn
Memory visualization:
Before append (cap full):
┌────┬────┬────┬────┐
│ 1 │ 2 │ 3 │ 4 │ cap=4, len=4
└────┴────┴────┴────┘
↓ append(s, 5)
After reallocation:
┌────┬────┬────┬────┬────┬────┬────┬────┐
│ 1 │ 2 │ 3 │ 4 │ 5 │ │ │ │ cap=8, len=5
└────┴────┴────┴────┴────┴────┴────┴────┘
↑ NEW array, OLD array becomes garbageReallocation Changes Pointer!
🔥 Raizo's Pitfall: Append May Reallocate
Sau reallocation, slice trỏ đến ARRAY MỚI!
go
original := make([]int, 3, 3) // cap=3
original[0], original[1], original[2] = 1, 2, 3
alias := original // Cả hai trỏ đến cùng array
// Append triggers reallocation
original = append(original, 4)
// BÂY GIỜ:
// - original trỏ đến array MỚI [1,2,3,4]
// - alias vẫn trỏ đến array CŨ [1,2,3]
original[0] = 999
fmt.Println(alias[0]) // 1 (not 999!) - Different arrays!Rule: Sau append, luôn dùng slice được return!
go
s = append(s, value) // ✅ Always reassign
append(s, value) // ❌ Result ignored🚨 Memory Leak Trap
The Problem: Large Backing Array
🔥 Critical: Memory Leak via Slicing
go
func getHeader(data []byte) []byte {
return data[:100] // Chỉ cần 100 bytes đầu
}
func main() {
hugeFile, _ := os.ReadFile("10gb_file.log") // 10GB
header := getHeader(hugeFile)
// header chỉ có len=100, nhưng...
// Underlying array vẫn là 10GB!
// GC KHÔNG THỂ thu hồi 10GB vì header vẫn reference nó!
// Giữ header trong memory = giữ 10GB
cache[key] = header // 💥 Memory leak!
}Memory Layout:
hugeFile slice header → [ 10GB array .......................... ]
↑
header slice header ─────┘
(len=100, cap=10GB)
GC sees: "header references 10GB array, cannot free"The Fix: Use copy
go
func getHeaderSafe(data []byte) []byte {
header := make([]byte, 100) // Allocate new, small array
copy(header, data[:100]) // Copy only needed data
return header
}
func main() {
hugeFile, _ := os.ReadFile("10gb_file.log")
header := getHeaderSafe(hugeFile)
// header có underlying array riêng, chỉ 100 bytes
// hugeFile có thể được GC sau khi hết scope
hugeFile = nil // Hint cho GC
cache[key] = header // ✅ Only 100 bytes cached
}🧩 Slice Operations Cheatsheet
Delete Element (Maintain Order)
go
// Delete element at index i, maintaining order
func deleteOrdered(s []int, i int) []int {
return append(s[:i], s[i+1:]...)
}
// Complexity: O(n) - phải shift tất cả elements sau iVisual (delete index 2):
Before: [ A | B | C | D | E ]
↑ delete this
Step 1: s[:2] = [ A | B ]
Step 2: s[3:] = [ D | E ]
Step 3: append(s[:2], s[3:]...) = [ A | B | D | E ]Delete Element (No Order - Fast)
go
// Delete without maintaining order - O(1)
func deleteUnordered(s []int, i int) []int {
s[i] = s[len(s)-1] // Move last element to deleted position
return s[:len(s)-1] // Shrink slice
}Insert Element
go
// Insert value at index i
func insert(s []int, i int, val int) []int {
s = append(s, 0) // Extend slice
copy(s[i+1:], s[i:]) // Shift elements right
s[i] = val // Insert value
return s
}🧠 Parsons Problem: Slice Deletion Logic
🧩 Coding Challenge: Đúng thứ tự các bước
Task: Xóa phần tử tại index i từ slice s và giữ nguyên thứ tự các phần tử còn lại.
Sắp xếp các bước sau theo đúng thứ tự:
📝 Các bước (chưa sắp xếp)
- A.
return append(s[:i], s[i+1:]...) - B. Kiểm tra
i >= 0 && i < len(s)để đảm bảo index hợp lệ - C. Lấy phần slice trước index i:
s[:i] - D. Lấy phần slice sau index i:
s[i+1:] - E. Nối hai phần lại với nhau bằng
append
✅ Đáp án đúng
Thứ tự đúng: B → C → D → E → A
go
func deleteAt(s []int, i int) []int {
// B: Validate index
if i < 0 || i >= len(s) {
return s
}
// C + D + E: Combine parts, skipping index i
// A: Return the result
return append(s[:i], s[i+1:]...)
}Giải thích:
- Validate first - Tránh panic nếu index ngoài range
- s[:i] - Tất cả elements TRƯỚC index i
- s[i+1:] - Tất cả elements SAU index i (skip i)
- append - Nối lại, element tại i bị bỏ qua
- return - Trả về slice mới (có thể cùng backing array)
⚡ Performance: Slice vs container/list
go
// Benchmark: Append 1 million elements
// Native Slice: ~15ms, contiguous memory, cache-friendly
// container/list: ~150ms, scattered nodes, cache misses
// Rule: Dùng slice cho hầu hết cases
// Chỉ dùng list khi cần frequent insertions ở giữa| Operation | Slice | container/list |
|---|---|---|
| Append at end | O(1) amortized | O(1) |
| Insert at middle | O(n) | O(1)* |
| Random access | O(1) | O(n) |
| Memory locality | Excellent | Poor |
| GC pressure | Low | High |
*Nếu đã có pointer đến node
💝 Support HPN Development
Mua Raizo một ly cà phê?
Mỗi module như thế này mất 2-3 giờ để research, viết, và test. Nếu bạn thấy content này hữu ích, hãy cân nhắc support để Raizo tiếp tục sản xuất thêm!
👑 Xem Premium Options🚀 HPN Ultimate Starter Kit
Muốn setup VS Code chuẩn để code Go mượt như Raizo?
Bao gồm:
- VS Code settings tối ưu cho Go
- Extensions pack (gopls, delve, etc.)
- Snippets và keybindings
- Debug configurations
📊 Summary
| Concept | Key Point |
|---|---|
| Arrays | Fixed size, value type, copying on pass |
| Slice Header | {Pointer, Length, Capacity} - 24 bytes |
| Slicing | Creates VIEW into backing array |
| append | May reallocate, always reassign |
| Memory Leak | Slice small from large → use copy |
➡️ Tiếp theo
Slices nắm vững rồi! Tiếp theo: Maps Internals - Hash table structure, buckets, và concurrent access.