Go bytes.FieldsSeq: อ่าน token ทีละชิ้นโดยไม่สร้าง slice ก้อนใหญ่
ถ้าต้องการแค่สองคำแรกจาก 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