กระรอกดินต้นฉบับค่อย ๆ ดึงชิ้นข้อมูลจากริบบิ้นไบต์ผ่านช่องคัดทีละชิ้น

Go bytes.FieldsSeq: อ่าน token ทีละชิ้นโดยไม่สร้าง slice ก้อนใหญ่

พี่โชว์Engineering

ถ้าต้องการแค่สองคำแรกจาก payload ขนาดใหญ่ การเรียก bytes.Fields จะสร้าง slice รวมทุก field ก่อนทั้งที่เราไม่ใช้ส่วนท้าย bytes.FieldsSeq คืน iterator แบบ lazy จึงอ่านทีละ field และหยุด loop ได้ทันทีครับ

WHY: หยิบของจากสายพานเท่าที่ใช้

ลองนึกถึงกล่องที่วิ่งบนสายพาน ถ้าต้องการสองกล่องแรก เราไม่จำเป็นต้องยกทุกกล่องลงมากองบนพื้น FieldsSeq แยก token ด้วย Unicode whitespace เหมือน Fields แต่ผู้เรียกเป็นฝ่ายดึงค่าถัดไป จึงเหมาะกับการตรวจ header หรือ prefix ของข้อมูลที่ยังเก็บใน buffer เดิม

package content

import (
    "bytes"
    "reflect"
    "testing"
)

func firstFields(input []byte, limit int) []string {
    out := make([]string, 0, limit)
    for field := range bytes.FieldsSeq(input) {
        out = append(out, string(field))
        if len(out) == limit {
            break
        }
    }
    return out
}

func TestFirstFields(t *testing.T) {
    got := firstFields([]byte("  alpha\tbeta\nunused tail  "), 2)
    if !reflect.DeepEqual(got, []string{"alpha", "beta"}) {
        t.Fatalf("unexpected fields: %#v", got)
    }
    if got := firstFields([]byte("   \n\t"), 2); len(got) != 0 {
        t.Fatalf("whitespace-only input must be empty: %#v", got)
    }
}

HOW: slice ที่ได้ยังอ้าง buffer ต้นฉบับ

ตัวอย่างแปลงแต่ละ field เป็น string เพื่อเก็บผลแบบอิสระ หากเก็บ []byte ตรง ๆ slice เหล่านั้นยังชี้เข้า input การแก้ buffer หรือใช้ buffer pool ซ้ำอาจทำให้ค่าที่เก็บเปลี่ยนตาม จึงต้องตัดสินใจให้ชัดว่าจะประมวลผลทันทีหรือ copy ก่อนส่งข้ามอายุของ buffer

FieldsSeq ไม่ได้ช่วยจำกัดขนาด token ให้เรา ถ้าข้อมูลมาจากเครือข่ายยังต้องจำกัด payload, จำนวน field และความยาวแต่ละ field เอง และหาก delimiter เป็นเครื่องหมายเฉพาะแทน whitespace ให้ใช้ API ตระกูล SplitSeq ที่ตรงรูปแบบกว่า

Recap

  • ใช้ FieldsSeq เมื่ออ่านทีละ token หรือหยุดก่อนจบได้
  • ใช้ Fields เมื่อจำเป็นต้องได้ slice ครบชุดและใช้งานหลายรอบ
  • copy field ก่อนเก็บยาวกว่าชีวิตของ buffer
  • จำกัด payload และจำนวน token แยกจาก iterator
  • ทดสอบ whitespace ซ้ำ ข้อมูลว่าง และ early stop