ArrayBuffer ใน TypeScript
บทความนี้อธิบายเกี่ยวกับ ArrayBuffer ใน TypeScript
เราจะอธิบาย ArrayBuffer ใน TypeScript ทีละขั้นตอน ตั้งแต่พื้นฐานจนถึงเทคนิคการใช้งานจริง
YouTube Video
ArrayBuffer ใน TypeScript
ArrayBuffer เป็นอ็อบเจกต์ในตัวที่ใช้แทน “พื้นที่หน่วยความจำดิบ” สำหรับข้อมูลไบนารี มันแทนที่บัฟเฟอร์แบบความยาวคงที่ และสามารถอ่านหรือเขียนข้อมูลได้โดยใช้ TypedArray หรือ DataView
แนวคิดพื้นฐานและลักษณะเฉพาะของ ArrayBuffer
ArrayBuffer คือกลุ่มไบต์ความยาวคงที่ คุณต้องระบุขนาดเป็นไบต์เมื่อสร้าง และความยาวจะไม่สามารถเปลี่ยนแปลงได้ภายหลัง
1// Create a new ArrayBuffer of 16 bytes.
2const buf = new ArrayBuffer(16);
3console.log(buf.byteLength); // 16
- โค้ดนี้สร้างบัฟเฟอร์เปล่าขนาด 16 ไบต์ คุณสามารถตรวจสอบขนาดได้ด้วย
byteLength
TypedArray และ DataView — มุมมองสำหรับจัดการบัฟเฟอร์
ArrayBuffer ไม่สามารถอ่านหรือเขียนข้อมูลได้ ดังนั้น การดำเนินการจริงจะต้องทำผ่าน TypedArray หรือ DataView โดยระบุชนิดของข้อมูล เช่น จำนวนเต็มหรือจำนวนทศนิยม และ endianness เมือ่เข้าถึงข้อมูล
1// Create a buffer and a Uint8Array view over it. Then write bytes and read them.
2const buffer = new ArrayBuffer(8);
3const u8 = new Uint8Array(buffer);
4
5u8[0] = 0x41; // 'A'
6u8[1] = 0x42; // 'B'
7console.log(u8); // Uint8Array(8) [ 65, 66, 0, 0, 0, 0, 0, 0 ]
Uint8Arrayคือมุมมองข้อมูลแบบแถวไบต์ สามารถใช้งานเหมือนกับอาเรย์ปกติ หากคุณสร้างมุมมองหลายอันบนArrayBufferเดียวกัน ข้อมูลที่อ่านหรือเขียนจะใช้หน่วยความจำเดียวกัน
DataView: อ่านและเขียนข้อมูลที่ตำแหน่งและ endian ใดก็ได้
DataView มีประโยชน์สำหรับการอ่านและเขียนข้อมูลอย่างละเอียดเป็นรายไบต์ และสามารถกำหนด endian เป็น little หรือ big ได้
1// Using DataView to write/read multi-byte values with endianness control.
2const buf2 = new ArrayBuffer(8);
3const view = new DataView(buf2);
4
5// Write a 32-bit integer (little-endian)
6view.setInt32(0, 0x12345678, true);
7
8// Read it back as little-endian and big-endian
9const little = view.getInt32(0, true);
10const big = view.getInt32(0, false);
11console.log(little.toString(16)); // "12345678"
12console.log(big.toString(16)); // "78563412"
DataViewช่วยให้สามารถอ่านและเขียนข้อมูลโดยระบุ offset ในหน่วยความจำ จึงเหมาะสำหรับการใช้งานโปรโตคอลที่ต้องจัดการ byte order ของเครือข่าย
การแปลงระหว่างสตริงและ ArrayBuffer (TextEncoder / TextDecoder)
ในการแปลงข้อความเป็นไบนารีและในทางกลับกัน ให้ใช้ TextEncoder และ TextDecoder
1// Convert string -> ArrayBuffer and back using TextEncoder/TextDecoder.
2const encoder = new TextEncoder();
3const decoder = new TextDecoder();
4
5// Unicode escape sequences
6const str = "\u3053\u3093\u306B\u3061\u306F";
7const encoded = encoder.encode(str); // Uint8Array
8console.log(encoded); // Uint8Array([...])
9
10// If you need an ArrayBuffer specifically:
11const ab = encoded.buffer.slice(encoded.byteOffset, encoded.byteOffset + encoded.byteLength);
12console.log(ab.byteLength); // bytes length of encoded text
13
14// Decode back
15const decoded = decoder.decode(encoded);
16console.log(decoded);TextEncoder.encodeจะคืนค่าเป็นUint8Arrayดังนั้นหากต้องการArrayBufferให้ใช้งานผ่านคุณสมบัติ.bufferด้วยเมธอดsliceคุณสามารถตัดข้อมูลที่ต้องการได้โดยระบุ offset และความยาว
การแบ่งส่วน (slice) และคัดลอก ArrayBuffer
เมธอด slice จะคืนค่าเป็น ArrayBuffer ตัวใหม่ ArrayBuffer ไม่สามารถเปลี่ยนขนาดได้ หากจำเป็นต้องเปลี่ยนขนาด ให้สร้างบัฟเฟอร์ใหม่และคัดลอกข้อมูลเข้าไป
1// Slice an ArrayBuffer and copy to a new sized buffer.
2const original = new Uint8Array([1,2,3,4,5]).buffer;
3const part = original.slice(1, 4); // bytes 1..3
4console.log(new Uint8Array(part)); // Uint8Array [ 2, 3, 4 ]
5
6// Resize: create a new buffer and copy existing content
7const larger = new ArrayBuffer(10);
8const target = new Uint8Array(larger);
9target.set(new Uint8Array(original), 0);
10console.log(target); // first bytes filled with original data
- เนื่องจากเมธอด
sliceจะคืนค่าArrayBufferตัวใหม่ หากคุณต้องการประสิทธิภาพหรือแชร์ข้อมูล คุณสามารถใช้subarrayของTypedArrayเพื่ออ้างอิงบัฟเฟอร์ตัวเดิมได้
ความแตกต่างระหว่าง TypedArray.subarray กับการคัดลอก
subarray ของ TypedArray จะคืนมุมมองใหม่แต่ใช้ ArrayBuffer เดียวกัน
1// subarray shares the same underlying buffer; modifying one affects the other.
2const arr = new Uint8Array([10,20,30,40]);
3const viewSub = arr.subarray(1,3); // shares memory
4viewSub[0] = 99;
5console.log(arr); // Uint8Array [10, 99, 30, 40]
- มุมมองที่ใช้ร่วมกันสามารถหลีกเลี่ยงต้นทุนของการคัดลอกข้อมูลได้ แต่เนื่องจากอ้างถึงบัฟเฟอร์ตัวเดียวกัน จึงต้องระวังผลข้างเคียงที่อาจเกิดขึ้น
การต่อบัฟเฟอร์ (รวม ArrayBuffer หลายอันเข้าด้วยกัน)
เนื่องจาก ArrayBuffer มีขนาดคงที่ หากต้องการรวมหลายบัฟเฟอร์ ให้สร้าง ArrayBuffer ใหม่และคัดลอกข้อมูลเข้าไป
1// Concatenate multiple ArrayBuffers
2function concatBuffers(buffers: ArrayBuffer[]): ArrayBuffer {
3 const total = buffers.reduce((sum, b) => sum + b.byteLength, 0);
4 const result = new Uint8Array(total);
5 let offset = 0;
6 for (const b of buffers) {
7 const u8 = new Uint8Array(b);
8 result.set(u8, offset);
9 offset += u8.length;
10 }
11 return result.buffer;
12}
13
14const a = new Uint8Array([1,2]).buffer;
15const b = new Uint8Array([3,4,5]).buffer;
16const c = concatBuffers([a, b]);
17console.log(new Uint8Array(c)); // [1,2,3,4,5]
- หากคุณต้องเชื่อมข้อมูลจำนวนมากบ่อยครั้ง การคำนวณขนาดรวมและจัดสรรบัฟเฟอร์ใหม่เพียงครั้งเดียวเหมือนในตัวอย่างนี้จะมีประสิทธิภาพมากกว่า
การส่ง ArrayBuffer ไปยัง Worker (Transferable)
ใน postMessage ของเบราว์เซอร์ คุณสามารถส่ง ArrayBuffer เป็นวัตถุแบบ "transferable" ได้ ความเป็นเจ้าของสามารถส่งต่อได้โดยไม่ต้องคัดลอกข้อมูล จึงหลีกเลี่ยงต้นทุนของการคัดลอก
1// Example: posting an ArrayBuffer to a Worker as a transferable object (browser)
2const worker = new Worker('worker.js');
3const bufferToSend = new Uint8Array([1,2,3,4]).buffer;
4
5// Transfer ownership to the worker (main thread no longer owns it)
6worker.postMessage(bufferToSend, [bufferToSend]);
7
8// After transfer, bufferToSend.byteLength === 0 in many browsers (detached)
9console.log(bufferToSend.byteLength); // may be 0
- โดยระบุวัตถุที่จะถ่ายโอนในอาร์เรย์เป็นอาร์กิวเมนต์ที่สองของ
postMessageคุณสามารถถ่ายโอนความเป็นเจ้าของของวัตถุประเภทtransferableเช่นArrayBufferได้โดยไม่ต้องคัดลอกข้อมูล - หลังจากถ่ายโอนแล้ว บัฟเฟอร์ฝั่งต้นทางจะกลายเป็น "detached" และไม่สามารถเข้าถึงได้อีก การใช้
SharedArrayBufferทำให้สามารถเข้าถึงพร้อมกันได้จากหลายเธรด แต่มีข้อกำหนดด้านความปลอดภัยและข้อจำกัดของสภาพแวดล้อม
การใช้งานใน Node.js (การแปลงไปมาระหว่าง Buffer)
ใน Node.js สามารถแปลงระหว่าง Buffer ที่เป็นประเภทไบนารีของ Node.js กับ ArrayBuffer ได้ สิ่งนี้มีประโยชน์เมื่อคุณพัฒนา TypeScript ที่รันได้ทั้งบนเบราว์เซอร์และ Node.js
1// Convert ArrayBuffer <-> Node.js Buffer
2// In Node.js environment:
3const ab = new Uint8Array([10,20,30]).buffer;
4const nodeBuffer = Buffer.from(ab); // ArrayBuffer -> Buffer
5console.log(nodeBuffer); // <Buffer 0a 14 1e>
6
7const backToAb = nodeBuffer.buffer.slice(
8 nodeBuffer.byteOffset,
9 nodeBuffer.byteOffset + nodeBuffer.byteLength
10);
11console.log(new Uint8Array(backToAb)); // Uint8Array [10,20,30]
Buffer.from(arrayBuffer)ใน Node.js โดยปกติจะเป็นการคัดลอกข้อมูล แต่บางกรณีอาจใช้การอ้างอิงร่วมกันได้ ดังนั้นต้องระวังเรื่อง offset ด้วย
ข้อควรคำนึงเกี่ยวกับประสิทธิภาพและแนวทางปฏิบัติที่ดีที่สุด
เพื่อเพิ่มประสิทธิภาพและการใช้งาน ArrayBuffer ให้มีประสิทธิผล มีข้อสำคัญบางประการที่ควรคำนึงถึง ด้านล่างนี้คือแนวทางปฏิบัติที่ดีที่สุดที่ใช้งานได้จริงพร้อมคำอธิบาย
-
ลดจำนวนการคัดลอกข้อมูล สำหรับข้อมูลไบนารีขนาดใหญ่ ให้ใช้
subarray(มุมมองร่วมกัน) หรือ transferable เพื่อช่วยลดการคัดลอก -
จัดสรรบัฟเฟอร์ขนาดใหญ่ในครั้งเดียว การจัดสรรบัฟเฟอร์ขนาดเล็กซ้ำๆ จะเพิ่มค่าใช้จ่ายส่วนเกิน ถ้าเป็นไปได้ ให้จัดสรรบัฟเฟอร์ขนาดใหญ่ทีเดียวและใช้บางส่วนตามที่ต้องการ
-
ระบุ Endianness อย่างชัดเจน เมื่อทำงานกับค่าหลายไบต์ควรใช้
DataViewและระบุ endianness ให้ชัดเจน Big-endian มักเป็นมาตรฐานสำหรับโปรโตคอลเครือข่าย
ตัวอย่างการใช้งานที่พบได้บ่อย
ArrayBuffer ถูกใช้อย่างแพร่หลายทั้งในเบราว์เซอร์และ Node.js
- การแปลงโพรโทคอลไบนารี (เช่น การประมวลผลข้อมูล header ด้วย
DataView) - ประมวลผลสื่อ เช่น ข้อมูลภาพและเสียง (
fetch(...).then(res => res.arrayBuffer())) - พื้นที่หน่วยความจำใช้ร่วมสำหรับ WebAssembly (หน่วยความจำของ Wasm ดำเนินการโดยใช้
ArrayBuffer) - การถ่ายโอนงานหนักไปยัง Worker (โดยส่ง
ArrayBufferแบบ transferable)
โค้ดด้านล่างนี้เป็นตัวอย่างการดึงข้อมูลและวิเคราะห์ข้อมูลไบนารี
1// Example: fetch binary data in browser and inspect first bytes
2async function fetchAndInspect(url: string) {
3 const resp = await fetch(url);
4 const ab = await resp.arrayBuffer();
5 const u8 = new Uint8Array(ab, 0, Math.min(16, ab.byteLength));
6 console.log('first bytes:', u8);
7}- โค้ดนี้ดึงข้อมูลไบนารีจาก URL ในเบราว์เซอร์และแสดงข้อมูลไบต์แรกๆ โค้ดจะโหลดข้อมูลด้วย API
fetchให้เป็นArrayBufferแล้วตรวจสอบ 16 ไบต์แรกด้วยUint8Array
สรุป
ArrayBuffer แสดงข้อมูลในรูปแบบหน่วยความจำดิบ ช่วยให้สามารถดำเนินการไบนารีได้อย่างมีประสิทธิภาพด้วย TypedArray และ DataView ด้วยการออกแบบให้เลี่ยงการคัดลอกที่ไม่จำเป็นและกำหนด endianness อย่างชัดเจน คุณจะสามารถประมวลผลไบนารีได้อย่างปลอดภัยและมีประสิทธิภาพสูง
คุณสามารถติดตามบทความข้างต้นโดยใช้ Visual Studio Code บนช่อง YouTube ของเรา กรุณาตรวจสอบช่อง YouTube ด้วย