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

บทนำ

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

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

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

พื้นฐานในการใช้อาร์เรย์ที่พิมพ์

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

หากต้องการใช้อาร์เรย์ที่กําหนดประเภท คุณต้องสร้าง ArrayBuffer และมุมมองสําหรับ 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 เดียวกัน ดังนั้นเมื่อคุณทราบวิธีใช้เครื่องมือใดเครื่องมือหนึ่งแล้ว ก็จะทราบวิธีใช้เครื่องมืออื่นๆ ทั้งหมดด้วย เราจะสร้างมุมมองอาร์เรย์ที่มีการจัดประเภทที่มีอยู่แต่ละรายการในตัวอย่างถัดไป

// 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 ที่ได้รับข้อมูลภายนอกมักจะจัดการกับ ArrayBuffer ดังนั้นนี่คือวิธีรับมุมมองอาร์เรย์ที่มีการจัดประเภทสำหรับ API เหล่านั้น

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 ไปยังบัฟเฟอร์ของมุมมอง และใช้ set เพื่อคัดลอกข้อมูล

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

หากต้องการใช้ ArrayBuffer ที่มีข้อมูลประเภทต่างๆ วิธีที่ง่ายที่สุดคือการใช้ 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 Endian หากค่าในบัฟเฟอร์เป็น Little Endian คุณสามารถส่งพารามิเตอร์ littleEndian (ไม่บังคับ) ไปยังตัวรับข้อมูลได้ ดังนี้

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

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

DataView ยังมีวิธีการเขียนค่าลงในบัฟเฟอร์ด้วย Setter เหล่านี้ตั้งชื่อในลักษณะเดียวกับ Getter โดยนำ "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 บางรุ่นสามารถกําหนดค่าให้รองรับทั้งข้อมูล Big-endian และ Little-endian ได้

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

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

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

มาดูตัวอย่างง่ายๆ 2 ตัวอย่างกัน รูปแบบไฟล์ Windows BMP เคยเป็นรูปแบบมาตรฐานสำหรับจัดเก็บรูปภาพในช่วงแรกๆ ของ Windows เอกสารประกอบที่ลิงก์ไว้ด้านบนระบุไว้อย่างชัดเจนว่าค่าจำนวนเต็มทั้งหมดในไฟล์จัดเก็บในรูปแบบ Little Endian ต่อไปนี้คือข้อมูลโค้ดที่แยกวิเคราะห์ส่วนเริ่มต้นของส่วนหัว 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();
  // ...
}

อีกตัวอย่างหนึ่งคือจากการสาธิตการแสดงผลช่วงไดนามิกสูงในโปรเจ็กต์ตัวอย่าง WebGL การแสดงตัวอย่างนี้จะดาวน์โหลดข้อมูลจุดลอยตัวแบบ Little Endian ดิบซึ่งแสดงถึงพื้นผิวแบบ High Dynamic Range และต้องอัปโหลดไปยัง 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 1 ครั้ง อ่านค่าตัวเลขแต่ละค่าและจัดเก็บไว้ในโครงสร้างข้อมูลอื่น เช่น ออบเจ็กต์ JavaScript (สำหรับ Structured Data ปริมาณน้อย) หรือมุมมองอาร์เรย์ที่มีการจัดประเภท (สำหรับบล็อกข้อมูลขนาดใหญ่) วิธีนี้จะช่วยให้มั่นใจได้ว่าโค้ดจะทํางานได้อย่างถูกต้องใน CPU ทุกประเภท นอกจากนี้ ให้ใช้ DataView เพื่อเขียนข้อมูลลงในไฟล์หรือเครือข่าย และตรวจสอบว่าได้ระบุอาร์กิวเมนต์ littleEndian ให้กับเมธอด set ต่างๆ อย่างเหมาะสมเพื่อสร้างรูปแบบไฟล์ที่คุณกำลังสร้างหรือใช้

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

API ของเบราว์เซอร์ที่ใช้ประเภทอาร์เรย์

เราจะอธิบายภาพรวมคร่าวๆ ของ API เบราว์เซอร์ต่างๆ ที่ใช้อาร์เรย์ที่กําหนดประเภทอยู่ในปัจจุบัน รายการปัจจุบันมี WebGL, Canvas, Web Audio API, XMLHttpRequest, WebSocket, Web Worker, Media Source API และ File API จากรายการ API คุณจะเห็นว่าอาร์เรย์ที่กําหนดประเภทเหมาะสําหรับงานมัลติมีเดียที่ไวต่อประสิทธิภาพ รวมถึงการส่งข้อมูลอย่างมีประสิทธิภาพ

WebGL

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

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

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

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
);

นอกจากนี้ คุณต้องใช้อาร์เรย์ที่กําหนดประเภทเพื่ออ่านพิกเซลจากบริบท 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 ให้ทำงานร่วมกับข้อกำหนดของอาร์เรย์ที่กําหนดประเภท ตอนนี้คุณจึงรับการแสดงผลอาร์เรย์ที่กําหนดประเภทของพิกเซลในองค์ประกอบ Canvas ได้ ซึ่งมีประโยชน์เนื่องจากตอนนี้คุณยังสร้างและแก้ไขอาร์เรย์พิกเซลของ Canvas ได้โดยไม่ต้องยุ่งยากกับองค์ประกอบ Canvas

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

XMLHttpRequest2

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

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

xhr.responseType = 'arraybuffer';

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

File API

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

reader.readAsArrayBuffer(file);

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

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

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

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

worker.webkitPostMessage(oneGBTypedArray, [oneGBTypedArray]);

หากต้องการรับออบเจ็กต์กลับจากผู้ปฏิบัติงาน ผู้ปฏิบัติงานสามารถส่งออบเจ็กต์กลับไปยังชุดข้อความหลักในลักษณะเดียวกัน

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

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

Media Source API

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

video.webkitSourceAppend(uint8Array);

WebSocket แบบไบนารี

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

socket.binaryType = 'arraybuffer';

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

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

jDataView

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

Eric Bidelman จากทีม Chrome Developer Relations เขียนตัวอย่างโปรแกรมอ่านแท็ก 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.
}

stringencoding

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

ตัวอย่างการใช้งานพื้นฐานของ stringencoding มีดังนี้

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 บิตที่ออฟเซตบิตที่กำหนดเอง

เลขทศนิยม 12 บิตเหมาะสําหรับการทํางานกับพิกัดหน้าจอ เนื่องจากจอแสดงผลมักจะมีพิกเซลน้อยกว่า 4096 พิกเซลในด้านยาว การใช้ ints 12 บิตแทน ints 32 บิต สามารถลดขนาดลง 62% ตัวอย่างที่รุนแรงกว่านั้นคือฉันทำงานกับ Shapefile ที่ใช้ตัวเลขทศนิยม 64 บิตสำหรับพิกัด แต่ฉันไม่ต้องการความแม่นยำเนื่องจากโมเดลจะแสดงในขนาดหน้าจอเท่านั้น การเปลี่ยนไปใช้พิกัดฐาน 12 บิตที่มีเดลต้า 6 บิตเพื่อเข้ารหัสการเปลี่ยนแปลงจากพิกัดก่อนหน้าทำให้ไฟล์มีขนาดเล็กลงถึง 1 ใน 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 คือการอ่านข้อมูลที่มีลักษณะเป็นโครงสร้างจากบัฟเฟอร์นั้นค่อนข้างยุ่งยาก DataView เหมาะสำหรับการอ่านตัวเลขทีละ 2-3 ตัวในลักษณะที่ปลอดภัยต่อ Endian ส่วนมุมมองอาร์เรย์ที่มีประเภทเหมาะสำหรับการอ่านอาร์เรย์ของตัวเลข Endian ดั้งเดิมที่ปรับขนาดองค์ประกอบ สิ่งที่เรารู้สึกว่าขาดหายไปคือวิธีอ่านอาร์เรย์และโครงสร้างข้อมูลในลักษณะที่ปลอดภัยต่อ Endian ได้อย่างสะดวก ป้อน DataStream.js

DataStream.js เป็นไลบรารีประเภท Arrays ที่อ่านและเขียนสเกลาร์ สตริง อาร์เรย์ และโครงสร้างของข้อมูลจาก ArrayBuffers ในรูปแบบเหมือนไฟล์

ตัวอย่างการอ่านอาร์เรย์ของตัวเลขทศนิยมจาก 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 จะรับอาร์เรย์คําจํากัดความของโครงสร้างซึ่งมีประเภทของสมาชิกโครงสร้าง ฟังก์ชันนี้มีฟังก์ชันการเรียกกลับสำหรับจัดการประเภทที่ซับซ้อน รวมถึงจัดการอาร์เรย์ของข้อมูลและโครงสร้างที่ฝังอยู่ด้วย

// 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
]);

ดังที่คุณเห็น การกำหนดโครงสร้างคืออาร์เรย์แบบแบนของคู่ [ชื่อ ประเภท] โครงสร้างที่ฝังอยู่จะสร้างขึ้นโดยใช้อาร์เรย์สำหรับประเภทนั้น อาร์เรย์จะกำหนดโดยใช้อาร์เรย์ 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

ประวัติของอาร์เรย์ที่พิมพ์

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

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

ข้อควรพิจารณาเกี่ยวกับการออกแบบ

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

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

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

ข้อมูลอ้างอิง