TypedArray ב- TypeScript

TypedArray ב- TypeScript

מאמר זה מסביר את TypedArray ב-TypeScript.

נסביר את TypedArray ב-TypeScript, כולל דוגמאות מעשיות.

YouTube Video

TypedArray ב- TypeScript

TypedArray הוא מנגנון לטיפול יעיל בנתונים בינאריים. זה מועיל במיוחד לפעולות בינאריות ברמה נמוכה כמו נתוני תמונה גדולים, זרמי בתים מהרשת, ומערכים מספריים עבור WebGL.

איך יוצרים ArrayBuffer

ArrayBuffer מייצג אזור של בתים באורך קבוע. קודם כל, צרו באפר ובדקו את גודלו ואורך הבתים שלו.

1// Create an ArrayBuffer of 16 bytes
2const buffer: ArrayBuffer = new ArrayBuffer(16);
3console.log("buffer.byteLength:", buffer.byteLength); // 16
  • קוד זה יוצר אזור ריק בגודל 16 בתים. ArrayBuffer עצמו לא כולל פונקציות קריאה/כתיבה, ולכן יש לגשת אליו דרך TypedArray או DataView.

סוגי TypedArray

ישנם סוגים רבים של TypedArray, כגון אלו הבאים. סוגי ה-TypedArrays משתנים בהתאם לסוג הנתונים וגודלם שהם מתמודדים איתו.

TypedArray סוג נתונים גודל ביט
Int8Array מספר שלם של 8 ביט 8 ביטים
Uint8Array מספר שלם ללא סימן בגודל 8 ביט 8 ביטים
Uint8ClampedArray מספר שלם ללא סימן תחום בגודל 8 ביט 8 ביטים
Int16Array מספר שלם של 16 ביט 16 ביטים
Uint16Array מספר שלם ללא סימן בגודל 16 ביט 16 ביטים
Int32Array מספר שלם של 32 ביט 32 ביטים
Uint32Array מספר שלם ללא סימן בגודל 32 ביט 32 ביטים
Float32Array מספר נקודה צפה בעל 32 ביטים 32 ביטים
Float64Array מספר נקודה צפה בעל 64 ביטים 64 ביטים

TypedArray בסיסיים (Uint8Array, Int16Array, Float32Array, וכו')

TypedArray יוצר 'תצוגה מטיפוס מוגדר' מעל ArrayBuffer. להלן דוגמאות ליצירה ושימוש ב-TypedArray נפוצים.

 1// Create a buffer and different typed views over it
 2const buf = new ArrayBuffer(8); // 8 bytes
 3
 4// Create views
 5const u8 = new Uint8Array(buf);    // 8 x uint8
 6const i16 = new Int16Array(buf);   // 4 x int16 (since each int16 is 2 bytes)
 7const f32 = new Float32Array(buf); // 2 x float32 (4 bytes each)
 8
 9console.log("Uint8 length:", u8.length);
10console.log("Int16 length:", i16.length);
11console.log("Float32 length:", f32.length);
  • בעזרת יצירת מספר תצוגות לאותו ArrayBuffer, ניתן לקרוא ולכתוב את אותה הזיכרון בסוגים או ברזולוציות שונות. ה-length של התצוגה הוא מספר האיברים, ו-byteLength הוא מספר הבתים.

כתיבה וקריאה (פעולות ברמת בתים)

כאשר כותבים ערך ל-TypedArray, הבתים המתאימים בזיכרון מתעדכנים. ניתן לראות את השינויים כאשר קוראים את אותו באפר עם תצוגה שונה.

 1// Demonstrate writing via one view and reading via another
 2const buffer2 = new ArrayBuffer(4);
 3const u8view = new Uint8Array(buffer2);
 4const u32view = new Uint32Array(buffer2);
 5
 6u8view[0] = 0x78;
 7u8view[1] = 0x56;
 8u8view[2] = 0x34;
 9u8view[3] = 0x12;
10
11console.log("Uint8 bytes:", Array.from(u8view)); // [120, 86, 52, 18]
12console.log("Uint32 value (platform endianness):", u32view[0]); // value depends on endianness
  • בדוגמה זו, אנו כותבים רצף בתים ואז קוראים את אותו אזור כמספר שלם 32-ביט. יש לשים לב שהתוצאה תלויה בסדר הבתים (Endianness) של סביבת ההרצה.

סדר בתים (Endianness) ו-DataView

DataView שימושי כאשר רוצים לשלוט בבעיות סדר בתים התלויות בסביבה. DataView מאפשר לציין את סדר הבתים בזמן קריאה וכתיבה.

 1// Use DataView to read/write with explicit endianness control
 2const b = new ArrayBuffer(4);
 3const dv = new DataView(b);
 4
 5// write little-endian 32-bit integer
 6dv.setUint32(0, 0x12345678, true);
 7
 8// read as little-endian and big-endian
 9const little = dv.getUint32(0, true);
10const big = dv.getUint32(0, false);
11
12console.log("little-endian read:", little.toString(16)); // "12345678"
13console.log("big-endian read:", big.toString(16));       // different value
  • DataView הוא מנגנון נוח לקריאה וכתיבה מפורטת של בתים. הוא תומך במגוון סוגים כמו מספרים שלמים חתומים/לא חתומים ומספרים עשרוניים, ומאפשר לציין במפורש את סדר הבתים, מה שנחוץ במיוחד במימוש פרוטוקולים בינאריים.

הבדל בין subarray ו-slice

subarray של TypedArray מחזיר תצוגה שמשתפת את הבאפר המקורי, בעוד slice מחזיר עותק חדש. הביצועים ותופעות הלוואי משתנים בהתאם למה שתבחרו.

1const base = new Uint8Array([1, 2, 3, 4, 5]);
2
3const shared = base.subarray(1, 4); // shares underlying buffer
4const copied = base.slice(1, 4);    // copies data
5
6shared[0] = 99;
7console.log("base after shared modification:", base); // shows change
8console.log("copied remains:", copied);              // unaffected
  • אם תשנה את התצוגה המשותפת, גם המערך המקורי ישתנה, דבר שיכול לגרום לתופעות לוואי לא צפויות. אם ברצונך לשמור את המערך המקורי בבטחה, תוכל ליצור עותק מראש באמצעות slice().

העתקת באפרים והמרת טיפוסים (המרה בין TypedArrays)

נסביר כיצד להעתיק נתונים בין TypedArrays ואיך להדביק חלקים בעזרת הפונקציה set.

 1// Copy and convert between typed arrays
 2const src = new Float32Array([1.5, 2.5, 3.5]);
 3const dst = new Uint16Array(src.length);
 4
 5// Convert by mapping (explicit conversion)
 6for (let i = 0; i < src.length; i++) {
 7  dst[i] = Math.round(src[i]); // simple conversion rule
 8}
 9console.log("converted dst:", Array.from(dst)); // [2, 2, 4]
10
11// Using set to copy bytes (requires compatible underlying buffer or same element type)
12const src2 = new Uint8Array([10, 20, 30]);
13const dst2 = new Uint8Array(6);
14dst2.set(src2, 2); // copy src2 into dst2 starting at index 2
15console.log("dst2 after set:", Array.from(dst2)); // [0,0,10,20,30,0]
  • אם הטיפוסים של האיברים שונים, יש לבצע המרת ערכים (כמו עיגול או בדיקת תחום) ולא להעתיק פשוט. set מאפשר העתקה מהירה בין TypedArrays עם אותו טיפוס איברים.

דוגמה מעשית: מנתח פרוטוקול בינארי (מימוש פשוט)

כאן נציג דוגמת מנתח פשוט שקורא נתונים בינאריים בפורמט קבוע: טיפוס של בית אחד, אורך נתון של שני בתים, ומטען (payload) לאחר מכן.

 1// Simple binary message parser:
 2// format: [type: uint8][length: uint16 BE][payload: length bytes]
 3function parseMessages(buffer: ArrayBuffer) {
 4  const dv = new DataView(buffer);
 5  let offset = 0;
 6  const messages: { type: number; payload: Uint8Array }[] = [];
 7
 8  while (offset + 3 <= dv.byteLength) {
 9    const type = dv.getUint8(offset);
10    const length = dv.getUint16(offset + 1, false); // big-endian
11    offset += 3;
12    if (offset + length > dv.byteLength) break; // truncated
13    const payload = new Uint8Array(buffer, offset, length);
14    messages.push({ type, payload });
15    offset += length;
16  }
17
18  return messages;
19}
20
21// Example usage
22const buf = new ArrayBuffer(1 + 2 + 3 + 1 + 2 + 2); // two messages
23const dv = new DataView(buf);
24let off = 0;
25// first message: type=1, length=3, payload=[1,2,3]
26dv.setUint8(off, 1); dv.setUint16(off + 1, 3, false); off += 3;
27new Uint8Array(buf, off, 3).set([1, 2, 3]); off += 3;
28// second message: type=2, length=2, payload=[9,8]
29dv.setUint8(off, 2); dv.setUint16(off + 1, 2, false); off += 3;
30new Uint8Array(buf, off, 2).set([9, 8]);
31
32console.log(parseMessages(buf));
  • בדוגמה זו, הכותרת נקראת על ידי שימוש ב־DataView, וחתך הנתון (payload) נוצר באמצעות Uint8Array. חשוב לבדוק את סדר הבתים ואת אורך הבאפר.

Web API ו-TypedArray (דוגמה: שליפת נתונים בינאריים)

זו דוגמה טיפוסית לטיפול ב-ArrayBuffer שהתקבל מבקשת רשת באמצעות TypedArray.

 1// Example of fetching binary and mapping to typed array
 2async function fetchBinary(url: string) {
 3  const res = await fetch(url);
 4  const ab = await res.arrayBuffer();
 5  const view = new Uint8Array(ab);
 6  // process view...
 7  console.log("received bytes:", view.length);
 8  return view;
 9}
10
11// Usage (call in async context)
12// fetchBinary("/path/to/file.bin");
  • ניתן להעביר ישירות את ה-ArrayBuffer שהתקבל בעזרת Response.arrayBuffer() ל-TypedArray. משתמשים בו עבור תמונות, שמע, או פרוטוקולים בינאריים מותאמים.

טיפים לביצועים ומכשולים נפוצים

להלן מספר טיפים לביצועים ומכשולים נפוצים שכדאי לדעת בשימוש ב-TypedArrays:.

  • הימנעו מהעתקה מיותרת לעיבוד נתונים גדולים ביעילות, ניתן להקטין העתקות מיותרות על ידי יצירת תצוגות חלקיות בעזרת subarray או שיתוף אותו ArrayBuffer במספר תצוגות.

  • היזהרו עם סדר הבתים (Endianness) בתקשורת רשת או פורמטי קבצים, לרוב סדר הבתים מוגדר מראש. שימוש ב-DataView מאפשר לציין במפורש את סדר הבתים בעת קריאה וכתיבה, וכך להימנע מפרשנות שגויה של הנתונים.

  • היו מודעים לטווחי הערכים של כל טיפוס לדוגמה, Uint8 יכול לייצג רק ערכים בין 0 ל-255. אם תזין ערך שלילי, עלולה להתרחש קיטום או גלישת ערך, לכן עליך להגדיר כללי המרה לפי הצורך.

  • חשבו על עומס על מנגנון איסוף הזבל (garbage collection) יצירה מחדש של ArrayBuffers גדולים לעיתים קרובות מגדילה את עומס ניהול הזיכרון. במצבים בהם הביצועים קריטיים, כדאי לשקול לעצב את הקוד כך שישתמש במאגרים קיימים (buffers) ככל האפשר.

סיכום

TypedArray הוא מנגנון לטיפול בנתונים בינאריים במהירות וביעילות. על ידי שילוב ArrayBuffer, שמקצה אזור בתים באורך קבוע, עם TypedArray או DataView שמבצעים קריאות וכתיבות בטיפוסים מסוימים, אפשר לבצע פעולות בינאריות גמישות. בהתאם למקרה השימוש שלך, תוכל לבחור בין שימוש ב־subarray/slice או DataView, ולעצב את המימוש שלך תוך תשומת לב לסדר בתים (endianness) ולהעתקות.

תוכלו לעקוב אחר המאמר שלמעלה באמצעות Visual Studio Code בערוץ היוטיוב שלנו. נא לבדוק גם את ערוץ היוטיוב.

YouTube Video