Skip to content

📡 I/O Abstractions & Reflection

"Everything is a file" — Unix philosophy.
Trong Go, "Everything is an io.Reader". Master interface này để build generic, memory-efficient systems.

🌊 The io.Reader Universe

The Most Important Interface in Go

go
// io package - chỉ 2 methods core
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

🎓 Professor Tom's Deep Dive: Why io.Reader is King

Mọi thứ implement io.Reader:

SourceTypeExample
Files*os.Fileos.Open("data.txt")
Networknet.ConnTCP/UDP connections
HTTP*http.Response.BodyAPI responses
Memory*bytes.BufferIn-memory data
Compression*gzip.ReaderWrapped reader
Strings*strings.Readerstrings.NewReader("hello")

Power: Viết code 1 lần, work với tất cả sources!

Composability Pattern

go
// Function xử lý BẤT KỲ io.Reader nào
func ProcessData(r io.Reader) error {
    scanner := bufio.NewScanner(r)
    for scanner.Scan() {
        line := scanner.Text()
        // Process line...
    }
    return scanner.Err()
}

// Gọi với file
file, _ := os.Open("data.txt")
defer file.Close()
ProcessData(file)

// Gọi với HTTP response
resp, _ := http.Get("https://api.example.com/data")
defer resp.Body.Close()
ProcessData(resp.Body)

// Gọi với string (testing!)
ProcessData(strings.NewReader("line1\nline2\nline3"))

// Gọi với gzip-compressed data
gzReader, _ := gzip.NewReader(file)
ProcessData(gzReader)
)

🚀 Stream Processing: Memory Efficiency

The Problem: Loading Everything into RAM

🔥 Raizo's Pitfall: Memory Explosion

go
// ❌ BAD: Đọc toàn bộ file vào RAM
func ProcessFileBad(path string) error {
    data, err := os.ReadFile(path)  // 10GB file = 10GB RAM!
    if err != nil {
        return err
    }
    
    lines := strings.Split(string(data), "\n")
    for _, line := range lines {
        process(line)  // Cũng copy thêm memory
    }
    return nil
}

Với file 10GB:

  • RAM usage: 10GB+ (file) + strings (copy)
  • GC pressure: Massive
  • Startup time: Must wait for full read

The Solution: Streaming

go
// ✅ GOOD: Stream processing - constant memory
func ProcessFileStream(path string) error {
    file, err := os.Open(path)
    if err != nil {
        return err
    }
    defer file.Close()
    
    scanner := bufio.NewScanner(file)
    // Optional: increase buffer for long lines
    scanner.Buffer(make([]byte, 64*1024), 1024*1024)
    
    for scanner.Scan() {
        line := scanner.Text()
        process(line)  // Process one line at a time
    }
    return scanner.Err()
}

Memory comparison:

┌─────────────────────────────────────────────────────────────────────┐
│               MEMORY USAGE: 10GB FILE                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   ReadFile (Bad):                                                   │
│   ┌────────────────────────────────────────────────────────────┐   │
│   │ ████████████████████████████████████████████ 10GB+ RAM     │   │
│   └────────────────────────────────────────────────────────────┘   │
│                                                                     │
│   Stream (Good):                                                    │
│   ┌────┐                                                            │
│   │ ██ │ ~64KB buffer (constant!)                                  │
│   └────┘                                                            │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

io.Copy: The Streaming Hero

go
// Copy data without loading into memory
func DownloadFile(url, filepath string) error {
    resp, err := http.Get(url)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    
    file, err := os.Create(filepath)
    if err != nil {
        return err
    }
    defer file.Close()
    
    // Streams data directly: Response → File
    // Never loads entire response into RAM!
    written, err := io.Copy(file, resp.Body)
    if err != nil {
        return err
    }
    
    log.Printf("Downloaded %d bytes", written)
    return nil
}

Reader Chaining (Composition)

go
// Stack readers for complex processing
func ProcessCompressedEncrypted(path string) error {
    file, _ := os.Open(path)
    defer file.Close()
    
    // Chain: File → Decrypt → Decompress → Process
    decrypted := crypto.NewReader(file, key)
    decompressed, _ := gzip.NewReader(decrypted)
    
    // Final reader is fully processed stream
    scanner := bufio.NewScanner(decompressed)
    for scanner.Scan() {
        // Each line is decrypted + decompressed on-the-fly
    }
    return nil
}

🔍 Reflection: Power with Responsibility

What is Reflection?

🎓 Under the Hood: reflect Package

Reflection cho phép inspect và manipulate types tại runtime:

go
import "reflect"

func InspectType(v interface{}) {
    t := reflect.TypeOf(v)
    v := reflect.ValueOf(v)
    
    fmt.Printf("Type: %s\n", t.Name())
    fmt.Printf("Kind: %s\n", t.Kind())
    fmt.Printf("Value: %v\n", v)
}

type User struct {
    ID   int64  `json:"id"`
    Name string `json:"name"`
}

InspectType(User{ID: 1, Name: "Raizo"})
// Type: User
// Kind: struct
// Value: {1 Raizo}

Common Use Cases

go
// 1. Reading struct tags (JSON, DB, validation)
func GetJSONFieldNames(v interface{}) []string {
    t := reflect.TypeOf(v)
    var names []string
    
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        jsonTag := field.Tag.Get("json")
        if jsonTag != "" && jsonTag != "-" {
            names = append(names, jsonTag)
        }
    }
    return names
}

// 2. Generic struct comparison
func StructsEqual(a, b interface{}) bool {
    return reflect.DeepEqual(a, b)
}

// 3. Dynamic function calling
func CallMethod(obj interface{}, methodName string, args ...interface{}) {
    v := reflect.ValueOf(obj)
    method := v.MethodByName(methodName)
    
    if method.IsValid() {
        in := make([]reflect.Value, len(args))
        for i, arg := range args {
            in[i] = reflect.ValueOf(arg)
        }
        method.Call(in)
    }
}

Reflection Performance Warning

🔥 Raizo's Critical Warning: Reflection is SLOW

Reflection có overhead đáng kể:

go
// Benchmark results:
// BenchmarkDirectAccess-8    500000000    3.2 ns/op
// BenchmarkReflection-8       5000000   285.0 ns/op
// → Reflection ~90x slower!

// ❌ BAD: Don't use reflection in hot paths
func ProcessOrdersBad(orders []Order) {
    for _, order := range orders {
        v := reflect.ValueOf(order)
        total := v.FieldByName("Total").Float()  // Slow!
        // ...
    }
}

// ✅ GOOD: Direct access
func ProcessOrdersGood(orders []Order) {
    for _, order := range orders {
        total := order.Total  // Fast!
        // ...
    }
}

When to Use Reflection

Use CaseReflection OK?Alternative
JSON/YAML marshaling✅ (library)
ORM field mapping✅ (library)
Validation frameworks✅ (library)
Hot path processingDirect access
Type checkingType switch
Generic containersGenerics (Go 1.18+)

🏗️ Building Generic Systems

Example: Universal Encoder

go
// Interface-based design (preferred)
type Encoder interface {
    Encode(v interface{}) error
}

type JSONEncoder struct {
    w io.Writer
}

func (e *JSONEncoder) Encode(v interface{}) error {
    return json.NewEncoder(e.w).Encode(v)
}

type XMLEncoder struct {
    w io.Writer
}

func (e *XMLEncoder) Encode(v interface{}) error {
    return xml.NewEncoder(e.w).Encode(v)
}

// Usage - swap encoders easily
func Export(data interface{}, format string, w io.Writer) error {
    var encoder Encoder
    
    switch format {
    case "json":
        encoder = &JSONEncoder{w: w}
    case "xml":
        encoder = &XMLEncoder{w: w}
    default:
        return fmt.Errorf("unsupported format: %s", format)
    }
    
    return encoder.Encode(data)
}

Example: Generic Data Pipeline

go
// Pipeline pattern with io.Reader
type Pipeline struct {
    readers []func(io.Reader) io.Reader
}

func (p *Pipeline) AddTransform(t func(io.Reader) io.Reader) {
    p.readers = append(p.readers, t)
}

func (p *Pipeline) Process(r io.Reader, w io.Writer) error {
    current := r
    
    // Chain all transformations
    for _, transform := range p.readers {
        current = transform(current)
    }
    
    _, err := io.Copy(w, current)
    return err
}

// Usage
func main() {
    pipeline := &Pipeline{}
    
    // Add transformations
    pipeline.AddTransform(func(r io.Reader) io.Reader {
        return gzip.NewReader(r)
    })
    pipeline.AddTransform(func(r io.Reader) io.Reader {
        return transform.NewReader(r, japanese.ShiftJIS.NewDecoder())
    })
    
    file, _ := os.Open("data.txt.gz")
    defer file.Close()
    
    pipeline.Process(file, os.Stdout)
}

📊 Summary

ConceptKey Point
io.ReaderUniversal interface for all data sources
StreamingProcess data chunk-by-chunk, constant memory
io.CopyZero-copy data transfer between Reader and Writer
Reader ChainingCompose readers for transformation pipelines
ReflectionPowerful but slow — use for frameworks, not hot paths
GenericsPrefer over reflection when possible (Go 1.18+)

📌 HPN Standard: I/O Decision Tree

Need to read data?
├── Size known & small (< 10MB)?
│   └── ReadFile/ReadAll OK
└── Size unknown or large?
    └── Use streaming (Scanner, io.Copy)

Need generic type handling?
├── Limited set of types?
│   └── Type switch
├── Go 1.18+?
│   └── Use Generics
└── Must work with arbitrary types?
    └── Reflection (last resort)

➡️ Tiếp theo

I/O và Reflection nắm vững rồi! Tiếp theo: Maps Internals - Hash table structure, buckets, và concurrent access patterns.