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

Ilmari Heikkinen

शुरुआती जानकारी

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

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

DataView दूसरे टाइप का व्यू है. यह विषम डेटा को हैंडल करने के लिए बनाया गया है. कलेक्शन जैसा एपीआई होने के बजाय, DataView ऑब्जेक्ट आपको आर्बिट्रेरी बाइट ऑफ़सेट पर आर्बिट्रेरी डेटा टाइप पढ़ने और लिखने के लिए get/set API देता है. 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];

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

टाइप किए गए किसी अरे को टाइप किए गए किसी अरे में कॉपी करने के लिए, सबसे तेज़ तरीका टाइप किए गए अरे सेट वाले तरीके का इस्तेमाल करना है. मीम की तरह के इस्तेमाल के लिए, व्यू के बफ़र के लिए 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 का इस्तेमाल करना सबसे आसान तरीका है. मान लें कि हमारे पास एक फ़ाइल फ़ॉर्मैट है, जिसमें एक हेडर है, जिसमें 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 में बफ़र के लिए वैल्यू लिखने के तरीके भी हैं. इन सेटर के नाम उसी तरह से रखे जाते हैं जिस तरह गैटर के नाम में दिए जाते हैं, "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

रूढ़िवादी सोच पर चर्चा

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

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

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

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

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

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

मैं आपको उन अलग-अलग ब्राउज़र एपीआई के बारे में खास जानकारी देने जा रही हूं जो फ़िलहाल टाइप की गई सरणियों का इस्तेमाल कर रहे हैं. क्रॉप की गई मौजूदा प्रोसेस में WebGL, Canvas, Web Audio API, XMLHttpRequests, WebSockets, Web Workers, 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);

कैनवस 2D

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

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

XMLHttpRequest2

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

आपको सिर्फ़ XMLHttpRequest ऑब्जेक्ट केResponseType को 'arraybuffer' पर सेट करना होगा.

xhr.responseType = 'arraybuffer';

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

फ़ाइल एपीआई

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

reader.readAsArrayBuffer(file);

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

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

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

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

worker.webkitPostMessage(oneGBTypedArray, [oneGBTypedArray]);

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

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

वाह!

मीडिया सोर्स एपीआई

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

video.webkitSourceAppend(uint8Array);

बाइनरी वेबसॉकेट

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

socket.binaryType = 'arraybuffer';

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

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

jDataView

jDataView सभी ब्राउज़र के लिए DataView शिम लागू करता है. पहले 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.
}

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

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

स्ट्रिंगएन्कोडिंग के बुनियादी इस्तेमाल का उदाहरण यहां दिया गया है:

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

यहां 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 मॉडल में बदलें. या EXIF टैग को JPEG से पढ़ें और उन्हें अपने स्लाइड शो ऐप्लिकेशन में दिखाएं.

ArrayBuffer XHRs के साथ समस्या यह है कि बफ़र से स्ट्रक्चर जैसे डेटा को पढ़ने में थोड़ी परेशानी होती है. DataView, एंडियन-सुरक्षित तरीके से एक बार में कुछ संख्याओं को पढ़ने के लिए अच्छा है. टाइप किए गए अरे व्यू, एलिमेंट के साइज़ के अलाइनमेंट वाले नेटिव एंडियन नंबर के अरे पढ़ने के लिए अच्छे होते हैं. हमने जो महसूस किया था वह डेटा को व्यवस्थित तरीके से और सही तरीके से पढ़ने में आसान है. 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]-पेयर की एक फ़्लैट अरे है. नेस्ट किए गए निर्देश, टाइप के लिए एक अरे का इस्तेमाल करके बनाए जाते हैं. अरे को तीन एलिमेंट वाले अरे का इस्तेमाल करके तय किया जाता है. इसमें दूसरा एलिमेंट अरे एलिमेंट टाइप होता है और तीसरा एलिमेंट अरे की लंबाई है (या तो संख्या के रूप में, पहले पढ़े गए फ़ील्ड के रेफ़रंस के तौर पर या कॉलबैक फ़ंक्शन के रूप में). अरे की परिभाषा का पहला एलिमेंट इस्तेमाल नहीं किया गया है.

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

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

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

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

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

डिज़ाइन के लिए ध्यान देने वाली बातें

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

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

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

रेफ़रंस