io และ bufio
Interface io.Reader และ io.Writer
หัวข้อที่มีชื่อว่า “Interface io.Reader และ io.Writer”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 — Reader ในหน่วยความจำ
หัวข้อที่มีชื่อว่า “strings.NewReader — Reader ในหน่วยความจำ”strings.NewReader(s) ห่อ string เป็น io.Reader มีประโยชน์มากใน test และตัวอย่าง — สามารถทดสอบฟังก์ชันที่อ่านจาก stream ได้โดยไม่ต้องแตะ filesystem
r := strings.NewReader("hello, world")data, _ := io.ReadAll(r)fmt.Println(string(data)) // hello, worldbytes.Buffer — buffer สำหรับอ่านและเขียน
หัวข้อที่มีชื่อว่า “bytes.Buffer — buffer สำหรับอ่านและเขียน”bytes.Buffer implement ทั้ง io.Reader และ io.Writer เขียนลงไปแล้วอ่านกลับออกมา:
var buf bytes.Bufferfmt.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 — การอ่านทีละบรรทัด
หัวข้อที่มีชื่อว่า “bufio.Scanner — การอ่านทีละบรรทัด”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
ฟังก์ชัน helper ของ io
หัวข้อที่มีชื่อว่า “ฟังก์ชัน helper ของ io”Package io ยังมี helper ที่มีประโยชน์:
io.ReadAll(r) // อ่านจนถึง EOF คืน []byteio.Copy(dst, src) // copy จาก Reader ไปยัง Writerio.MultiReader(r1, r2) // ต่อ reader หลายตัวตามลำดับio.Discard // Writer ที่ทิ้ง byte ทั้งหมด (เหมือน /dev/null)io.LimitReader(r, n) // อ่านไม่เกิน n bytespackage main
import ( "bufio" "bytes" "fmt" "io" "strings")
// printLines accepts any io.Reader — works with files, buffers, or stringsfunc 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]) }}Loading Go runtime (first run only, ~8 MB)…
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”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 chain | composable, zero-copy pipeline | error handling ซับซ้อนเมื่อ chain ยาว |
| io.Copy | efficient 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