ArrayBuffer trong TypeScript
Bài viết này giải thích về ArrayBuffer trong TypeScript.
Chúng tôi sẽ giải thích về ArrayBuffer trong TypeScript một cách từng bước, từ cơ bản đến các kỹ thuật thực tế.
YouTube Video
ArrayBuffer trong TypeScript
ArrayBuffer là một đối tượng tích hợp dùng để đại diện cho “vùng nhớ thô” cho dữ liệu nhị phân. Nó đại diện cho một bộ đệm thô với độ dài cố định, được phủ lên bởi TypedArray hoặc DataView để đọc và ghi.
Khái niệm cơ bản và đặc điểm của ArrayBuffer
ArrayBuffer là một dãy byte có độ dài cố định. Bạn chỉ định kích thước theo byte khi tạo, và độ dài này không thể thay đổi sau đó.
1// Create a new ArrayBuffer of 16 bytes.
2const buf = new ArrayBuffer(16);
3console.log(buf.byteLength); // 16
- Đoạn mã này tạo một bộ đệm trống gồm 16 byte. Bạn có thể kiểm tra kích thước bằng cách sử dụng
byteLength.
TypedArray và DataView — Các khung nhìn để thao tác với bộ đệm
ArrayBuffer không có khả năng đọc hoặc ghi dữ liệu. Do đó, các thao tác thực tế được thực hiện thông qua TypedArray hoặc DataView, bằng cách chỉ định các kiểu như số nguyên hoặc số thực và thứ tự byte (endianness) khi truy cập dữ liệu.
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 ]
Uint8Arraylà một kiểu xem mảng theo từng byte và có thể truy cập như một mảng bình thường. Nếu bạn tạo nhiều cửa sổ (view) trên cùng mộtArrayBuffer, chúng sẽ chia sẻ cùng vùng nhớ cho việc đọc và ghi.
DataView: Đọc và ghi tại vị trí tuỳ ý và kiểm soát thứ tự byte
DataView hữu ích cho việc đọc và ghi từng byte một cách chi tiết, và cho phép bạn chỉ định thứ tự byte (little hoặc big endian).
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"
DataViewcho phép bạn đọc và ghi giá trị bằng cách chỉ định các vị trí offset trong bộ nhớ, rất thích hợp để triển khai các giao thức yêu cầu xử lý thứ tự byte của mạng.
Chuyển đổi giữa chuỗi và ArrayBuffer (TextEncoder / TextDecoder)
Để chuyển đổi văn bản sang nhị phân và ngược lại, hãy sử dụng TextEncoder và 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.encodetrả về mộtUint8Array, vì vậy nếu bạn cần mộtArrayBuffer, bạn nên tham chiếu đến thuộc tính.buffercủa nó. Với phương thứcslice, bạn có thể trích xuất dữ liệu cần thiết bằng cách chỉ định vị trí offset và độ dài.
Cắt và sao chép ArrayBuffer
Phương thức slice trả về một ArrayBuffer mới. ArrayBuffer không thể thay đổi kích thước; nếu cần thay đổi, hãy tạo một bộ đệm mới và sao chép dữ liệu vào đó.
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
- Vì phương thức
slicetrả về mộtArrayBuffermới, nếu bạn ưu tiên hiệu suất hoặc muốn chia sẻ tham chiếu, bạn có thể sử dụng phương thứcsubarraycủaTypedArrayđể tham chiếu đến cùng một bộ đệm.
Sự khác biệt giữa TypedArray.subarray và việc sao chép
subarray của TypedArray trả về một cửa sổ (view) mới tham chiếu cùng một 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]
- Các khung nhìn chia sẻ có thể tránh được chi phí sao chép, nhưng vì chúng tham chiếu cùng một bộ đệm nên bạn cần chú ý đến các tác động phụ.
Nối các Buffer (kết hợp nhiều ArrayBuffer)
Vì ArrayBuffer có độ dài cố định, để kết hợp nhiều buffer hãy tạo một ArrayBuffer mới và sao chép dữ liệu vào.
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]
- Nếu bạn thường xuyên nối một lượng lớn dữ liệu, tính toán trước tổng kích thước và phân bổ bộ đệm mới chỉ một lần như trong mã này sẽ hiệu quả hơn.
Truyền ArrayBuffer đến Worker (chuyển nhượng quyền sở hữu - Transferable)
Trong postMessage của trình duyệt, bạn có thể chuyển một ArrayBuffer như một đối tượng "transferable". Quyền sở hữu có thể được chuyển mà không cần sao chép, giúp tránh chi phí sao chép.
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
- Bằng cách chỉ định các đối tượng cần chuyển trong một mảng làm đối số thứ hai của
postMessage, bạn có thể chuyển quyền sở hữu mà không cần sao chép các đối tượng có thể chuyển nhưArrayBuffer. - Sau khi chuyển, bộ đệm sẽ trở nên "detached" (bị tách rời) phía bên gốc và không thể truy cập được. Sử dụng
SharedArrayBuffercho phép truy cập đồng thời từ nhiều luồng, nhưng việc sử dụng nó có kèm theo các yêu cầu về bảo mật và hạn chế môi trường.
Xử lý trong Node.js (chuyển đổi giữa Buffer)
Trong Node.js, bạn có thể chuyển đổi giữa kiểu nhị phân Buffer của Node.js và ArrayBuffer. Điều này hữu ích khi bạn phát triển TypeScript hướng tới cả trình duyệt và 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)trong Node.js thường tạo một bản sao, nhưng đôi khi có thể dùng chung tham chiếu, do đó hãy lưu ý các chỉ số offset.
Những lưu ý về hiệu năng và thực tiễn tốt nhất
Để tối ưu hiệu suất và xử lý ArrayBuffer hiệu quả, cần chú ý một số điểm quan trọng. Dưới đây, chúng tôi liệt kê và giải thích các thực tiễn tốt nhất.
-
Giảm số lượng thao tác sao chép Khi xử lý dữ liệu nhị phân lớn, hãy dùng
subarray(view chia sẻ) hoặc transferable để giảm việc sao chép. -
Cấp phát mảng lớn một lần duy nhất Việc cấp phát các bộ đệm nhỏ nhiều lần sẽ làm tăng chi phí tài nguyên. Nếu có thể, hãy cấp phát một bộ đệm lớn một lần và sử dụng từng phần của nó khi cần thiết.
-
Chỉ định thứ tự byte một cách rõ ràng Khi xử lý giá trị nhiều byte, hãy dùng
DataViewvà chỉ định rõ thứ tự byte. Big-endian thường là tiêu chuẩn cho các giao thức mạng.
Các ví dụ sử dụng phổ biến
ArrayBuffer được sử dụng rộng rãi cả trong trình duyệt và Node.js.
- Phân tích và xây dựng giao thức nhị phân (xử lý thông tin header bằng
DataView) - Xử lý dữ liệu media như hình ảnh và âm thanh (
fetch(...).then(res => res.arrayBuffer())) - Bộ nhớ chia sẻ cho WebAssembly (bộ nhớ Wasm hoạt động dựa trên
ArrayBuffer) - Chuyển xử lý nặng sang Workers (truyền
ArrayBuffernhư transferable)
Đoạn mã sau là ví dụ về việc lấy và phân tích dữ liệu nhị phân.
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}- Đoạn mã này tải dữ liệu nhị phân từ bất kỳ URL nào trong trình duyệt và hiển thị một vài byte đầu. Đoạn mã tải dữ liệu nhận được qua API
fetchthànhArrayBuffervà kiểm tra 16 byte đầu tiên bằngUint8Array.
Tóm tắt
ArrayBuffer là đại diện cho vùng nhớ thô, giúp thực hiện các thao tác nhị phân hiệu quả thông qua TypedArray và DataView. Bằng cách thiết kế để tránh sao chép không cần thiết và chỉ định rõ thứ tự byte, bạn có thể xử lý nhị phân hiệu quả và an toàn.
Bạn có thể làm theo bài viết trên bằng cách sử dụng Visual Studio Code trên kênh YouTube của chúng tôi. Vui lòng ghé thăm kênh YouTube.