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

io และ bufio

Package io นิยาม interface ขนาดเล็กแต่เป็นรากฐานสองตัว:

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

Interface ทั้งสองนี้คือภาษากลางของ I/O ใน Go มี type จำนวนมากที่ implement ทั้งสอง: ไฟล์, network connection, HTTP request และ response body, in-memory buffer และอื่นๆ ฟังก์ชันที่รับ io.Reader หรือ io.Writer จะทำงานกับทุก type เหล่านั้นโดยไม่ต้องแก้ไขโค้ด

strings.NewReader(s) ห่อ string เป็น io.Reader มีประโยชน์มากใน test และตัวอย่าง — สามารถทดสอบฟังก์ชันที่อ่านจาก stream ได้โดยไม่ต้องแตะ filesystem

r := strings.NewReader("hello, world")
data, _ := io.ReadAll(r)
fmt.Println(string(data)) // hello, world

bytes.Buffer implement ทั้ง io.Reader และ io.Writer เขียนลงไปแล้วอ่านกลับออกมา:

var buf bytes.Buffer
fmt.Fprintln(&buf, "first line")
fmt.Fprintln(&buf, "second line")
content, _ := io.ReadAll(&buf)
fmt.Print(string(content))

fmt.Fprintln รับ io.Writer ใดก็ได้ ดังนั้นจึงเปลี่ยน output ที่ฟอร์แมตแล้วไปยัง buffer, ไฟล์ หรือ network connection ได้โดยไม่ต้องเปลี่ยนโค้ด

bufio.Scanner ห่อ io.Reader ใดก็ได้และให้การอ่านทีละบรรทัด (หรือทีละ token) อย่างสะดวก ฟังก์ชัน split แบบ default คือ bufio.ScanLines

scanner := bufio.NewScanner(r)
for scanner.Scan() {
fmt.Println(scanner.Text()) // ทีละบรรทัด
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}

scanner.Scan() คืน false เมื่อ input หมดหรือเกิด error ควรตรวจสอบ scanner.Err() หลังลูปเสมอ — การคืน false เพียงอย่างเดียวไม่ได้บอกว่าเป็น EOF หรือ error

Package io ยังมี helper ที่มีประโยชน์:

io.ReadAll(r) // อ่านจนถึง EOF คืน []byte
io.Copy(dst, src) // copy จาก Reader ไปยัง Writer
io.MultiReader(r1, r2) // ต่อ reader หลายตัวตามลำดับ
io.Discard // Writer ที่ทิ้ง byte ทั้งหมด (เหมือน /dev/null)
io.LimitReader(r, n) // อ่านไม่เกิน n bytes
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"strings"
)
// printLines accepts any io.Reader — works with files, buffers, or strings
func printLines(r io.Reader) {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
fmt.Println(">", scanner.Text())
}
}
func main() {
// strings.NewReader implements io.Reader
r := strings.NewReader("first line\nsecond line\nthird line")
printLines(r)
// bytes.Buffer implements both io.Reader and io.Writer
var buf bytes.Buffer
fmt.Fprintln(&buf, "written to buffer")
fmt.Fprintln(&buf, "another line")
content, _ := io.ReadAll(&buf)
fmt.Print(string(content))
// io.MultiReader chains two readers sequentially
r1 := strings.NewReader("hello ")
r2 := strings.NewReader("world\n")
combined := io.MultiReader(r1, r2)
io.Copy(io.Discard, combined)
fmt.Println("MultiReader composed two readers")
// bufio.Scanner over in-memory CSV
csv := "alice,30\nbob,25\ncharlie,35"
scanner := bufio.NewScanner(strings.NewReader(csv))
for scanner.Scan() {
parts := strings.Split(scanner.Text(), ",")
fmt.Printf("name=%s age=%s\n", parts[0], parts[1])
}
}
io.Reader interface:
Source → [Read(p []byte)] → buffer p → consumer
Chaining readers:
os.File → bufio.NewReader → gzip.NewReader → json.NewDecoder
↓ ↓ ↓ ↓
raw buffered decompressed decoded
bufio.Writer batching:
small writes → [buffer 4 KB] → one flush → os.File
many syscalls one syscall
สิ่งที่ได้ประโยชน์ต้นทุน
bufio.Readerลด syscall (batching reads)เพิ่ม memory สำหรับ buffer
bufio.Writerลด syscall (batching writes)ต้อง Flush() ก่อน close
io.Reader chaincomposable, zero-copy pipelineerror handling ซับซ้อนเมื่อ chain ยาว
io.Copyefficient transfer ระหว่าง Reader→Writerไม่ inspect data ระหว่างทาง
  • unbuffered read เร็วพอ — ทุก Read() call โดยไม่มี buffer = 1 syscall — ช้ามากสำหรับ small reads จำนวนมาก
  • bufio.Writer flush อัตโนมัติ — ไม่ — ต้อง Flush() เองก่อน close หรือส่ง response ไม่งั้น data อาจหาย
  • io.Reader อ่าน exact n bytes เสมอ — Read() อาจ return น้อยกว่า n bytes — ต้องวน loop ถ้าต้องการ exact read
  • strings.NewReader กับ bytes.NewReader เหมือนกัน — strings.NewReader สำหรับ string literal, bytes.NewReader สำหรับ []byte ที่มีอยู่แล้ว

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

Kubernetes: etcd client ใช้ bufio สำหรับ gRPC message buffering — ลด syscall overhead เมื่อรับ stream ของ events

Docker: container image layer copying ใช้ io.Copy เพื่อ transfer data จาก registry ไปยัง local storage อย่าง efficient

Go HTTP server: net/http ห่อ TCP connection ด้วย bufio.Reader โดยอัตโนมัติ — request parsing ไม่ต้อง syscall ทุก byte

io.ReadAll(r) คืนค่าอะไร?
หลังจาก bufio.Scanner.Scan() คืนค่า false ต้องตรวจสอบอะไร?
Type ใดที่ implement ทั้ง io.Reader และ io.Writer?
ทำไมควรรับ io.Reader ในฟังก์ชันแทนที่จะรับ *os.File?