ในโลกของ JVM เมื่อคุณสร้าง List:
val checkpoints = ArrayList<Checkpoint>()
สัญชาตญาณของคุณคือ: ArrayList คือ Object บน Heap มีการห่อหุ้มข้อมูลด้วย Object Header, มีการจัดการ Array ภายในแบบ Object-based (เช่น elementData array บน Heap), และเมื่อขนาดเต็ม มันจะทำการสร้าง Array ใหม่บน Heap ขนาดใหญ่ขึ้น แล้วย้าย Reference ไปพร้อมกับโยน Object เก่าให้ GC ทิ้ง
ใน Go เมื่อคุณประกาศ s := make([]Checkpoint, 5, 10) ตัวแปร s มีขนาดเพียง **24 Bytes บนสถาปัตยกรรม 64-bit** (เก็บอยู่บน Stack เสมอ ถ้าไม่ Escape) โครงสร้างใต้กระโปรงคือ Struct SliceHeader:
type SliceHeader struct {
Data unsafe.Pointer // 8 Bytes: ชี้ไปยังสมาชิกตัวแรกของ Underlying Array บน RAM
Len int // 8 Bytes: จำนวนสมาชิกที่มีอยู่จริง (Length)
Cap int // 8 Bytes: ความจุสูงสุดที่รองรับได้โดยไม่ต้องจองแอดเดรสใหม่ (Capacity)
}
| คุณสมบัติ | Java `ArrayList` | Go Slice Header |
|---|---|---|
| Memory Footprint | Object บน Heap + Array Object + Object Headers | 24 Bytes บน Stack (Pointer + Len + Cap) |
| Pass by Value | ส่ง Reference (ชี้ไปยัง Heap Object เดียวกัน) | ก๊อปปี้ Slice Header 24 ไบต์ (Data pointer ยังชี้ Array เดิม) |
| Underlying Array | ซ่อนอยู่หลังม่าน จัดการโดย JVM | นักพัฒนาต้องเข้าใจความสัมพันธ์ระหว่าง Slice กับ Array ชัดเจน |
ในระบบรับสัญญาณ RFID นักวิ่ง s1 := pings[0:3] จะเป็นการสร้าง Slice ตัวใหม่ที่แชร์ Underlying Array ร่วมกับตัวเดิม นี่คือจุดที่ทำให้เกิดบั๊กข้อมูลทับซ้อนถ้าไม่ระวัง:
เมื่อคุณใช้ฟังก์ชัน append เพิ่มข้อมูลลงใน Slice แล้วความจุ (Cap) เต็ม Go Runtime จะจัดสรร Underlying Array ใหม่ให้อัตโนมัติด้วยอัลกอริทึม Growth Strategy:
TimingEvent ประกอบด้วย Bib int64 และ TimeSec int64BatchSplitter(events []TimingEvent, batchSize int) [][]TimingEvent เพื่อแบ่งสตรีมข้อมูลสถิตินักวิ่งออกเป็น Batch ย่อยๆ ตามขนาดที่กำหนด