TypeScript'te `DataView`
Bu makale, TypeScript'teki DataView öğesini açıklar.
DataView'in TypeScript'te nasıl kullanılacağını temelden uygulamalı kullanıma kadar adım adım açıklayacağız.
YouTube Video
TypeScript'te DataView
DataView'i TypeScript'te kullanarak bir ArrayBuffer üzerinde bayt düzeyinde ayrıntılı okuma ve yazma işlemleri gerçekleştirebilirsiniz. DataView, protokol uygulama, ikili dosya analizi ve WebSocket üzerinden ikili veri gönderme/alma gibi düşük seviyeli ikili işlemler için son derece kullanışlıdır.
Temel Kavramlar: ArrayBuffer, TypedArray ve DataView Arasındaki Farklar
ArrayBuffer, sabit uzunlukta bir bayt dizisini depolamak için kullanılan temel bir veri yapısıdır. Uint8Array gibi TypedArray'ler, tamponu belirli bir türün sabit tipli elemanlarından oluşan bir dizi olarak ele alan görünümlerdir.
Öte yandan, DataView, herhangi bir öteleme konumunda her türlü veri tipinin değerlerini okumanıza ve yazmanıza olanak tanıyan esnek bir görünümdür. Sabit veri türlerini varsaymadığı ve bayt düzeyinde ayrıntılı kontrol sağladığı için, ikili protokollerin analizinde ve düşük seviyeli işlemlerde uygundur.
Aşağıda, bir ArrayBuffer oluşturup ardından bundan bir Uint8Array ve DataView oluşturma örneği bulunmaktadır.
1// Create an ArrayBuffer (8 bytes)
2const buffer = new ArrayBuffer(8);
3
4// Create a Uint8Array view (easy to manipulate individual bytes)
5const u8 = new Uint8Array(buffer);
6u8[0] = 0x12;
7u8[1] = 0x34;
8
9// Create a DataView (allows reading/writing any type at any offset)
10const dv = new DataView(buffer);
11
12// Read a 16-bit unsigned integer (big-endian assumed if second argument is omitted)
13console.log(dv.getUint16(0)); // 0x1234
- Bu kodda, tek bir tampon üzerinde iki farklı tipte görünüm—
Uint8ArrayveDataView—aynı anda kullanılmaktadır.DataViewkullanarak, isteğe bağlı konumları ve bayt sırasını (endianness) belirterek esnek bir şekilde değerler okuyup yazabilirsiniz.
DataView'in Temel Metodları
DataView, bir ArrayBuffer üzerinde bayt düzeyinde işlem yapmak için bir arayüzdür ve tamsayılar ile kayan noktalı sayılar gibi çeşitli tipleri okumak ve yazmak için yöntemler sağlar.
Temel metodlar şunlardır:.
- Okuma:
getUint8,getInt8,getUint16,getInt16,getUint32,getInt32,getFloat32,getFloat64 - Yazma:
setUint8,setInt8,setUint16,setInt16,setUint32,setInt32,setFloat32,setFloat64
Bu metodların hepsi ilk parametre olarak 'bayt ofseti' alır: get o konumdaki değeri okur, set ise değeri o konuma yazar. 16-, 32- veya 64-bit verilerle çalışırken ikinci parametre olarak bayt sırasını (endianness) da belirtebilirsiniz. Pratikte, veri spesifikasyonuna göre her zaman bayt sırasını açıkça belirtmek en güvenlisidir.
Aşağıdaki örnek, bir 32 bit tam sayı ile 32 bit kayan noktalı bir değeri tampon üzerine küçük uçlu (little endian) formatında yazmayı ve aynı formatta tekrar okumayı göstermektedir.
1const buf = new ArrayBuffer(12); // Enough space for integer + float
2const view = new DataView(buf);
3
4// Write a 32-bit unsigned integer at offset 0 (little-endian)
5view.setUint32(0, 305419896, true); // 305419896 = 0x12345678
6
7// Read the same 32-bit unsigned integer back from offset 0 (little-endian)
8const intVal = view.getUint32(0, true);
9console.log(intVal); // 305419896
10
11// Write a 32-bit floating number at offset 4 (little-endian)
12view.setFloat32(4, 3.1415926, true);
13
14// Read the 32-bit floating number back from offset 4 (little-endian)
15const floatVal = view.getFloat32(4, true);
16console.log(floatVal); // 3.1415926 (approx)
- Bayt sırasını açıkça belirterek, farklı platformlar ve ikili veri yapılarına uyumluluk sağlayabilirsiniz.
Endianness (Bayt Sırası) Hakkında
Bazı protokoller, örneğin ağ protokolleri, büyük uçlu (big-endian) kullansa da, birçok CPU ve dosya formatı çoğunlukla küçük uçlu (little-endian) tasarımı kullanır. DataView'de ikinci argüman true olarak ayarlanırsa veri küçük uçlu (little endian) olarak, false veya belirtilmezse büyük uçlu (big endian) olarak işlenir.
Aşağıdaki örnekte, aynı sayıyı hem big-endian hem de little-endian formatlarını kullanarak yazarken, bellekteki bayt dizisinin nasıl değiştiğini görebilirsiniz.
1const b = new ArrayBuffer(4);
2const a = new Uint8Array(b);
3const dv = new DataView(b);
4
5// Write in big-endian order
6dv.setUint32(0, 0x0A0B0C0D, false); // big-endian
7console.log([...a]); // [10, 11, 12, 13]
8
9// Write the same value in little-endian order
10dv.setUint32(0, 0x0A0B0C0D, true); // little-endian
11console.log([...a]); // [13, 12, 11, 10]
- Bayt sırasının büyük uçlu ile küçük uçlu arasında nasıl değiştiğini anlamak, iletişim verilerini veya ikili formatları analiz etmeyi çok daha kolaylaştıracaktır.
String Girdi/Çıktı (TextEncoder ve Decoder'ın Birlikte Kullanılması)
DataView, sayıları okumak ve yazmak için mükemmeldir ancak metinleri (string) doğrudan işlemez. String'leri TextEncoder veya TextDecoder (örneğin UTF-8'e) ile kodlayıp ardından Uint8Array aracılığıyla tamponun içine kopyalamak yaygındır. Aşağıda, bir UTF-8 dizesinin tampona yazılması ve tekrar okunması örneği verilmiştir.
1const encoder = new TextEncoder();
2const decoder = new TextDecoder();
3
4const str = "\u3053\u3093\u306b\u3061\u306f";
5const encoded = encoder.encode(str); // Uint8Array (UTF-8 encoded bytes)
6
7// Create a buffer and write the encoded string bytes into it
8const buf2 = new ArrayBuffer(encoded.length);
9const u8v = new Uint8Array(buf2);
10u8v.set(encoded);
11
12// Read the bytes and decode them back into a string
13const decodedStr = decoder.decode(new Uint8Array(buf2));
14console.log(decodedStr);- Dizeleri ikili (binary) olarak dönüştürüp, gerekiyorsa önlerine uzunluklarını ekleyerek, değişken uzunlukta string'ler depolayabilirsiniz.
Uygulamalı Örnek: Özel Bir İkili Formatı Kodlama/Kod Çözme
Aşağıda, bir sürüm numarası, kimlik ve adı içeren basit bir mesaj formatı tanımlıyoruz. Mesajları ikili verilere dönüştürmek için bir kodlama süreci ve ikiliden orijinal nesneyi geri yüklemek için bir kod çözme süreci uyguluyoruz. Bu mesaj formatında, ilk bayt sürümü saklar, sonraki 4 bayt kimliği little-endian formatında saklar, bir sonraki bayt adın uzunluğunu içerir ve son olarak UTF-8 ile kodlanmış ad saklanır.
1// Encode an object into a binary message
2function encodeMessage(msg: { version: number; id: number; name: string }): ArrayBuffer {
3 const encoder = new TextEncoder();
4 const nameBytes = encoder.encode(msg.name);
5 const total = 1 + 4 + 1 + nameBytes.length;
6 const buf = new ArrayBuffer(total);
7 const dv = new DataView(buf);
8 let offset = 0;
9
10 dv.setUint8(offset, msg.version); offset += 1; // write version
11 dv.setUint32(offset, msg.id, true); offset += 4; // write ID (little-endian)
12 dv.setUint8(offset, nameBytes.length); offset += 1; // write name length
13
14 // write name bytes
15 new Uint8Array(buf, offset).set(nameBytes);
16 return buf;
17}
18
19// Decode a binary message back into an object
20function decodeMessage(buf: ArrayBuffer) {
21 const dv = new DataView(buf);
22 let offset = 0;
23
24 const version = dv.getUint8(offset); offset += 1; // read version
25 const id = dv.getUint32(offset, true); offset += 4; // read ID (little-endian)
26 const nameLen = dv.getUint8(offset); offset += 1; // read name length
27
28 // extract name bytes
29 const nameBytes = new Uint8Array(buf, offset, nameLen);
30 const decoder = new TextDecoder();
31 // decode UTF-8 string
32 const name = decoder.decode(nameBytes);
33
34 return { version, id, name };
35}
36
37// Example usage
38const packed = encodeMessage({ version: 1, id: 42, name: "Alice" });
39const unpacked = decodeMessage(packed);
40console.log(unpacked); // { version: 1, id: 42, name: "Alice" }
- Değişken uzunlukta dize içeren temel bir mesaj formatının işlenmesi için tipik bir uygulamadır ve ağ iletişimi ya da özel ikili dosya tasarımları gibi birçok pratik durumda kullanılabilir.
Dikkat Edilecekler ve En İyi Uygulamalar
DataView ile ikili verilerle çalışırken, yalnızca değer okumak ve yazmaktan öte, güvenlik, uç sıraların (endianness) tutarlı kullanımı ve tiplerin doğru şekilde işlenmesi gibi birkaç pratik noktayı göz önünde bulundurmalısınız. Özellikle dış kaynaklardan alınan ikili verilerle veya büyük tam sayılarla çalışırken, yanlış okuma ve tampon taşmalarını önleyecek şekilde kodunuzu tasarlamanız önemlidir. Aşağıda, pratik kullanım için hatırlamanız gereken bazı yararlı noktalar bulunmaktadır.
-
Sınır Kontrolü Bir ofset veya boyut tampon sınırını aşarsa,
DataViewbir hata (exception) fırlatır. Güvenilmeyen ikili veriyle çalışırken daima uzunlukları kontrol edin. -
Her Zaman Endianness Belirtin Kodunuzda daima küçük uçlu (little endian) veya büyük uçlu (big endian) değerlerini açık ve tutarlı biçimde belirtiniz.
-
Tip Tutarlılığı JavaScript'teki sayılar 64-bit IEEE-754 kayan noktalı değerlerdir.
getUint32gibi metodlar uygun şekilde işlenir; ancakgetUint64yoktur, bu yüzden 64-bit tamsayılar için özel işlemler gerekir. -
64-bit Tamsayıların İşlenmesi 64-bit tamsayıları işlemek için
BigIntkullanmalı veya değeri 32 bitlik yüksek ve düşük bölümlere manuel olarak ayırmalısınız. İşte 64-bit işaretsiz bir tamsayıyı okuma örneği:.
1function getUint64(dv: DataView, offset: number, littleEndian = true): bigint {
2 const low = BigInt(dv.getUint32(offset, littleEndian));
3 const high = BigInt(dv.getUint32(offset + 4, littleEndian));
4 return littleEndian ? (high << 32n) | low : (low << 32n) | high;
5}BigIntkullanarak, 64 bitten daha büyük tamsayıları da güvenli bir şekilde işleyebilirsiniz.
Node.js'de Kullanım (Buffer ile Birlikte Kullanım)
Node.js'de genellikle Buffer kullanılsa da, ArrayBuffer ile DataView arasında dönüşüm yapmak da kolaydır. Dönüştürme işlemleri için Buffer nesnelerinin buffer özelliğini veya Uint8Array yapıcısını kullanın.
1// Node.js: Buffer -> ArrayBuffer
2const nodeBuf = Buffer.from([1,2,3,4]);
3const arrBuf = nodeBuf.buffer.slice(nodeBuf.byteOffset, nodeBuf.byteOffset + nodeBuf.byteLength);
4const dvNode = new DataView(arrBuf);
5console.log(dvNode.getUint16(0));- Bu yöntem, Node.js ile tarayıcılar arasında ikili veri alışverişi için bir araç olarak kullanılabilir.
Özet
DataView, ikili verileri serbestçe okuyup yazmak için güçlü bir mekanizmadır—özellikle, uç sırası (endianness) belirtme ve istenilen konuma erişim gibi esnek denetimin gerektiği durumlar için kullanışlıdır. ArrayBuffer, TypedArray ve DataView'i birleştirerek TypeScript ile ikili verileri esnek ve doğru bir şekilde işleyebilir, protokol uygulamasından dosya analizine kadar geniş bir kullanım alanı elde edebilirsiniz. Ayrıca, gerektiğinde dize kodlama ve 64-bit tamsayıların işlenmesini de dahil ederek, çok daha pratik ikili işlemler gerçekleştirebilirsiniz.
Yukarıdaki makaleyi, YouTube kanalımızda Visual Studio Code'u kullanarak takip edebilirsiniz. Lütfen YouTube kanalını da kontrol edin.