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 — תצוגות למניפולציה על Buffers
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: קריאה וכתיבה בגבולות וגודל בתים שרירותיים וסדרי בתים שונים
DataView שימושי לקריאה וכתיבה מדויקים לפי בתים, ומאפשר לציין סדר בתים (סדר קטן או גדול).
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 מאפשר קריאה וכתיבה של ערכים ע״י ציון מיקומים בזיכרון (offsets), ולכן מתאים ליישום פרוטוקולים הדורשים טיפול בסדר בייטים של רשת.
המרה בין מחרוזות ל-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) והאורך (length).
חיתוך והעתקה של ArrayBuffer
המתודה slice מחזירה ArrayBuffer חדש. אי אפשר לשנות את הגודל של ArrayBuffer; אם יש צורך לשנות גודל, יש ליצור buffer חדש ולהעתיק אליו את הנתונים.
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 כדי להתייחס לאותו buffer.
ההבדל בין 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]
- תצוגות משותפות מאפשרות להימנע מהעתקה, אך מכיוון שהן מפנות לאותו buffer יש להיזהר מתופעות לוואי.
שרשור באפרים (שילוב כמה ArrayBuffers)
מכיוון ש-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]
- אם יש צורך לעבד כמויות גדולות של נתונים במיזוג תדיר, חישוב מראש של הגודל הכולל והקצאת buffer חדש פעם אחת, כמו בדוגמה, הוא יעיל יותר.
העברת ArrayBuffer ל-Worker (העברה)
ב- 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
- על ידי ציון האובייקטים להעברה במערך (array) כארגומנט שני ל-postMessage, ניתן להעביר בעלות (בלי להעתיק) של אובייקטים שניתנים להעברה כגון ArrayBuffer.
- לאחר העברה, ה-buffer הופך להיות 'מנותק' (detached) בצד המקורי ולא ניתן לגשת אליו. השימוש ב-SharedArrayBuffer מאפשר גישה בו-זמנית ממספר תהליכונים (threads), אך דורש עמידה בדרישות אבטחה ומוגבלות לפי סביבת ההרצה.
טיפול ב-Node.js (המרה הדדית עם Buffer)
ב-Node.js ניתן להמיר בין טיפוס ה-Buffer הבינארי של Node.js לבין ArrayBuffer. זה שימושי כאשר מייעדים את הפרויקט לדפדפן ול-Node.js ב-TypeScript.
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 יוצר בדרך כלל עותק, אך יש מקרים שבהם משתפים הפניה, שים לב להיסטים.
שיקולי ביצועים והמלצות לשימוש נכון
כדי לשפר את הביצועים ולטפל ב-ArrayBuffer בצורה יעילה, ישנם מספר דגשים חשובים שכדאי לזכור. להלן נפרט ונסביר עקרונות עבודה יעילים.
-
הקטן את מספר ההעתקות בעת טיפול בקבצים בינאריים גדולים, השתמש ב-
subarray(תצוגה משותפת) או באובייקטים ניתנים להעברה לצמצום כמות ההעתקות. -
הקצה באפרים גדולים בבת אחת הקצאת חוצצים קטנים שוב ושוב מגדילה את התקורה. אם אפשר, הקצה חוצץ גדול בבת אחת והשתמש בחלקים ממנו לפי הצורך.
-
ציין במפורש את סדר האוקטרטים בעת טיפול בערכים מרובי בתים, השתמש ב-
DataViewוציין במפורש את סדר הבתים. Big-endian הוא לרוב ברירת מחדל בפרוטוקולי רשת.
דוגמאות לשימושים נפוצים
ArrayBuffer נמצא בשימוש רחב גם בדפדפנים וגם ב-Node.js.
- ניתוח והרכבה של פרוטוקולים בינאריים (עיבוד מידע כותרת עם
DataView) - עיבוד מדיה כגון נתוני תמונה ושמע (
fetch(...).then(res => res.arrayBuffer())) - זיכרון משותף עבור WebAssembly (הזיכרון של Wasm פועל על בסיס
ArrayBuffer) - העברת עיבוד כבד ל-Workers (העברת
ArrayBufferכאובייקט להעברה)
הקוד הבא הוא דוגמה לאיתור וניתוח נתונים בינאריים.
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. על ידי תכנון להימנע מהעתקות מיותרות וציון סדר בתים במפורש, ניתן להגיע לעיבוד בינארי בטוח ומהיר.
תוכלו לעקוב אחר המאמר שלמעלה באמצעות Visual Studio Code בערוץ היוטיוב שלנו. נא לבדוק גם את ערוץ היוטיוב.