อาร์เรย์ที่พิมพ์ - ข้อมูลไบนารีในเบราว์เซอร์

เกริ่นนำ

อาร์เรย์ประเภทเป็นส่วนเพิ่มเติมที่เพิ่งเพิ่มเข้ามาในเบราว์เซอร์ เกิดจากความต้องการวิธีการที่มีประสิทธิภาพในการจัดการข้อมูลไบนารีใน WebGL Typed Array คือแผ่นหน่วยความจำที่มีมุมมองที่พิมพ์ลงไป คล้ายกับการทำงานของอาร์เรย์ใน C เนื่องจาก Typed Array ได้รับการสนับสนุนจากหน่วยความจำดิบ เครื่องมือ JavaScript จึงสามารถส่งหน่วยความจำไปยังไลบรารีแบบเนทีฟโดยตรงโดยไม่ต้องแปลงข้อมูลไปเป็นการนำเสนอแบบเนทีฟอย่างละเอียด ดังนั้น อาร์เรย์ที่พิมพ์จึงทำงานได้ดีกว่าอาร์เรย์ JavaScript ในการส่งข้อมูลไปยัง WebGL และ API อื่นๆ ที่เกี่ยวข้องกับข้อมูลไบนารี

มุมมองอาร์เรย์ที่พิมพ์จะทำหน้าที่เหมือนอาร์เรย์ประเภทเดียวต่อกลุ่มของ ArrayBuffer จะมีมุมมองสำหรับตัวเลขประเภทปกติทั้งหมด โดยมีชื่อที่สื่อความหมายในตัว เช่น Float32Array, Float64Array, Int32Array และ Uint8Array นอกจากนี้ ยังมีมุมมองพิเศษที่แทนที่ประเภทอาร์เรย์พิกเซลใน ImageData ของ Canvas: Uint8ClampedArray

DataView เป็นข้อมูลพร็อพเพอร์ตี้ประเภทที่ 2 และมีไว้สำหรับจัดการข้อมูลที่หลากหลาย แทนที่จะใช้ API ที่มีลักษณะคล้ายอาร์เรย์ ออบเจ็กต์ DataView จะให้ API get/set แก่คุณเพื่ออ่านและเขียนประเภทข้อมูลที่กำหนดเองที่ออฟเซ็ตไบต์ที่กำหนดเอง DataView เหมาะสำหรับการอ่านและเขียนส่วนหัวของไฟล์และข้อมูลอื่นๆ ที่มีลักษณะเหมือนโครงสร้าง

หลักพื้นฐานของการใช้ Typed Arrays

มุมมองอาร์เรย์ที่พิมพ์

หากต้องการใช้ Typed Arrays คุณต้องสร้าง ArrayBuffer และมุมมอง วิธีที่ง่ายที่สุดคือการสร้างมุมมองอาร์เรย์แบบพิมพ์ของขนาดและประเภทที่ต้องการ

// Typed array views work pretty much like normal arrays.
var f64a = new Float64Array(8);
f64a[0] = 10;
f64a[1] = 20;
f64a[2] = f64a[0] + f64a[1];

มุมมองอาร์เรย์ที่พิมพ์มีหลายประเภท ทุกแอปใช้ API เดียวกัน ดังนั้นเมื่อคุณรู้วิธีใช้ คุณก็จะรู้วิธีใช้ API ทั้งหมด ในตัวอย่างถัดไป ฉันจะสร้างมุมมองอาร์เรย์แบบพิมพ์ที่มีอยู่แต่ละแบบในปัจจุบัน

// Floating point arrays.
var f64 = new Float64Array(8);
var f32 = new Float32Array(16);

// Signed integer arrays.
var i32 = new Int32Array(16);
var i16 = new Int16Array(32);
var i8 = new Int8Array(64);

// Unsigned integer arrays.
var u32 = new Uint32Array(16);
var u16 = new Uint16Array(32);
var u8 = new Uint8Array(64);
var pixels = new Uint8ClampedArray(64);

กฎสุดท้ายมีความพิเศษเล็กน้อย โดยยึดค่าอินพุตระหว่าง 0 ถึง 255 ซึ่งมีประโยชน์มากโดยเฉพาะสำหรับอัลกอริทึมการประมวลผลรูปภาพของ Canvas เนื่องจากตอนนี้คุณไม่ต้องคิดเลขคณิตศาสตร์การประมวลผลรูปภาพด้วยตนเองเพื่อหลีกเลี่ยงการเกิดช่วง 8 บิตแล้ว

ตัวอย่างเช่น วิธีนำตัวประกอบแกมมาไปใช้กับรูปภาพที่เก็บไว้ใน Uint8Array ไม่ค่อยสวย:

u8[i] = Math.min(255, Math.max(0, u8[i] * gamma));

เมื่อใช้ Uint8ClampedArray คุณจะข้ามการบีบด้วยตัวเองได้ดังนี้

pixels[i] *= gamma;

อีกวิธีหนึ่งในการสร้างมุมมองอาร์เรย์ที่พิมพ์คือการสร้าง ArrayBuffer ก่อน แล้วจึงสร้างมุมมองที่ชี้ไปยังมุมมองดังกล่าว โดยปกติแล้ว API ที่รับข้อมูลภายนอกของคุณจะจัดการกับ ArrayBuffers ดังนั้นนี่คือวิธีการที่คุณจะดูมุมมองอาร์เรย์แบบพิมพ์ให้กับรายการเหล่านั้น

var ab = new ArrayBuffer(256); // 256-byte ArrayBuffer.
var faFull = new Uint8Array(ab);
var faFirstHalf = new Uint8Array(ab, 0, 128);
var faThirdQuarter = new Uint8Array(ab, 128, 64);
var faRest = new Uint8Array(ab, 192);

คุณยังดูข้อมูลหลายมุมมองใน ArrayBuffer เดียวกันได้

var fa = new Float32Array(64);
var ba = new Uint8Array(fa.buffer, 0, Float32Array.BYTES_PER_ELEMENT); // First float of fa.

หากต้องการคัดลอกอาร์เรย์ที่พิมพ์ไปยังอาร์เรย์ที่พิมพ์ประเภทอื่น วิธีที่เร็วที่สุดคือการใช้เมธอดชุดอาร์เรย์ที่พิมพ์ สำหรับการใช้งานแบบ Memcpy ให้สร้าง Uint8Arrays ไปยังบัฟเฟอร์ของมุมมอง และใช้ชุดเพื่อคัดลอกข้อมูล

function memcpy(dst, dstOffset, src, srcOffset, length) {
  var dstU8 = new Uint8Array(dst, dstOffset, length);
  var srcU8 = new Uint8Array(src, srcOffset, length);
  dstU8.set(srcU8);
};

DataView

หากต้องการใช้ ArrayBuffers ที่มีข้อมูลซึ่งมีประเภทที่หลากหลาย วิธีที่ง่ายที่สุดคือการใช้ DataView เป็นบัฟเฟอร์ สมมติว่าเรามีรูปแบบไฟล์ที่มีส่วนหัวเป็น Int แบบไม่ลงชื่อเข้าใช้แบบ 8 บิต ตามด้วย int 16 บิต 2 ชุด ตามด้วยอาร์เรย์เพย์โหลดของ Float 32 บิต การอ่านนี้อีกครั้งด้วยมุมมองอาร์เรย์ที่พิมพ์นั้นสามารถทำได้ แต่ก็จะยุ่งยากเล็กน้อย DataView ทำให้เราอ่านส่วนหัวและใช้มุมมองอาร์เรย์แบบพิมพ์สำหรับอาร์เรย์แบบลอยได้

var dv = new DataView(buffer);
var vector_length = dv.getUint8(0);
var width = dv.getUint16(1); // 0+uint8 = 1 bytes offset
var height = dv.getUint16(3); // 0+uint8+uint16 = 3 bytes offset
var vectors = new Float32Array(width*height*vector_length);
for (var i=0, off=5; i<vectors.length; i++, off+=4) {
  vectors[i] = dv.getFloat32(off);
}

ในตัวอย่างด้านบน ค่าทั้งหมดที่ฉันอ่านคือ Big-end หากค่าในบัฟเฟอร์เป็นส่วนน้อยนิด คุณสามารถส่งพารามิเตอร์ LittleEndian ที่ไม่บังคับไปยัง Getter ได้ดังนี้

...
var width = dv.getUint16(1, true);
var height = dv.getUint16(3, true);
...
vectors[i] = dv.getFloat32(off, true);
...

โปรดทราบว่ามุมมองอาร์เรย์แบบพิมพ์จะอยู่ในลำดับไบต์แบบเนทีฟเสมอ เนื่องจากจะทำให้โหลดได้เร็ว คุณควรใช้ DataView เพื่ออ่านและเขียนข้อมูลที่อาจเป็นปัญหาสำหรับปลายทาง

DataView ยังมีวิธีการเขียนค่าลงในบัฟเฟอร์ด้วย ตัวตั้งค่าเหล่านี้ตั้งชื่อในลักษณะเดียวกับ Getters "set" ตามด้วยประเภทข้อมูล

dv.setInt32(0, 25, false); // set big-endian int32 at byte offset 0 to 25
dv.setInt32(4, 25); // set big-endian int32 at byte offset 4 to 25
dv.setFloat32(8, 2.5, true); // set little-endian float32 at byte offset 8 to 2.5

พูดคุยเกี่ยวกับความไม่แน่นอน

Endianness หรือลำดับไบต์ คือลำดับที่จัดเก็บหมายเลขหลายไบต์ในหน่วยความจำของคอมพิวเตอร์ คำว่า big-endian อธิบายสถาปัตยกรรม CPU ซึ่งเก็บไบต์ที่สำคัญที่สุดไว้ก่อน หรือ little-endian คือไบต์ที่มีนัยสำคัญน้อยที่สุดก่อน ปลายทางใดที่ใช้ในสถาปัตยกรรม CPU หนึ่งๆ เป็นไปโดยอิสระ ก็มีเหตุผลดีๆ ที่จะเลือกอย่างใดอย่างหนึ่ง อันที่จริงแล้ว CPU บางตัวสามารถกำหนดค่าให้รองรับทั้งข้อมูลจากบริษัทขนาดใหญ่และข้อมูลระดับล่างๆ ได้

ทำไมคุณจึงต้องกังวลเรื่องปัญหาครอบครัว เหตุผลก็ง่ายดาย ขณะอ่านหรือเขียนข้อมูลจากดิสก์หรือเครือข่าย คุณต้องระบุจุดสิ้นสุดของข้อมูล วิธีนี้ช่วยให้มั่นใจได้ว่าข้อมูลจะได้รับการแปลอย่างถูกต้อง โดยไม่คำนึงถึงปลายทางของ CPU ที่ทำงานกับข้อมูล ในโลกที่มีเครือข่ายเพิ่มขึ้นเรื่อยๆ การสนับสนุนอุปกรณ์ทุกชนิดอย่างเหมาะสม ไม่ว่าจะเป็นอุปกรณ์ขนาดใหญ่หรือปลายทางขนาดเล็ก ที่อาจต้องทำงานกับข้อมูลไบนารีที่มาจากเซิร์ฟเวอร์หรือเครื่องอื่นๆ ในเครือข่ายเดียวกัน

อินเทอร์เฟซ DataView ออกแบบมาเพื่ออ่านและเขียนข้อมูลระหว่างไฟล์และเครือข่ายโดยเฉพาะ DataView ดำเนินการกับข้อมูลที่มีปลายทางที่ระบุ ต้องระบุ Endian ในขนาดใหญ่หรือน้อยด้วยการเข้าถึงทุกค่า เพื่อให้แน่ใจว่าคุณจะได้รับผลลัพธ์ที่ถูกต้องและสม่ำเสมอเมื่ออ่านหรือเขียนข้อมูลไบนารี ไม่ว่า CPU ที่เบราว์เซอร์ทำงานจะอยู่ที่ใดก็ตาม

โดยปกติเมื่อแอปพลิเคชันของคุณอ่านข้อมูลไบนารีจากเซิร์ฟเวอร์ คุณจะต้องสแกนผ่านเพียงครั้งเดียวเพื่อแปลงเป็นโครงสร้างข้อมูลที่แอปพลิเคชันใช้ภายใน ควรใช้ DataView ในระยะนี้ ไม่ควรจะใช้มุมมองอาร์เรย์ที่พิมพ์หลายไบต์ (Int16Array, Uint16Array ฯลฯ) โดยตรงกับข้อมูลที่ดึงผ่าน XMLHttpRequest, FileReader หรือ API อินพุต/เอาต์พุตอื่นๆ เนื่องจากมุมมองอาร์เรย์ที่พิมพ์ใช้ปลายทางดั้งเดิมของ CPU ข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ภายหลัง

เรามาดูตัวอย่างง่ายๆ สัก 2 ข้อกัน รูปแบบไฟล์ Windows BMP เคยเป็นรูปแบบมาตรฐานสำหรับการเก็บรูปภาพในยุคแรกๆ ของ Windows เอกสารที่ลิงก์ข้างต้นระบุไว้อย่างชัดเจนว่าค่าจำนวนเต็มทั้งหมดในไฟล์ได้รับการจัดเก็บในรูปแบบที่จบลงเล็กน้อย ต่อไปนี้คือข้อมูลโค้ดที่แยกวิเคราะห์ช่วงต้นของส่วนหัว BMP โดยใช้ไลบรารี DataStream.js ที่มาพร้อมกับบทความนี้

function parseBMP(arrayBuffer) {
  var stream = new DataStream(arrayBuffer, 0,
    DataStream.LITTLE_ENDIAN);
  var header = stream.readUint8Array(2);
  var fileSize = stream.readUint32();
  // Skip the next two 16-bit integers
  stream.readUint16();
  stream.readUint16();
  var pixelOffset = stream.readUint32();
  // Now parse the DIB header
  var dibHeaderSize = stream.readUint32();
  var imageWidth = stream.readInt32();
  var imageHeight = stream.readInt32();
  // ...
}

นี่คืออีกตัวอย่างหนึ่งจากการสาธิตการแสดงผลแบบ High Dynamic Range ในโปรเจ็กต์ตัวอย่าง WebGL การสาธิตนี้จะดาวน์โหลดข้อมูลจุดลอยตัวเล็กๆ ที่เป็นข้อมูลดิบที่แสดงถึงพื้นผิวที่มีช่วงไดนามิกสูง และต้องการอัปโหลดไปยัง WebGL นี่คือข้อมูลโค้ดที่แปลค่าจุดทศนิยมในสถาปัตยกรรม CPU ทั้งหมดอย่างเหมาะสม สมมติว่าตัวแปร “arrayBuffer” เป็น ArrayBuffer ที่เพิ่งดาวน์โหลดจากเซิร์ฟเวอร์ผ่าน XMLHttpRequest ดังนี้

var arrayBuffer = ...;
var data = new DataView(arrayBuffer);
var tempArray = new Float32Array(
  data.byteLength / Float32Array.BYTES_PER_ELEMENT);
var len = tempArray.length;
// Incoming data is raw floating point values
// with little-endian byte ordering.
for (var jj = 0; jj < len; ++jj) {
  tempArray[jj] =
    data.getFloat32(jj * Float32Array.BYTES_PER_ELEMENT, true);
}
gl.texImage2D(...other arguments...,
  gl.RGB, gl.FLOAT, tempArray);

กฎทั่วไปคือเมื่อรับข้อมูลไบนารีจากเว็บเซิร์ฟเวอร์ ให้ส่งต่อข้อมูลด้วย DataView อ่านค่าตัวเลขแต่ละค่าและจัดเก็บไว้ในโครงสร้างข้อมูลอื่นๆ ไม่ว่าจะเป็นออบเจ็กต์ JavaScript (สำหรับ Structured Data จำนวนน้อย) หรือมุมมองอาร์เรย์ที่พิมพ์ (สำหรับข้อมูลบล็อกขนาดใหญ่) วิธีนี้จะทำให้โค้ดทำงานได้อย่างถูกต้องใน CPU ทุกประเภท นอกจากนี้ ให้ใช้ DataView เพื่อเขียนข้อมูลลงในไฟล์หรือเครือข่าย และตรวจสอบว่าได้ระบุอาร์กิวเมนต์ littleEndian ในเมธอด set ต่างๆ อย่างเหมาะสมเพื่อสร้างรูปแบบไฟล์ที่คุณกำลังสร้างหรือใช้งาน

อย่าลืมว่าข้อมูลทั้งหมดที่ส่งผ่านเครือข่ายโดยปริยายนั้นมีรูปแบบและจุดสิ้นสุด (อย่างน้อยก็สำหรับค่าแบบหลายไบต์) อย่าลืมระบุและบันทึกรูปแบบของข้อมูลทั้งหมดที่แอปพลิเคชันส่งผ่านเครือข่ายอย่างชัดเจน

API ของเบราว์เซอร์ที่ใช้ Typed Arrays

ผมจะให้ภาพรวมคร่าวๆ เกี่ยวกับ API ของเบราว์เซอร์ต่างๆ ที่กำลังใช้ Typed Arrays การครอบตัดปัจจุบันประกอบด้วย WebGL, Canvas, Web Audio API, XMLHttpRequests, WebSockets, Web Workers, Media Source API และ File API จากรายชื่อ API คุณจะเห็นว่า Typed Array นั้นเหมาะสมอย่างยิ่งสำหรับการทำงานมัลติมีเดียที่เน้นประสิทธิภาพ รวมถึงการส่งข้อมูลอย่างมีประสิทธิภาพ

WebGL

การใช้ Typed Arrays ครั้งแรกคือใน WebGL ซึ่งใช้เพื่อส่งต่อข้อมูลบัฟเฟอร์และข้อมูลภาพ หากต้องการตั้งค่าเนื้อหาของออบเจ็กต์บัฟเฟอร์ WebGL ให้ใช้การเรียกใช้ gl.bufferData() กับ Typed Array

var floatArray = new Float32Array([1,2,3,4,5,6,7,8]);
gl.bufferData(gl.ARRAY_BUFFER, floatArray);

และยังใช้อาร์เรย์ประเภทเพื่อส่งต่อข้อมูลพื้นผิว ต่อไปนี้เป็นตัวอย่างพื้นฐานของการส่งเนื้อหาพื้นผิวโดยใช้ Typed Array

var pixels = new Uint8Array(16*16*4); // 16x16 RGBA image
gl.texImage2D(
  gl.TEXTURE_2D, // target
  0, // mip level
  gl.RGBA, // internal format
  16, 16, // width and height
  0, // border
  gl.RGBA, //format
  gl.UNSIGNED_BYTE, // type
  pixels // texture data
);

คุณต้องใช้ Typed Arrays ในการอ่านพิกเซลจากบริบท WebGL ด้วย

var pixels = new Uint8Array(320*240*4); // 320x240 RGBA image
gl.readPixels(0, 0, 320, 240, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

Canvas 2 มิติ

เมื่อเร็วๆ นี้ มีการพัฒนาออบเจ็กต์ Canvas ImageData ขึ้นเพื่อให้ใช้งานร่วมกับข้อกำหนด Typed Arrays ได้ ตอนนี้คุณสามารถรับการแสดง Typed Arrays ของพิกเซลในองค์ประกอบ Canvas ได้แล้ว ซึ่งจะเป็นประโยชน์เนื่องจากตอนนี้คุณสามารถสร้างและแก้ไขอาร์เรย์พิกเซล Canvas ได้โดยไม่ต้องมายุ่งกับองค์ประกอบ Canvas แล้ว

var imageData = ctx.getImageData(0,0, 200, 100);
var typedArray = imageData.data // data is a Uint8ClampedArray

XMLHttpRequest2

XMLHttpRequest ได้รับการบูสต์ Typed Array แล้ว และตอนนี้คุณสามารถรับการตอบกลับ Typed Array ได้เลยโดยไม่ต้องแยกวิเคราะห์สตริง JavaScript เป็น Typed Array การทำเช่นนี้มีประโยชน์มากสำหรับการส่งต่อข้อมูลที่ดึงมาไปยัง API มัลติมีเดียโดยตรง และสำหรับการแยกวิเคราะห์ไฟล์ไบนารีที่ดึงมาจากเครือข่าย

ทั้งหมดที่คุณต้องทำคือ ตั้งค่า responseType ของออบเจ็กต์ XMLHttpRequest เป็น "arraybuffer"

xhr.responseType = 'arraybuffer';

โปรดทราบว่าคุณต้องตระหนักถึงปัญหาปลายทางเมื่อดาวน์โหลดข้อมูลจากเครือข่าย ดูส่วนเกี่ยวกับปลายทางด้านบน

API ของไฟล์

FileReader สามารถอ่านเนื้อหาของไฟล์เป็น ArrayBuffer จากนั้นคุณสามารถแนบมุมมองอาร์เรย์ที่พิมพ์และ DataViews เข้ากับบัฟเฟอร์เพื่อจัดการกับเนื้อหาได้

reader.readAsArrayBuffer(file);

คุณควรคำนึงถึงปลายทางที่นี่เช่นกัน ดูรายละเอียดได้ที่ส่วน Endianness

ออบเจ็กต์ที่โอนได้

ออบเจ็กต์ที่โอนได้ใน postMessage ทำให้การส่งข้อมูลไบนารีไปยังหน้าต่างอื่นและ Web Worker เร็วขึ้นอย่างมาก เมื่อคุณส่งออบเจ็กต์ให้ผู้ปฏิบัติงานเป็นแบบ "โอนได้" ออบเจ็กต์ดังกล่าวจะไม่สามารถเข้าถึงได้ในเทรดการส่ง และผู้ปฏิบัติงานที่เป็นผู้รับจะได้รับสิทธิ์การเป็นเจ้าของออบเจ็กต์ วิธีนี้เหมาะสำหรับการใช้งานที่เพิ่มประสิทธิภาพอย่างมาก โดยจะไม่มีการคัดลอกข้อมูลที่ส่ง จะมีเพียงการเป็นเจ้าของ Typed Array เท่านั้นที่จะถูกโอนไปที่ตัวรับ

หากต้องการใช้ออบเจ็กต์ที่โอนได้กับ Web Workers คุณต้องใช้เมธอด webkitPostMessage ในผู้ปฏิบัติงาน เมธอด webkitPostMessage ทำงานเหมือนกับ postMessage แต่ใช้อาร์กิวเมนต์ 2 ตัวแทนที่จะเป็นอาร์กิวเมนต์เดียว อาร์กิวเมนต์ที่ 2 ที่เพิ่มเข้ามาคืออาร์เรย์ของออบเจ็กต์ที่คุณต้องการโอนไปยังผู้ปฏิบัติงาน

worker.webkitPostMessage(oneGBTypedArray, [oneGBTypedArray]);

หากต้องการดึงวัตถุคืนจากผู้ปฏิบัติงาน ผู้ปฏิบัติงานจะส่งวัตถุกลับไปยังเทรดหลักได้ด้วยวิธีเดียวกัน

webkitPostMessage({results: grand, youCanHaveThisBack: oneGBTypedArray}, [oneGBTypedArray]);

ไม่มีสำเนาเลย วู้ว!

API แหล่งที่มาของสื่อ

เมื่อเร็วๆ นี้ องค์ประกอบสื่อบางอย่างก็ดีต่ออาร์เรย์ Typed ในรูปแบบของ Media Source API ด้วย คุณส่งผ่าน Typed Array ที่มีข้อมูลวิดีโอไปยังองค์ประกอบวิดีโอได้โดยตรงโดยใช้ webkitSourceAppend การทำเช่นนี้จะทำให้องค์ประกอบวิดีโอต่อท้ายข้อมูลวิดีโอต่อจากวิดีโอที่มีอยู่ SourceAppend เหมาะสำหรับการทำโฆษณาคั่นระหว่างหน้า เพลย์ลิสต์ สตรีมมิง และการใช้งานอื่นๆ ซึ่งคุณอาจต้องการเล่นวิดีโอหลายรายการโดยใช้องค์ประกอบวิดีโอรายการเดียว

video.webkitSourceAppend(uint8Array);

WebSocket แบบไบนารี

คุณยังสามารถใช้ Typed Arrays กับ WebSockets เพื่อจะได้ไม่ต้องจัดเรียงข้อมูลทั้งหมด เหมาะสำหรับการเขียนโปรโตคอลที่มีประสิทธิภาพและลดการจราจรของข้อมูลในเครือข่าย

socket.binaryType = 'arraybuffer';

ค่อยยังชั่ว! ทั้งหมดนี้เป็นการตรวจสอบ API ต่อไปมาดูไลบรารีของบุคคลที่สามสำหรับจัดการ Typed Arrays กัน

ไลบรารีของบุคคลที่สาม

jDataView

jDataView จะใช้ shim ของ DataView กับเบราว์เซอร์ทั้งหมด DataView เคยเป็นคุณลักษณะที่มีเพียง WebKit เท่านั้น แต่ตอนนี้ได้รับการสนับสนุนโดยเบราว์เซอร์อื่นๆ ส่วนใหญ่แล้ว ทีมนักพัฒนาซอฟต์แวร์ของ Mozilla กำลังดำเนินการแพตช์เพื่อเปิดใช้ DataView ใน Firefox ด้วยเช่นกัน

Eric Bidelman จากทีมนักพัฒนาสัมพันธ์ของ Chrome ได้เขียนตัวอย่างโปรแกรมอ่านแท็ก MP3 ID3 ขนาดเล็กที่ใช้ jDataView ต่อไปนี้เป็นตัวอย่างการใช้งานจากบล็อกโพสต์

var dv = new jDataView(arraybuffer);

// "TAG" starts at byte -128 from EOF.
// See http://en.wikipedia.org/wiki/ID3
if (dv.getString(3, dv.byteLength - 128) == 'TAG') {
  var title = dv.getString(30, dv.tell());
  var artist = dv.getString(30, dv.tell());
  var album = dv.getString(30, dv.tell());
  var year = dv.getString(4, dv.tell());
} else {
  // no ID3v1 data found.
}

การเข้ารหัสสตริง

การทำงานกับสตริงในอาร์เรย์ประเภท Typed อาจยุ่งยากเล็กน้อยในขณะนี้ แต่เรามีไลบรารีสตริงการเข้ารหัสที่ช่วยแก้ไขปัญหาดังกล่าว การเข้ารหัสสตริงใช้ข้อกำหนดการเข้ารหัสสตริง Typed Array ที่เสนอไว้ ดังนั้นจึงเป็นวิธีที่ดีในการทำความเข้าใจสิ่งที่กำลังจะเกิดขึ้นด้วย

ต่อไปนี้เป็นตัวอย่างการใช้งานพื้นฐานของสตริงการเข้ารหัส

var uint8array = new TextEncoder(encoding).encode(string);
var string = new TextDecoder(encoding).decode(uint8array);

BitView.js

ฉันได้เขียนไลบรารีการจัดการเล็กๆ สำหรับอาร์เรย์ประเภทที่เรียกว่า BitView.js มันทำงานคล้ายกับ DataView มากอย่างที่ชื่อบอกไว้ แต่ทำงานกับบิตได้ ด้วย BitView คุณจะสามารถรับและกำหนดค่าของบิตที่ออฟเซ็ตบิตที่กำหนดใน ArrayBuffer ได้ นอกจากนี้ BitView ยังมีวิธีการจัดเก็บและโหลด Int แบบ 6 บิตและ 12 บิตด้วยออฟเซ็ตบิตที่กำหนดเองอีกด้วย

Int แบบ 12 บิตเหมาะสําหรับการทํางานกับพิกัดหน้าจอ เนื่องจากจอแสดงผลมักจะมีพิกเซลน้อยกว่า 4, 096 พิกเซลตามด้านที่ยาวกว่า เมื่อใช้ Int 12 บิตแทน Int 32 บิต คุณจะลดขนาดลง 62% ตัวอย่างที่ชัดเจนมากขึ้นก็คือผมทำงานกับ Shapefile ที่ใช้เลขทศนิยม 64 บิตเป็นพิกัด แต่ฉันไม่ต้องการความแม่นยําเพราะโมเดลจะแสดงที่ขนาดหน้าจอเท่านั้น การเปลี่ยนไปใช้พิกัดฐาน 12 บิตด้วยเดลต้าแบบ 6 บิตเพื่อเข้ารหัสการเปลี่ยนแปลงจากพิกัดก่อนหน้าทำให้ขนาดไฟล์ลดลงเป็น 10 คุณดูการสาธิตดังกล่าวได้ที่นี่

ตัวอย่างการใช้ BitView.js มีดังนี้

var bv = new BitView(arrayBuffer);
bv.setBit(4, 1); // Set fourth bit of arrayBuffer to 1.
bv.getBit(17); // Get 17th bit of arrayBuffer.

bv.getBit(50*8 + 3); // Get third bit of 50th byte in arrayBuffer.

bv.setInt6(3, 18); // Write 18 as a 6-bit int to bit position 3 in arrayBuffer.
bv.getInt12(9); // Read a 12-bit int from bit position 9 in arrayBuffer.

DataStream.js

สิ่งหนึ่งที่น่าตื่นเต้นที่สุดเกี่ยวกับอาร์เรย์ที่พิมพ์คือช่วยให้จัดการไฟล์ไบนารีใน JavaScript ได้ง่ายขึ้น แทนที่จะแยกวิเคราะห์อักขระสตริงทีละอักขระและแปลงอักขระเป็นตัวเลขฐานสองด้วยตนเอง คุณสามารถรับ ArrayBuffer ที่มี XMLHttpRequest และประมวลผลได้โดยตรงโดยใช้ DataView ซึ่งช่วยให้ทำได้ง่ายขึ้น เช่น การโหลดไฟล์ MP3 และอ่านแท็กข้อมูลเมตาสำหรับใช้ในโปรแกรมเล่นเสียงของคุณ หรือโหลดใน Shapefile แล้วเปลี่ยนเป็นโมเดล WebGL หรืออ่านแท็ก EXIF จาก JPEG และแสดงในแอปภาพสไลด์

ปัญหาของ ArrayBuffer XHR คือการอ่านข้อมูลที่เหมือนกับ Struct จากบัฟเฟอร์ค่อนข้างยุ่งยาก DataView เหมาะสำหรับการอ่านตัวเลข 2-3 ตัวพร้อมกันแบบปลอดภัยสำหรับปลายทาง มุมมองอาร์เรย์แบบพิมพ์ดีจะเหมาะสำหรับการอ่านอาร์เรย์ของตัวเลขปลายทางแบบเนทีฟที่มีขนาดตามองค์ประกอบ สิ่งที่เรารู้สึกว่ายังขาดอยู่คือวิธีอ่านแบบอาร์เรย์และโครงสร้างของข้อมูลอย่างสะดวกสบายและปลอดภัย ป้อน DataStream.js

DataStream.js เป็นไลบรารี Typed Arrays ซึ่งอ่านและเขียนสเกลาร์ สตริง อาร์เรย์ และโครงสร้างของข้อมูลจาก ArrayBuffers ในลักษณะคล้ายไฟล์

ตัวอย่างการอ่านค่าในอาร์เรย์ของ Float จาก ArrayBuffer

// without DataStream.js
var dv = new DataView(buffer);
var f32 = new Float32Array(buffer.byteLength / 4);
var littleEndian = true;
for (var i = 0; i<f32.length; i++) {
  f32[i] = dv.getFloat32(i*4, littleEndian);
}

// with DataStream.js
var ds = new DataStream(buffer);
ds.endianness = DataStream.LITTLE_ENDIAN;
var f32 = ds.readFloat32Array(ds.byteLength / 4);

สิ่งที่ DataStream.js มีประโยชน์มากคือการอ่านข้อมูลที่ซับซ้อนมากขึ้น สมมติว่าคุณมีเมธอดที่อ่านเป็นเครื่องหมาย JPEG

// without DataStream.js
var dv = new DataView(buffer);
var objs = [];
for (var i=0; i<buffer.byteLength;) {
  var obj = {};
  obj.tag = dv.getUint16(i);
  i += 2;
  obj.length = dv.getUint16(i);
  i += 2;
  obj.data = new Uint8Array(obj.length - 2);
  for (var j=0; j<obj.data.length; j++,i++) {
    obj.data[j] = dv.getUint8(i);
  }
  objs.push(obj);
}

// with DataStream.js
var ds = new DataStream(buffer);
ds.endianness = ds.BIG_ENDIAN;
var objs = [];
while (!ds.isEof()) {
  var obj = {};
  obj.tag = ds.readUint16();
  obj.length = ds.readUint16();
  obj.data = ds.readUint8Array(obj.length - 2);
  objs.push(obj);
}

หรือใช้เมธอด DataStream.readStruct เพื่ออ่านในโครงสร้างของข้อมูล เมธอด readStruct จะใช้อาร์เรย์คำจำกัดความโครงสร้างที่มีประเภทของสมาชิก Struct มีฟังก์ชันเรียกกลับสำหรับจัดการประเภทที่ซับซ้อน รวมถึงจัดการอาร์เรย์ของข้อมูลและโครงสร้างที่ซ้อนกันด้วย ดังนี้

// with DataStream.readStruct
ds.readStruct([
  'objs', ['[]', [ // objs: array of tag,length,data structs
    'tag', 'uint16',
    'length', 'uint16',
    'data', ['[]', 'uint8', function(s,ds){ return s.length - 2; }], // get length with a function
  '*'] // read in as many struct as there are
]);

คุณจะเห็นได้ว่า คำจำกัดความของโครงสร้าง คืออาร์เรย์แบบแฟลตของคู่ [name, type]- โครงสร้างที่ซ้อนกันนั้นทำงานโดยใช้อาร์เรย์สำหรับประเภท อาร์เรย์กำหนดโดยใช้อาร์เรย์ 3 องค์ประกอบ โดยที่องค์ประกอบที่ 2 คือประเภทองค์ประกอบอาร์เรย์ และองค์ประกอบที่ 3 คือความยาวของอาร์เรย์ (เป็นตัวเลข เป็นการอ้างอิงฟิลด์ที่อ่านก่อนหน้านี้ หรือเป็นฟังก์ชันเรียกกลับ) องค์ประกอบแรกของการกำหนดอาร์เรย์ไม่มีการใช้งาน

ค่าที่เป็นไปได้สำหรับประเภทมีดังนี้

Number types

Unsuffixed number types use DataStream endianness.
To explicitly specify endianness, suffix the type with
'le' for little-endian or 'be' for big-endian,
e.g. 'int32be' for big-endian int32.

  'uint8' -- 8-bit unsigned int
  'uint16' -- 16-bit unsigned int
  'uint32' -- 32-bit unsigned int
  'int8' -- 8-bit int
  'int16' -- 16-bit int
  'int32' -- 32-bit int
  'float32' -- 32-bit float
  'float64' -- 64-bit float

String types

  'cstring' -- ASCII string terminated by a zero byte.
  'string:N' -- ASCII string of length N.
  'string,CHARSET:N' -- String of byteLength N encoded with given CHARSET.
  'u16string:N' -- UCS-2 string of length N in DataStream endianness.
  'u16stringle:N' -- UCS-2 string of length N in little-endian.
  'u16stringbe:N' -- UCS-2 string of length N in big-endian.

Complex types

  [name, type, name_2, type_2, ..., name_N, type_N] -- Struct

  function(dataStream, struct) {} -- Callback function to read and return data.

  {get: function(dataStream, struct) {}, set: function(dataStream, struct) {}}
  -- Getter/setter functions to reading and writing data. Handy for using the
     same struct definition for both reading and writing.

  ['', type, length] -- Array of given type and length. The length can be either
                        a number, a string that references a previously-read
                        field, or a callback function(struct, dataStream, type){}.
                        If length is set to '*', elements are read from the
                        DataStream until a read fails.

ดูตัวอย่างจริงของการอ่านข้อมูลเมตา JPEG ได้ที่นี่ การสาธิตใช้ DataStream.js ในการอ่านโครงสร้างระดับแท็กของไฟล์ JPEG (พร้อมกับการแยกวิเคราะห์ EXIF บางส่วน) และใช้ jpg.js ในการถอดรหัสและแสดงรูปภาพ JPEG ใน JavaScript

ประวัติของอาร์เรย์ประเภท

Typed Arrays เริ่มเปิดใช้งานในระยะแรกของ WebGL เมื่อเราพบว่าการส่งอาร์เรย์ JavaScript ไปยังไดรเวอร์กราฟิกทำให้เกิดปัญหาด้านประสิทธิภาพ เมื่อใช้อาร์เรย์ JavaScript การเชื่อมโยง WebGL จะต้องจัดสรรอาร์เรย์แบบเนทีฟและเติมอาร์เรย์โดยการเดินผ่านอาร์เรย์ JavaScript แล้วแคสต์ออบเจ็กต์ JavaScript ทุกรายการในอาร์เรย์ไปยังประเภทดั้งเดิมที่ต้องการ

ในการแก้ไขจุดคอขวดของ Conversion ข้อมูล Vladimir Vukicevic ของ Mozilla ได้เขียน CanvasFloatArray ซึ่งเป็นอาร์เรย์แบบลอยแบบ C ที่มีอินเทอร์เฟซ JavaScript ตอนนี้คุณสามารถแก้ไข CanvasFloatArray ใน JavaScript และส่งต่อไปยัง WebGL ได้โดยตรงโดยไม่ต้องทำการเชื่อมโยงเพิ่มเติม ในการปรับปรุงต่อไป CanvasFloatArray ได้เปลี่ยนชื่อเป็น WebGLFloatArray ซึ่งเปลี่ยนชื่อเป็น Float32Array และแยกเป็น ArrayBuffer สำรองและมุมมอง Float32Array-View ที่พิมพ์เพื่อเข้าถึงบัฟเฟอร์ และยังเพิ่มประเภทสำหรับจำนวนเต็มและขนาดจุดลอยตัวอื่นๆ รวมถึงตัวแปรที่มี/ไม่มีเครื่องหมายด้วย

ข้อควรพิจารณาในการออกแบบ

ตั้งแต่แรกเริ่ม การออกแบบอาร์เรย์ Typed เกิดจากความต้องการในการส่งข้อมูลไบนารีไปยังไลบรารีแบบเนทีฟอย่างมีประสิทธิภาพ ด้วยเหตุนี้ มุมมองอาร์เรย์ที่พิมพ์จะทำงานกับข้อมูลที่สอดคล้องกันในปลายทางดั้งเดิมของ CPU ของโฮสต์ การตัดสินใจเหล่านี้ทำให้ JavaScript มีประสิทธิภาพสูงสุดในระหว่างการทำงาน เช่น การส่งข้อมูลจุดยอดไปยังการ์ดแสดงผล

DataView ออกแบบมาสำหรับไฟล์และ I/O เครือข่ายโดยเฉพาะ โดยที่ข้อมูลจะมีปลายทางที่ระบุเสมอ และอาจไม่สอดคล้องกันเพื่อประสิทธิภาพสูงสุด

การออกแบบที่แยกระหว่างการประกอบข้อมูลในหน่วยความจำ (โดยใช้มุมมองอาร์เรย์ที่พิมพ์) และ I/O (โดยใช้ DataView) เป็นสิ่งที่ควรทำ เครื่องมือ JavaScript สมัยใหม่เพิ่มประสิทธิภาพมุมมองอาร์เรย์ที่พิมพ์อย่างมาก และให้ประสิทธิภาพการทำงานด้านตัวเลขสูง ระดับประสิทธิภาพปัจจุบันของมุมมองอาร์เรย์ที่พิมพ์นั้นเกิดขึ้นได้ด้วยการตัดสินใจออกแบบนี้

รายการอ้างอิง