เรียน Go ภาษาง่าย 📘

📄

เจาะลึก File + IO + OS ฉบับภาษาง่าย

Reader/Writer, bufio, embed, path ปลอดภัย

7 บท
10 ข้อสอบ
1. เปิด-อ่าน-เขียน-ปิด (defer)
go
f, err := os.Open("a.txt")
if err != nil {
    if errors.Is(err, os.ErrNotExist) { fmt.Println("ไม่มีไฟล์") }
    return err
}
defer f.Close() // สัญญาว่าปิดแน่ ไม่ว่าจะ return ทางไหน

b, err := io.ReadAll(f) // ไฟล์เล็กอ่านรวดเดียวได้
// ไฟล์เล็กมากๆ ใช้ os.ReadFile / os.WriteFile สั้นกว่า
data, _ := os.ReadFile("config.json")
_ = os.WriteFile("out.txt", data, 0644)

กฎ: เปิดแล้ว defer Close() ทันทีบรรทัดถัดไป อย่าเว้น

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


2. io.Reader Writer คือท่อ
go
// Reader = ที่ดูดออก, Writer = ที่เทเข้า, ReadWriter = ทั้งคู่
func (f *File) Read(buf []byte) (n int, err error)
n, err := f.Read(buf[0:32]) // อ่าน 32 byte แรก

ต่อท่อด้วย io.Copy (ก็อปไฟล์/โหลด/อัพโหลด):

go
src, _ := os.Open("in.zip")
defer src.Close()
dst, _ := os.Create("out.zip")
defer dst.Close()
io.Copy(dst, src) // สตรีม ไม่โหลดทั้งก้อน
✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


3. bufio + สตรีมไฟล์ยักษ์

อย่า ReadAll ไฟล์ 1GB (แรมระเบิด) ให้อ่านทีละบรรทัด:

go
f, _ := os.Open("big.log")
defer f.Close()
sc := bufio.NewScanner(f)
sc.Buffer(make([]byte, 1024*1024), 10*1024*1024) // เผื่อบรรทัดยาว
for sc.Scan() {
    line := sc.Text()
    // ประมวลผลทีละบรรทัด
}
if err := sc.Err(); err != nil { log.Println(err) }

เขียนบัฟเฟอร์ + Flush:

go
w := bufio.NewWriter(f)
w.WriteString("hello\n")
w.Flush()

ไฟล์ยักษ์ + JSON ใช้ jsontext (ใหม่ 1.27) สตรีมทีละ token แทนโหลดทั้งก้อน (ดูเล่ม testing-http ประกอบ)

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


4. filepath เดินโฟลเดอร์ + embed
go
// เดินทั้งต้น
filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
    if err != nil { return err }
    fmt.Println(path)
    return nil
})
filepath.Join("a", "b", "c.txt") // ต่อ path ถูกทุก OS
filepath.Ext("a.jpg") // .jpg
os.MkdirAll("data/2026", 0755)

ฝังไฟล์ใน binary ด้วย embed (config/templ เล็กๆ):

go
import "embed"
//go:embed templates/*.html
var tmplFS embed.FS
✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


5. กับดัก + เช็กลิสต์
  • ลืม Close -> fd รั่ว เปิดเยอะๆ พัง (ใช้ defer + linter ช่วย)
  • ใช้ path แบบ "a\\b" ตรงๆ พังบน Linux/Mac ให้ใช้ filepath.Join
  • รับ path จาก user ต้องคลีน filepath.Clean + ล็อกไม่ให้ ../ หลุดออกนอก (path traversal)
  • permission 0644 ไฟล์ 0755 โฟลเดอร์ อย่า 0777 มั่ว
  • go vet จับ print/scan ผิดได้

อ้างอิง: Effective Go #data, std os io bufio filepath embed

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


ภาคผนวก: ตัวอย่างเพิ่ม + Best Practices
ตัวอย่างเพิ่ม 1: อ่านไฟล์ยักษ์ทีละบรรทัด (บัฟเฟอร์ใหญ่)
go
f, _ := os.Open("big.log")
defer f.Close()
sc := bufio.NewScanner(f)
sc.Buffer(make([]byte, 64*1024), 10*1024*1024) // บรรทัดยาวเกิน 64K ต้องขยาย
for sc.Scan() {
    handle(sc.Text())
}
if err := sc.Err(); err != nil { // เช็กหลัง loop เสมอ
    log.Fatal(err)
}
ตัวอย่างเพิ่ม 2: hash ไฟล์แบบสตรีม (แรมไม่บวม)
go
f, _ := os.Open("app.zip")
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
    log.Fatal(err)
}
fmt.Printf("%x\n", h.Sum(nil))
ตัวอย่างเพิ่ม 3: ฝังไฟล์ใน binary ด้วย embed
go
//go:embed template.html
var tmpl string

//go:embed static
var staticFS embed.FS
Best practices เช็กลิสต์
  • เปิดแล้ว defer Close() ทันทีบรรทัดถัดไป อย่ารอ
  • ไฟล์ใหญ่ห้าม ReadFile ทั้งก้อน ใช้สตรีม (Scanner/Copy)
  • path จาก user: Clean + กัน ../ หลุดโฟลเดอร์ (path traversal)
  • ใช้ filepath.Join อย่าต่อ \ หรือ / เอง (พังข้าม OS)
  • permission: ไฟล์ 0644 โฟลเดอร์ 0755 ไม่ใช้ 0777 มั่ว
  • เช็ก sc.Err() หลัง loop เสมอ ไม่งั้น error เงียบ
  • ลืม Close = fd รั่ว เปิดเยอะๆ พัง (ใช้ defer + linter ช่วย)
แบบฝึกหัด
  • เขียนโปรแกรมนับคำในไฟล์ 1GB โดยใช้แรมไม่เกิน 20MB
  • ทำ file server ที่กัน path traversal แล้วลองเจาะเองดู