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

บทนำ

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

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 เราจะอ่านส่วนหัวและใช้มุมมองอาร์เรย์ที่มีการจัดประเภทสำหรับอาร์เรย์ประเภท float ได้

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 หนึ่งๆ นั้นขึ้นอยู่กับความชอบล้วนๆ เนื่องจากทั้ง 2 แบบมีข้อดีที่แตกต่างกัน อันที่จริงแล้ว CPU บางรุ่นสามารถกําหนดค่าให้รองรับทั้งข้อมูล Big Endian และ Little Endian ได้

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

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

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

อีกตัวอย่างหนึ่งมาจากการสาธิตการแสดงผล High Dynamic Range ในโปรเจ็กต์ตัวอย่าง 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 ต่างๆ อย่างเหมาะสมเพื่อสร้างรูปแบบไฟล์ที่คุณกำลังสร้างหรือใช้

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

Browser 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 ได้ จากนั้นคุณสามารถแนบมุมมองอาร์เรย์ที่กําหนดประเภทและ DataView ไปยังบัฟเฟอร์เพื่อดําเนินการกับเนื้อหาได้

reader.readAsArrayBuffer(file);

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

วัตถุที่โอนได้

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

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

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 ใช้ชิม DataView สําหรับเบราว์เซอร์ทั้งหมด DataView เคยเป็นฟีเจอร์ของ WebKit เท่านั้น แต่ตอนนี้เบราว์เซอร์อื่นๆ ส่วนใหญ่รองรับแล้ว ทีมนักพัฒนาซอฟต์แวร์ของ Mozilla กำลังดำเนินการเปิดตัวแพตช์เพื่อเปิดใช้ DataView ใน Firefox ด้วย

Eric Bidelman จากทีมสัมพันธ์นักพัฒนาซอฟต์แวร์ของ Chrome ได้เขียนตัวอย่างโปรแกรมอ่านแท็ก ID3 ของ MP3 ขนาดเล็กที่ใช้ 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 พิกเซลในด้านยาว เมื่อใช้ int 12 บิตแทน int 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 เป็นไลบรารีอาร์เรย์ที่มีการจัดประเภทซึ่งอ่านและเขียนค่าสเกลาร์ สตริง อาร์เรย์ และโครงสร้างของข้อมูลจาก ArrayBuffer ในลักษณะไฟล์

ตัวอย่างการอ่านอาร์เรย์ของตัวเลขทศนิยมจาก 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 ของไฟล์และเครือข่าย โดยข้อมูลจะมีการจัดเรียงข้อมูลแบบ Big Endian หรือ Little Endian ที่ระบุเสมอ และอาจไม่ได้จัดแนวเพื่อให้ได้ประสิทธิภาพสูงสุด

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

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