آرایه های تایپ شده - داده های باینری در مرورگر

Ilmari Heikkinen

معرفی

تایپ آرایه ها اضافه شده نسبتاً جدید به مرورگرها هستند که به دلیل نیاز به داشتن روشی کارآمد برای مدیریت داده های باینری در WebGL به وجود آمده اند. یک آرایه تایپ شده یک صفحه از حافظه با نمای تایپ شده در داخل آن است، دقیقاً شبیه نحوه کار آرایه ها در C. از آنجا که یک آرایه تایپ شده توسط حافظه خام پشتیبانی می شود، موتور جاوا اسکریپت می تواند حافظه را مستقیماً به کتابخانه های بومی منتقل کند بدون اینکه نیازی به تبدیل دقیق آن باشد. داده ها به یک نمایندگی بومی در نتیجه، آرایه‌های تایپ‌شده برای ارسال داده‌ها به WebGL و سایر APIهایی که با داده‌های باینری سروکار دارند، بسیار بهتر از آرایه‌های جاوا اسکریپت عمل می‌کنند.

نماهای آرایه تایپ شده مانند آرایه های تک نوع در قسمتی از ArrayBuffer عمل می کنند. نماهایی برای همه انواع عددی معمول، با نام‌های خود توصیفی مانند Float32Array، Float64Array، Int32Array و Uint8Array وجود دارد. همچنین یک نمای ویژه وجود دارد که جایگزین نوع آرایه پیکسلی در ImageData Canvas شده است: Uint8ClampedArray.

DataView نوع دوم نمایش است و برای مدیریت داده های ناهمگن است. به جای داشتن یک API آرایه مانند، شی DataView یک API 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];

انواع مختلفی از نمای آرایه تایپ شده وجود دارد. همه آنها از یک API مشترک استفاده می کنند، بنابراین وقتی می دانید چگونه از یکی استفاده کنید، تقریباً می دانید که چگونه از همه آنها استفاده کنید. من قصد دارم یکی از هر نمای آرایه تایپ شده موجود را در مثال بعدی ایجاد کنم.

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

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

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

آخرین مورد کمی خاص است، مقادیر ورودی را بین 0 تا 255 می‌بندد. این به ویژه برای الگوریتم‌های پردازش تصویر Canvas مفید است، زیرا اکنون لازم نیست ریاضی پردازش تصویر خود را به صورت دستی گیره دهید تا از سرریز شدن محدوده 8 بیتی جلوگیری کنید.

برای مثال، در اینجا نحوه اعمال ضریب گاما به تصویر ذخیره شده در Uint8Array آمده است. خیلی زیبا نیست:

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

با Uint8ClampedArray می توانید از بستن دستی صرفنظر کنید:

pixels[i] *= gamma;

راه دیگر برای ایجاد نماهای آرایه تایپ شده این است که ابتدا یک ArrayBuffer ایجاد کنید و سپس نماهایی را ایجاد کنید که به آن اشاره می کنند. APIهایی که داده‌های خارجی را به شما می‌رسانند معمولاً در ArrayBuffers قرار می‌گیرند، بنابراین این راهی است که شما یک نمای آرایه تایپ شده برای آن‌ها دریافت می‌کنید.

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

همچنین می توانید چندین نما به همان ArrayBuffer داشته باشید.

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

برای کپی کردن یک آرایه تایپ شده به آرایه تایپ شده دیگر، سریعترین راه استفاده از روش مجموعه آرایه تایپ شده است. برای استفاده شبیه به memcpy، Uint8Arrays را در بافر نماها ایجاد کنید و از 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 بیتی و به دنبال آن یک آرایه باری متشکل از شناورهای 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 هستند. اگر مقادیر بافر کمی اندین هستند، می توانید پارامتر اختیاری littleEndian را به گیرنده ارسال کنید:

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

توجه داشته باشید که نماهای آرایه تایپ شده همیشه به ترتیب بایت اصلی هستند. این برای سریع کردن آنهاست. شما باید از DataView برای خواندن و نوشتن داده هایی استفاده کنید که در آن endianness مشکل است.

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

بحث در مورد endianness

Endianness یا ترتیب بایت ترتیبی است که در آن اعداد چند بایتی در حافظه کامپیوتر ذخیره می شوند. اصطلاح big-endian یک معماری CPU را توصیف می کند که در ابتدا مهم ترین بایت را ذخیره می کند. little-endian ، ابتدا بایت کم اهمیت است. اینکه کدام endianness در یک معماری خاص CPU استفاده می شود کاملاً دلخواه است. دلایل خوبی برای انتخاب هر کدام وجود دارد. در واقع، برخی از CPU ها را می توان به گونه ای پیکربندی کرد که از داده های بزرگ و کوچک اندین پشتیبانی کند.

چرا باید در مورد پایانی بودن نگران باشید؟ دلیلش هم ساده است. هنگام خواندن یا نوشتن داده ها از دیسک یا شبکه، پایانی بودن داده ها باید مشخص شود. این تضمین می‌کند که داده‌ها به درستی تفسیر می‌شوند، بدون در نظر گرفتن پایانی بودن CPU که با آن کار می‌کند. در دنیای روزافزون شبکه‌ای ما، پشتیبانی مناسب از انواع دستگاه‌ها، بزرگ یا کوچک، که ممکن است نیاز به کار با داده‌های باینری از سرورها یا سایر همتایان در شبکه داشته باشند، ضروری است.

رابط DataView به طور خاص برای خواندن و نوشتن داده ها از فایل ها و شبکه طراحی شده است. DataView بر روی داده‌ها با یک endianness مشخص عمل می‌کند. Endianness، بزرگ یا کوچک، باید با هر دسترسی به هر مقدار مشخص شود، تا اطمینان حاصل شود که هنگام خواندن یا نوشتن داده‌های باینری، نتایج ثابت و صحیحی دریافت می‌کنید، مهم نیست که CPU که مرورگر روی آن کار می‌کند چقدر است.

به طور معمول، زمانی که برنامه شما داده‌های باینری را از یک سرور می‌خواند، باید یک بار آن‌ها را اسکن کنید تا آن‌ها را به ساختارهای داده‌ای که برنامه‌تان در داخل استفاده می‌کند تبدیل کنید. DataView باید در این مرحله استفاده شود. استفاده از نماهای آرایه تایپ شده چند بایتی (Int16Array، Uint16Array، و غیره) به طور مستقیم با داده های واکشی شده از طریق XMLHttpRequest، FileReader یا هر API ورودی/خروجی دیگری ایده خوبی نیست، زیرا نماهای آرایه تایپ شده از endianness بومی CPU استفاده می کنند. بیشتر در این مورد بعدا.

بیایید به چند مثال ساده نگاه کنیم. فرمت فایل BMP ویندوز در روزهای اولیه ویندوز، فرمت استاندارد برای ذخیره تصاویر بود. اسناد لینک شده در بالا به وضوح نشان می دهد که تمام مقادیر صحیح در فایل در قالب کمی اندین ذخیره می شوند. در اینجا یک قطعه کد است که ابتدای هدر 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 است . این نسخه ی نمایشی داده های ممیز شناور خام و اندکی را که نمایانگر بافت های محدوده دینامیکی بالا هستند دانلود می کند و باید آن را در 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 عبور دهید. مقادیر عددی مجزا را بخوانید و آنها را در ساختار داده دیگری ذخیره کنید، یا یک شی جاوا اسکریپت (برای مقادیر کمی از داده های ساختاریافته) یا یک نمای آرایه تایپ شده (برای بلوک های بزرگ داده). این اطمینان حاصل می کند که کد شما به درستی روی همه نوع CPU کار می کند. همچنین از DataView برای نوشتن داده‌ها در یک فایل یا شبکه استفاده کنید و مطمئن شوید که آرگومان littleEndian را به روش‌های مختلف مجموعه به‌منظور تولید فرمت فایلی که ایجاد می‌کنید یا استفاده می‌کنید، به درستی مشخص کرده‌اید.

به یاد داشته باشید، تمام داده‌هایی که به طور ضمنی روی شبکه می‌روند دارای یک قالب و یک endianness هستند (حداقل برای هر مقدار چند بایتی). مطمئن شوید که فرمت تمام داده‌هایی که برنامه شما از طریق شبکه ارسال می‌کند به وضوح تعریف و مستند کنید.

APIهای مرورگرهایی که از آرایه‌های تایپ شده استفاده می‌کنند

من قصد دارم یک نمای کلی از APIهای مختلف مرورگرهایی که در حال حاضر از تایپ آرایه ها استفاده می کنند به شما ارائه کنم. برش فعلی شامل WebGL، Canvas، Web Audio API، XMLHttpRequests، WebSockets، Web Workers، Media Source API و File API است. از لیست API ها می توانید ببینید که تایپ آرایه ها برای کارهای چندرسانه ای حساس به عملکرد و همچنین انتقال داده ها به روشی کارآمد مناسب هستند.

WebGL

اولین استفاده از Typed Arrays در WebGL بود، جایی که برای انتقال داده های بافر و داده های تصویر استفاده می شود. برای تنظیم محتویات یک شی بافر WebGL، از فراخوانی ()gl.bufferData با یک آرایه Typed استفاده می کنید.

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 به Typed Arrays نیاز دارید.

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

بوم 2 بعدی

اخیراً شی Canvas ImageData برای کار با مشخصات Typed Arrays ساخته شده است. اکنون می توانید یک نمایش تایپ آرایه از پیکسل ها روی یک عنصر بوم دریافت کنید. این کار مفید است زیرا اکنون می‌توانید آرایه‌های پیکسل بوم را بدون نیاز به درگیری با عنصر بوم ایجاد و ویرایش کنید.

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

XMLHttpRequest2

XMLHttpRequest تقویت آرایه تایپ شده را دریافت کرد و اکنون می توانید به جای تجزیه رشته جاوا اسکریپت در یک آرایه تایپ شده، پاسخ آرایه تایپ شده را دریافت کنید. این برای ارسال مستقیم داده‌های واکشی شده به APIهای چندرسانه‌ای و برای تجزیه فایل‌های باینری واکشی شده از شبکه، واقعاً خوب است.

تنها کاری که باید انجام دهید این است که answerType شی XMLHttpRequest را روی 'arraybuffer' تنظیم کنید.

xhr.responseType = 'arraybuffer';

به یاد داشته باشید که هنگام بارگیری داده ها از شبکه باید از مشکلات endianness آگاه باشید! به بخش endianness در بالا مراجعه کنید.

APIهای فایل

FileReader می تواند محتویات فایل را به صورت ArrayBuffer بخواند. سپس می توانید نماهای آرایه تایپ شده و DataViews را به بافر متصل کنید تا محتویات آن را دستکاری کنید.

reader.readAsArrayBuffer(file);

شما باید endianness را در اینجا نیز در نظر داشته باشید. برای جزئیات، بخش endianness را بررسی کنید.

اشیاء قابل انتقال

اشیاء قابل انتقال در postMessage انتقال داده های باینری را به سایر ویندوزها و Web Workers بسیار سریعتر می کند. هنگامی که یک شی را به عنوان یک انتقال پذیر برای یک Worker ارسال می کنید، شی در رشته ارسال غیرقابل دسترسی می شود و Worker دریافت کننده مالکیت شی را به دست می آورد. این امکان اجرای بسیار بهینه را فراهم می کند که در آن داده های ارسالی کپی نمی شوند، فقط مالکیت Typed Array به گیرنده منتقل می شود.

برای استفاده از اشیاء قابل انتقال با Web Workers، باید از متد webkitPostMessage در worker استفاده کنید. متد webkitPostMessage درست مانند postMessage کار می کند، اما به جای یک آرگومان، دو آرگومان می گیرد. آرگومان دوم اضافه شده آرایه ای از اشیاء است که می خواهید به کارگر منتقل کنید.

worker.webkitPostMessage(oneGBTypedArray, [oneGBTypedArray]);

برای پس گرفتن اشیاء از کارگر، کارگر می تواند آنها را به همان شکل به نخ اصلی برگرداند.

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

بدون کپی، وو!

API منبع رسانه

اخیراً، عناصر رسانه‌ای نیز در قالب Media Source API دارای ویژگی‌های Typed Array هستند. با استفاده از webkitSourceAppend می‌توانید مستقیماً یک آرایه تایپ حاوی داده‌های ویدیویی را به یک عنصر ویدیویی ارسال کنید. این باعث می‌شود عنصر ویدیو، داده‌های ویدیو را بعد از ویدیوی موجود اضافه کند. SourceAppend برای انجام کارهای بینابینی، لیست پخش، پخش جریانی و موارد دیگر که ممکن است بخواهید چندین ویدیو را با استفاده از یک عنصر ویدیویی پخش کنید، عالی است.

video.webkitSourceAppend(uint8Array);

وب سوکت های باینری

همچنین می‌توانید از آرایه‌های تایپ شده با WebSockets استفاده کنید تا مجبور نباشید تمام داده‌های خود را به‌طور کامل درآورید. برای نوشتن پروتکل های کارآمد و به حداقل رساندن ترافیک شبکه عالی است.

socket.binaryType = 'arraybuffer';

وای! که بررسی API را به پایان می رساند. بیایید به سراغ کتابخانه های شخص ثالث برای مدیریت آرایه های تایپ شده برویم.

کتابخانه های شخص ثالث

jDataView

jDataView شیم DataView را برای همه مرورگرها پیاده سازی می کند. DataView قبلاً یک ویژگی فقط WebKit بود، اما اکنون توسط اکثر مرورگرهای دیگر پشتیبانی می شود. تیم توسعه دهنده موزیلا در حال ایجاد وصله ای برای فعال کردن DataView در فایرفاکس است.

اریک بیدلمن در تیم روابط توسعه‌دهنده کروم یک نمونه تگ خوان 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 مشخصات رمزگذاری رشته آرایه تایپ شده پیشنهادی را پیاده‌سازی می‌کند، بنابراین راه خوبی برای درک آنچه در راه است نیز می‌باشد.

در اینجا یک مثال استفاده اساسی از 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 پیکسل در طول ابعاد طولانی‌تر دارند. با استفاده از اینت های 12 بیتی به جای اینت های 32 بیتی، 62٪ کاهش اندازه دریافت می کنید. برای مثال شدیدتر، من با Shapefiles کار می‌کردم که از شناورهای 64 بیتی برای مختصات استفاده می‌کردند، اما به دقت نیازی نداشتم زیرا مدل فقط در اندازه صفحه نمایش داده می‌شد. جابجایی به مختصات پایه 12 بیتی با دلتاهای 6 بیتی برای رمزگذاری تغییرات نسبت به مختصات قبلی، اندازه فایل را به یک دهم کاهش داد. شما می توانید نسخه ی نمایشی آن را در اینجا ببینید.

در اینجا مثالی از استفاده از 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

یکی از جالب‌ترین چیزها در مورد آرایه‌های تایپ شده این است که چگونه کار کردن با فایل‌های باینری در جاوا اسکریپت را آسان‌تر می‌کنند. به جای تجزیه یک کاراکتر رشته به کاراکتر و تبدیل دستی کاراکترها به اعداد باینری و غیره، اکنون می توانید یک ArrayBuffer با XMLHttpRequest دریافت کنید و مستقیماً آن را با استفاده از DataView پردازش کنید. این باعث می شود که به عنوان مثال بارگذاری در یک فایل MP3 و خواندن تگ های ابرداده برای استفاده در پخش کننده صوتی شما آسان شود. یا در یک شکل فایل بارگذاری کنید و آن را به یک مدل WebGL تبدیل کنید. یا تگ های EXIF ​​را از یک JPEG بخوانید و آنها را در برنامه نمایش اسلاید خود نشان دهید.

مشکل ArrayBuffer XHR این است که خواندن داده‌های ساختار مانند از بافر کمی دردسرساز است. DataView برای خواندن چند عدد در یک زمان به روش ایمن endian خوب است، نماهای آرایه تایپ شده برای خواندن آرایه هایی از اعداد اندیان بومی تراز شده با اندازه عنصر خوب هستند. چیزی که احساس می‌کردیم گم شده بود راهی برای خواندن آرایه‌ها و ساختارهای داده‌ها به روشی راحت و ایمن است. DataStream.js را وارد کنید.

DataStream.js یک کتابخانه Typed 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
]);

همانطور که می بینید، تعریف ساختار یک آرایه مسطح از جفت [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 را در اینجا ببینید. نسخه ی نمایشی از DataStream.js برای خواندن ساختار سطح برچسب فایل JPEG (به همراه مقداری تجزیه EXIF) و jpg.js برای رمزگشایی و نمایش تصویر JPEG در جاوا اسکریپت استفاده می کند.

تاریخچه آرایه های تایپ شده

تایپ آرایه‌ها در مراحل اولیه پیاده‌سازی WebGL شروع شدند، زمانی که متوجه شدیم که ارسال آرایه‌های جاوا اسکریپت به درایور گرافیک باعث ایجاد مشکلاتی در عملکرد می‌شود. با آرایه‌های جاوا اسکریپت، پیوند WebGL باید یک آرایه بومی را اختصاص می‌داد و آن را با قدم زدن روی آرایه جاوا اسکریپت پر می‌کرد و هر شی جاوا اسکریپت در آرایه را به نوع اصلی مورد نیاز می‌فرستاد.

برای رفع گلوگاه تبدیل داده، ولادیمیر ووکیچویچ از موزیلا CanvasFloatArray را نوشت: یک آرایه شناور به سبک C با رابط جاوا اسکریپت. اکنون می توانید CanvasFloatArray را در جاوا اسکریپت ویرایش کنید و بدون نیاز به انجام کار اضافی در صحافی، آن را مستقیماً به WebGL منتقل کنید. در تکرارهای بعدی، CanvasFloatArray به WebGLFloatArray تغییر نام داد، که بعداً به Float32Array تغییر نام داد و به یک ArrayBuffer پشتیبان و Float32Array-view تایپ شده برای دسترسی به بافر تقسیم شد. انواع نیز برای سایر اندازه‌های اعداد صحیح و ممیز شناور و انواع علامت‌دار/بدون علامت اضافه شد.

ملاحظات طراحی

از همان ابتدا، طراحی تایپ آرایه‌ها به دلیل نیاز به انتقال کارآمد داده‌های باینری به کتابخانه‌های بومی انجام شد. به همین دلیل، نماهای آرایه تایپ شده بر روی داده های تراز شده در endianness بومی CPU میزبان عمل می کنند. این تصمیمات این امکان را برای جاوا اسکریپت فراهم می کند که در طول عملیاتی مانند ارسال داده های راس به کارت گرافیک به حداکثر کارایی برسد.

DataView به طور خاص برای فایل و شبکه ورودی/خروجی طراحی شده است، جایی که داده‌ها همیشه دارای یک endianness مشخص هستند و ممکن است برای حداکثر کارایی تراز نباشند.

تقسیم طراحی بین مجموعه داده های درون حافظه (با استفاده از نمای آرایه تایپ شده) و I/O (با استفاده از DataView) یک طرح آگاهانه بود. موتورهای جاوا اسکریپت مدرن نماهای آرایه تایپ شده را به شدت بهینه می کنند و عملکرد بالایی در عملیات عددی با آنها به دست می آورند. سطوح فعلی عملکرد نماهای آرایه تایپ شده با این تصمیم طراحی ممکن شد.

منابع