टाइप की गई अरे - ब्राउज़र में बाइनरी डेटा

Ilmari Heikkinen

टाइप किए गए ऐरे, ब्राउज़र में हाल ही में जोड़े गए हैं. इन्हें WebGL में बाइनरी डेटा को बेहतर तरीके से मैनेज करने की ज़रूरत के हिसाब से बनाया गया है. टाइप किया गया कलेक्शन, मेमोरी का एक स्लैब होता है. इसमें टाइप किया गया व्यू होता है. यह C में कलेक्शन के काम करने के तरीके से काफ़ी मिलता-जुलता है. टाइप किए गए कलेक्शन में रॉ मेमोरी का इस्तेमाल होता है. इसलिए, JavaScript इंजन, मेमोरी को सीधे नेटिव लाइब्रेरी में भेज सकता है. इसके लिए, डेटा को नेटिव वर्शन में बदलने की ज़रूरत नहीं होती. इस वजह से, WebGL और बाइनरी डेटा से जुड़े अन्य एपीआई को डेटा भेजने के लिए, टाइप किए गए ऐरे, JavaScript ऐरे की तुलना में काफ़ी बेहतर परफ़ॉर्म करते हैं.

टाइप किए गए ऐरे व्यू, ArrayBuffer के सेगमेंट के लिए सिंगल-टाइप ऐरे की तरह काम करते हैं. सभी सामान्य संख्याओं के लिए व्यू मौजूद हैं. इनके नाम से ही पता चलता है कि वे किस तरह के हैं. जैसे, Float32Array, Float64Array, Int32Array, और Uint8Array. एक खास व्यू भी है, जिसने कैनवस के ImageData: Uint8ClampedArray में पिक्सल कलेक्शन टाइप की जगह ले ली है.

DataView, दूसरी तरह का व्यू है. इसका मकसद अलग-अलग तरह के डेटा को मैनेज करना है. DataView ऑब्जेक्ट, ऐरे जैसे एपीआई के बजाय, आपको get/set एपीआई उपलब्ध कराता है. इसकी मदद से, किसी भी बाइट ऑफ़सेट पर किसी भी डेटा टाइप को पढ़ा और उसमें बदलाव किया जा सकता है. DataView, फ़ाइल हेडर के साथ-साथ स्ट्रक्चर जैसे दूसरे डेटा को पढ़ने और लिखने के लिए बेहतरीन काम करता है.

टाइप की गई सरणियों को इस्तेमाल करने की बुनियादी बातें

टाइप किए गए कलेक्शन के व्यू

टाइप की गई सरणियों का इस्तेमाल करने के लिए, आपको 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];

टाइप किए गए अरे व्यू अलग-अलग तरह के होते हैं. वे सभी एक ही एपीआई का इस्तेमाल करते हैं. इसलिए, जब आप इन्हें इस्तेमाल करने का तरीका जान जाएंगे, तब आपको काफ़ी हद तक इनकी जानकारी मिल जाएगी. अगले उदाहरण में, मैं टाइप किए गए मौजूदा हर व्यू में से एक व्यू बनाने वाली हूं.

// 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 के बीच क्लैंप करता है. यह खास तौर पर कैनवस इमेज प्रोसेसिंग एल्गोरिदम के लिए बहुत काम का है, क्योंकि अब आपको 8-बिट की रेंज से बाहर जाने से बचने के लिए, इमेज प्रोसेसिंग के मैथ को मैन्युअल तौर पर जोड़ने की ज़रूरत नहीं है.

उदाहरण के लिए, यहां बताया गया है कि Uint8Array में सेव की गई इमेज पर, गामा फ़ैक्टर कैसे लागू किया जाता है. बहुत अच्छी नहीं:

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

Uint8ClampedArray के साथ, मैन्युअल तरीके से क्लैंप करने की ज़रूरत नहीं पड़ती:

pixels[i] *= gamma;

टाइप किए गए ऐरे व्यू बनाने का दूसरा तरीका यह है कि पहले ArrayBuffer बनाएं और फिर उस पर पॉइंट करने वाले व्यू बनाएं. बाहरी डेटा पाने वाले एपीआई, आम तौर पर 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 बनाएं और डेटा को कॉपी करने के लिए 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

अलग-अलग तरह के डेटा वाले ArrayBuffers का इस्तेमाल करने के लिए, बफ़र में DataView का इस्तेमाल करना सबसे आसान तरीका है. मान लें कि हमारे पास एक फ़ाइल फ़ॉर्मैट है, जिसमें 8-बिट के बिना हस्ताक्षर वाले इंट के साथ हेडर है. इसके बाद, दो 16-बिट इंट और फिर 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);
}

ऊपर दिए गए उदाहरण में, मैंने जो वैल्यू पढ़ी हैं वे सभी बिग-एंडियन हैं. अगर बफ़र में मौजूद वैल्यू लिटल-इंडियन हैं, तो गेट्टर फ़ंक्शन में वैकल्पिक littleEndian पैरामीटर पास किया जा सकता है:

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

ध्यान दें कि टाइप किए गए ऐरे व्यू, हमेशा नेटिव बाइट ऑर्डर में होते हैं. ऐसा उन्हें तेज़ बनाने के लिए किया जाता है. आपको DataView का इस्तेमाल करके, डेटा को पढ़ना और उसमें डेटा सेव करना चाहिए. ऐसा तब करना चाहिए, जब डेटा को पढ़ने और उसमें डेटा सेव करने में, एंडीऐन (बाइट के क्रम) की समस्या आ रही हो.

DataView में, बफ़र में वैल्यू लिखने के तरीके भी होते हैं. इन सेटर का नाम, गटर के नाम से मिलता-जुलता होता है. इनका नाम "सेट" होता है और इसके बाद डेटा टाइप होता है.

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

एंडियननेस की चर्चा

एंडियननेस या बाइट ऑर्डर, वह क्रम है जिसमें मल्टी-बाइट संख्याएं कंप्यूटर की मेमोरी में स्टोर की जाती हैं. big-endian शब्द, ऐसे सीपीयू आर्किटेक्चर के बारे में बताता है जो सबसे ज़्यादा ज़रूरी बाइट को पहले स्टोर करता है; little-endian, जो सबसे कम ज़रूरी बाइट पहले सेव करता है. किसी सीपीयू आर्किटेक्चर में, किस endianness का इस्तेमाल किया जाए, यह पूरी तरह से मनमुताबिक होता है. दोनों में से किसी एक को चुनने की कई वजहें हो सकती हैं. असल में, कुछ सीपीयू को बिग-एंडियन और लिटिल-एंडियन, दोनों तरह के डेटा के साथ काम करने के लिए कॉन्फ़िगर किया जा सकता है.

आपको एंडियननेस के बारे में क्यों चिंता करने की ज़रूरत है? वजह आसान है. डिस्क या नेटवर्क से डेटा पढ़ते या लिखते समय, डेटा की एंडियननेस को तय किया जाना चाहिए. इससे यह पक्का होता है कि डेटा को सही तरीके से समझा जाए. भले ही, उसमें काम करने वाले सीपीयू का endianness कुछ भी हो. नेटवर्क की पहुंच लगातार बढ़ रही है. ऐसे में, यह ज़रूरी है कि सभी तरह के डिवाइसों पर, बिग- या लिटल-एंडियन फ़ॉर्मैट में डेटा सही तरीके से काम करे. इन डिवाइसों को नेटवर्क पर मौजूद सर्वर या दूसरे पीयर से मिलने वाले बाइनरी डेटा के साथ काम करना पड़ सकता है.

DataView इंटरफ़ेस को खास तौर पर, फ़ाइलों और नेटवर्क से डेटा पढ़ने और उसमें बदलाव करने के लिए डिज़ाइन किया गया है. DataView, तय एंडियननेस वाले डेटा पर काम करता है. हर वैल्यू को ऐक्सेस करते समय, बिग या लिटल, एंडीऐननेस की जानकारी देना ज़रूरी है. इससे यह पक्का होता है कि बाइनरी डेटा को पढ़ने या लिखने के दौरान, आपको एक जैसे और सही नतीजे मिलते हैं. भले ही, ब्राउज़र जिस सीपीयू पर चल रहा हो उसका एंडीऐननेस कुछ भी हो.

आम तौर पर, जब आपका ऐप्लिकेशन किसी सर्वर से बाइनरी डेटा पढ़ता है, तो आपको उसे एक बार स्कैन करना होगा, ताकि उसे उस डेटा स्ट्रक्चर में बदला जा सके जिसका इस्तेमाल आपका ऐप्लिकेशन अंदरूनी तौर पर करता है. इस चरण के दौरान, DataView का इस्तेमाल किया जाना चाहिए. XMLHttpRequest, FileReader या किसी अन्य इनपुट/आउटपुट एपीआई से फ़ेच किए गए डेटा के साथ, मल्टी-बाइट टाइप किए गए ऐरे व्यू (Int16Array, Uint16Array वगैरह) का सीधे तौर पर इस्तेमाल करना अच्छा नहीं है. ऐसा इसलिए, क्योंकि टाइप किए गए ऐरे व्यू, सीपीयू के नेटिव एंडीऐननेस का इस्तेमाल करते हैं. इस विषय पर ज़्यादा जानकारी बाद में.

आइए, कुछ आसान उदाहरण देखते हैं. Windows BMP फ़ाइल फ़ॉर्मैट, Windows के शुरुआती दिनों में इमेज को स्टोर करने के लिए स्टैंडर्ड फ़ॉर्मैट हुआ करता था. ऊपर दिए गए दस्तावेज़ से साफ़ तौर पर पता चलता है कि फ़ाइल में मौजूद सभी पूर्णांक वैल्यू, लिटल-इंडियन फ़ॉर्मैट में सेव की गई हैं. यहां कोड का एक स्निपेट दिया गया है, जो इस लेख में दी गई DataStream.js लाइब्रेरी का इस्तेमाल करके, BMP हेडर की शुरुआत को पार्स करता है:

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 सैंपल प्रोजेक्ट में हाई डाइनैमिक रेंज रेंडरिंग डेमो से लिया गया है. यह डेमो, हाई डाइनैमिक रेंज टेक्सचर दिखाने वाले रॉ, लिटल-इंडियन फ़्लोटिंग-पॉइंट डेटा को डाउनलोड करता है. साथ ही, उसे WebGL पर अपलोड करता है. यहां कोड का एक स्निपेट दिया गया है, जो सभी सीपीयू आर्किटेक्चर पर फ़्लोटिंग-पॉइंट वैल्यू को सही तरीके से समझता है. मान लें कि वैरिएबल “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 ऑब्जेक्ट या डेटा के बड़े ब्लॉक के लिए टाइप किया गया कलेक्शन व्यू. इससे यह पक्का हो जाएगा कि आपका कोड सभी तरह के सीपीयू पर सही तरीके से काम करेगा. किसी फ़ाइल या नेटवर्क में डेटा लिखने के लिए भी DataView का इस्तेमाल करें. साथ ही, यह पक्का करें कि आपने set के अलग-अलग तरीकों के लिए, littleEndian आर्ग्युमेंट को सही तरीके से तय किया हो. इससे, आपको वह फ़ाइल फ़ॉर्मैट मिलेगा जिसे बनाया जा रहा है या इस्तेमाल किया जा रहा है.

याद रखें कि नेटवर्क पर भेजे जाने वाले सभी डेटा का एक फ़ॉर्मैट और endianness होता है. कम से कम, कई बाइट वाली वैल्यू के लिए ऐसा होता है. पक्का करें कि आपके ऐप्लिकेशन से नेटवर्क पर भेजे जाने वाले सभी डेटा के फ़ॉर्मैट को साफ़ तौर पर बताया गया हो और उसका दस्तावेज़ बनाया गया हो.

टाइप किए गए कलेक्शन का इस्तेमाल करने वाले ब्राउज़र एपीआई

हम आपको उन अलग-अलग ब्राउज़र एपीआई के बारे में खास जानकारी देंगे जो फ़िलहाल टाइप किए गए ऐरे का इस्तेमाल कर रहे हैं. फ़िलहाल, WebGL, कैनवस, Web Audio API, XMLHttpRequests, WebSockets, वेब वर्कर्स, Media Source API, और File 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 2D

हाल ही में, कैनवस ImageData ऑब्जेक्ट को टाइप किए गए ऐरे स्पेसिफ़िकेशन के साथ काम करने के लिए बनाया गया था. अब आपको कैनवस एलिमेंट पर पिक्सल का टाइप किया गया ऐरे दिख सकता है. यह सुविधा इसलिए मददगार है, क्योंकि अब कैनवस एलिमेंट में बदलाव किए बिना भी कैनवस पिक्सल ऐरे बनाए जा सकते हैं और उनमें बदलाव किया जा सकता है.

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

XMLHttpRequest2

XMLHttpRequest को टाइप की गई ऐरे की सुविधा मिली है. अब आपको टाइप की गई ऐरे का जवाब मिल सकता है. इसके लिए, आपको JavaScript स्ट्रिंग को टाइप की गई ऐरे में पार्स करने की ज़रूरत नहीं है. फ़ेच किए गए डेटा को सीधे मल्टीमीडिया एपीआई पर भेजने और नेटवर्क से फ़ेच की गई बाइनरी फ़ाइलों को पार्स करने के लिए, यह तरीका काम आता है.

आपको बस XMLHttpRequest ऑब्जेक्ट के प्रतिक्रियाType को 'arraybuffer' में सेट करना है.

xhr.responseType = 'arraybuffer';

याद रखें कि नेटवर्क से डेटा डाउनलोड करते समय, आपको एंडियननेस से जुड़ी समस्याओं के बारे में पता होना चाहिए! ऊपर एंडियननेस वाला सेक्शन देखें.

फ़ाइल एपीआई

FileReader, फ़ाइल के कॉन्टेंट को ArrayBuffer के तौर पर पढ़ सकता है. इसके बाद, टाइप किए गए अरे व्यू और DataViews को बफ़र में अटैच किया जा सकता है, ताकि इनकी कॉन्टेंट में बदलाव किया जा सके.

reader.readAsArrayBuffer(file);

आपको यहां भी endianness का ध्यान रखना चाहिए. ज़्यादा जानकारी के लिए, एंडियननेस सेक्शन देखें.

ट्रांसफ़र किए जा सकने वाले ऑब्जेक्ट

postMessage फ़ंक्शन में ट्रांसफ़र किए जा सकने वाले ऑब्जेक्ट की मदद से, अन्य विंडो और वेब वर्कर्स को बाइनरी डेटा तेज़ी से भेजा जा सकता है. जब किसी ऑब्जेक्ट को ट्रांसफ़र किए जा सकने वाले आइटम के तौर पर किसी Worker को भेजा जाता है, तो उसे भेजने वाली थ्रेड में उस ऑब्जेक्ट को ऐक्सेस नहीं किया जा सकता. साथ ही, उसे पाने वाले Worker को ऑब्जेक्ट का मालिकाना हक मिल जाता है. इसकी मदद से, डेटा को ऑप्टिमाइज़ करके भेजा जा सकता है. इसमें भेजे गए डेटा की कॉपी नहीं की जाती. सिर्फ़ टाइप किए गए कलेक्शन का मालिकाना हक, पाने वाले को ट्रांसफ़र किया जाता है.

वेब वर्कर के साथ ट्रांसफ़र किए जा सकने वाले ऑब्जेक्ट का इस्तेमाल करने के लिए, आपको वर्कर पर webkitpostMessage तरीके का इस्तेमाल करना होगा. webkitpostMessage तरीका, postMessage की तरह ही काम करता है, लेकिन इसमें एक आर्ग्युमेंट की जगह दो आर्ग्युमेंट इस्तेमाल किए जाते हैं. जोड़ा गया दूसरा आर्ग्युमेंट, उन ऑब्जेक्ट का कलेक्शन होता है जिन्हें आपको वर्कफ़्लो में ट्रांसफ़र करना है.

worker.webkitPostMessage(oneGBTypedArray, [oneGBTypedArray]);

वर्कर से ऑब्जेक्ट वापस पाने के लिए, वर्कर उन्हें उसी तरह से मुख्य थ्रेड में वापस भेज सकता है.

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

शून्य कॉपी, वाह!

मीडिया स्रोत API

हाल ही में, मीडिया एलिमेंट को Media Source API के तौर पर टाइप की गई कुछ रेंज भी मिली हैं. आप webkitSourceAppend का इस्तेमाल करके वीडियो डेटा वाली टाइप की गई श्रेणी सीधे किसी वीडियो एलिमेंट को पास कर सकते हैं. इससे वीडियो एलिमेंट, मौजूदा वीडियो के बाद वीडियो डेटा जोड़ देता है. SourceAppend, इंटरस्टीशियल, प्लेलिस्ट, स्ट्रीमिंग, और अन्य कामों के लिए बेहतरीन है. इनमें, एक ही वीडियो एलिमेंट का इस्तेमाल करके कई वीडियो चलाए जा सकते हैं.

video.webkitSourceAppend(uint8Array);

बाइनरी WebSockets

अपने सभी डेटा को स्ट्रिंगिफ़ाइड करने की ज़रूरत से बचने के लिए, WebSockets के साथ टाइप की गई सरणियों का भी इस्तेमाल किया जा सकता है. बेहतर प्रोटोकॉल लिखने और नेटवर्क ट्रैफ़िक को कम करने के लिए बेहतरीन.

socket.binaryType = 'arraybuffer';

शुक्र है! एपीआई की समीक्षा पूरी हो गई. चलिए, टाइप किए गए ऐरे को मैनेज करने के लिए, तीसरे पक्ष की लाइब्रेरी देखें.

तीसरे पक्ष की लाइब्रेरी

jDataView

jDataView, सभी ब्राउज़र के लिए DataView shim लागू करता है. DataView की सुविधा पहले सिर्फ़ WebKit के लिए उपलब्ध थी, लेकिन अब यह ज़्यादातर ब्राउज़र पर काम करती है. Mozilla की डेवलपर टीम, Firefox पर भी DataView की सुविधा चालू करने के लिए एक पैच लाने की प्रोसेस में है.

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.
}

स्ट्रिंग एनकोडिंग

फ़िलहाल, टाइप किए गए ऐरे में स्ट्रिंग के साथ काम करना थोड़ा मुश्किल है. हालांकि, 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 में, आर्बिट्रेरी बिट ऑफ़सेट पर 6-बिट और 12-बिट इन्ट को स्टोर और लोड करने के तरीके भी हैं.

स्क्रीन के निर्देशांक के साथ काम करने के लिए, 12-बिट इंट का इस्तेमाल करना अच्छा होता है. ऐसा इसलिए, क्योंकि डिसप्ले के लंबे डाइमेंशन में 4096 पिक्सल से कम पिक्सल होते हैं. 32-बिट पूर्णांक के बजाय 12-बिट पूर्णांक का इस्तेमाल करने पर, आपको आकार में 62% की कमी मिलती है. एक और उदाहरण के लिए, मैं उन शेपफ़ाइल के साथ काम कर रहा था जो निर्देशांक के लिए 64-बिट फ़्लोट का इस्तेमाल करती हैं. हालांकि, मुझे सटीक निर्देशांक की ज़रूरत नहीं थी, क्योंकि मॉडल सिर्फ़ स्क्रीन साइज़ पर दिखाया जाना था. पिछले निर्देशांक में हुए बदलावों को कोड में बदलने के लिए, 6-बिट डेल्टा के साथ 12-बिट बेस निर्देशांक पर स्विच करने से, फ़ाइल का साइज़ 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 में बाइनरी फ़ाइलों को आसानी से मैनेज करने में मदद करते हैं. स्ट्रिंग को वर्ण के हिसाब से पार्स करने और वर्णों को मैन्युअल तरीके से बाइनरी नंबर वगैरह में बदलने के बजाय, अब XMLHttpRequest की मदद से ArrayBuffer पाया जा सकता है और उसे सीधे DataView का इस्तेमाल करके प्रोसेस किया जा सकता है. इससे आसान हो जाता है. जैसे, किसी MP3 फ़ाइल में लोड करना और अपने ऑडियो प्लेयर में इस्तेमाल करने के लिए मेटाडेटा टैग पढ़ना. या किसी शेपफ़ाइल में लोड करें और उसे WebGL मॉडल में बदलें. इसके अलावा, किसी JPEG फ़ाइल के EXIF टैग को पढ़कर, उन्हें अपने स्लाइड शो ऐप्लिकेशन में दिखाया जा सकता है.

ArrayBuffer XHRs की समस्या यह है कि बफ़र से स्ट्रक्चर जैसा डेटा पढ़ना थोड़ा मुश्किल है. DataView, एक बार में कुछ संख्याओं को endian-safe तरीके से पढ़ने के लिए अच्छा है. टाइप किए गए ऐरे व्यू, एलिमेंट के साइज़ के हिसाब से अलाइन किए गए नेटिव endian नंबर के ऐरे को पढ़ने के लिए अच्छे हैं. हमें लगा कि डेटा के ऐरे और स्ट्रक्चर को आसानी से endian-safe तरीके से पढ़ने का कोई तरीका मौजूद नहीं है. DataStream.js डालें.

DataStream.js, टाइप की गई अरे वाली लाइब्रेरी है. यह 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
]);

जैसा कि आप देख सकते हैं, स्ट्रक्चर की परिभाषा, [name, type]-pairs का एक फ़्लैट कलेक्शन है. नेस्ट किए गए स्ट्रक्चर बनाने के लिए, टाइप के लिए एक कलेक्शन बनाया जाता है. ऐरे को तीन एलिमेंट वाले ऐरे का इस्तेमाल करके तय किया जाता है. इसमें दूसरा एलिमेंट, ऐरे एलिमेंट का टाइप होता है और तीसरा एलिमेंट, ऐरे की लंबाई होती है. यह लंबाई, संख्या के तौर पर, पहले से पढ़े गए फ़ील्ड के रेफ़रंस के तौर पर या कॉलबैक फ़ंक्शन के तौर पर हो सकती है. कलेक्शन की परिभाषा का पहला एलिमेंट इस्तेमाल नहीं हुआ है.

टाइप के लिए ये वैल्यू हो सकती हैं:

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 मेटाडेटा में पढ़ने का लाइव उदाहरण यहां देखा जा सकता है. इस डेमो में, JPEG फ़ाइल के टैग-लेवल स्ट्रक्चर को पढ़ने के लिए DataStream.js का इस्तेमाल किया गया है. साथ ही, JavaScript में JPEG इमेज को डिकोड करने और दिखाने के लिए jpg.js का इस्तेमाल किया गया है. इसमें कुछ EXIF पार्सिंग भी शामिल है.

टाइप की गई सरणियों का इतिहास

टाइप किए गए ऐरे की शुरुआत, WebGL के लागू होने के शुरुआती चरण में हुई थी. तब हमें पता चला था कि ग्राफ़िक्स ड्राइवर को JavaScript ऐरे पास करने से परफ़ॉर्मेंस से जुड़ी समस्याएं आ रही हैं. JavaScript अरे के साथ, WebGL बाइंडिंग को एक नेटिव अरे असाइन करना था और उसे भरना होता था. इसके लिए, JavaScript अरे पर चलना होता था और अरे में मौजूद हर JavaScript ऑब्जेक्ट को उसके हिसाब से नेटिव टाइप में कास्ट करना होता था.

डेटा कन्वर्ज़न की समस्या को ठीक करने के लिए, Mozilla के व्लादिमीर वुकिसेविक ने CanvasFloatArray: को JavaScript इंटरफ़ेस वाली सी-स्टाइल फ़्लोट अरे लिखा. अब JavaScript में CanvasFloatArray में बदलाव किया जा सकता है और उसे सीधे WebGL में पास किया जा सकता है. इसके लिए, बाइंडिंग में कोई अतिरिक्त काम नहीं करना पड़ता. आने वाले समय में, CanvasFloatArray का नाम बदलकर WebGLFloatArray कर दिया गया, जिसका नाम बदलकर Flood32Array किया गया, और बफ़र को ऐक्सेस करने के लिए, यह बैकिंग ArrayBuffer और टाइप Flow32Array-view में बंट गया, तो इसे बदल दिया गया. अन्य इंटिजर और फ़्लोटिंग-पॉइंट साइज़ और साइन वाले/बिना साइन वाले वैरिएंट के लिए भी टाइप जोड़े गए थे.

डिज़ाइन पर ध्यान देना

शुरू से ही, टाइप किए गए ऐरे का डिज़ाइन, नेटिव लाइब्रेरी में बाइनरी डेटा को असरदार तरीके से पास करने की ज़रूरत के हिसाब से बनाया गया था. इस वजह से, टाइप किए गए ऐरे व्यू, होस्ट सीपीयू के नेटिव एंडीऐननेस में अलाइन किए गए डेटा पर काम करते हैं. इन फ़ैसलों की मदद से, JavaScript, ग्राफ़िक्स कार्ड पर वर्टिक्स डेटा भेजने जैसे कामों के दौरान, बेहतर परफ़ॉर्मेंस हासिल कर पाता है.

DataView को खास तौर पर फ़ाइल और नेटवर्क I/O के लिए डिज़ाइन किया गया है, जहां डेटा में हमेशा खास तौर पर उसका अंत होता है. ऐसा हो सकता है कि बेहतर परफ़ॉर्मेंस के लिए इस डेटा को अलाइन न किया जाए.

इन-मेमोरी डेटा असेंबली (टाइप किए गए ऐरे व्यू का इस्तेमाल करके) और आई/ओ (DataView का इस्तेमाल करके) के बीच डिज़ाइन को अलग-अलग रखा गया है. आधुनिक JavaScript इंजन, टाइप किए गए ऐरे व्यू को ज़्यादा से ज़्यादा ऑप्टिमाइज़ करते हैं. साथ ही, उनके साथ अंकों वाले ऑपरेशन पर बेहतर परफ़ॉर्मेंस हासिल करते हैं. इस डिज़ाइन फ़ैसले की वजह से, टाइप किए गए ऐरे व्यू की परफ़ॉर्मेंस के मौजूदा लेवल को हासिल किया जा सका.

रेफ़रंस