`DataView` in TypeScript
Dit artikel legt DataView in TypeScript uit.
We leggen DataView in TypeScript stap voor stap uit, van de basis tot praktisch gebruik.
YouTube Video
DataView in TypeScript
Door DataView in TypeScript te gebruiken, kun je op byte-niveau gedetailleerde lees- en schrijfoperaties uitvoeren op een ArrayBuffer. DataView is zeer handig voor laag-niveau binaire verwerking zoals protocolimplementatie, analyse van binaire bestanden en het verzenden/ontvangen van binaire data via WebSocket.
Basisconcepten: Verschillen tussen ArrayBuffer, TypedArray en DataView
ArrayBuffer is een fundamentele datastructuur die wordt gebruikt om een reeks bytes met vaste lengte op te slaan. TypedArray's zoals Uint8Array zijn weergaven die de buffer behandelen als een array van een specifiek type, waarbij elk element een vaste type heeft.
Aan de andere kant is DataView een flexibele weergave waarmee je waarden van elk gegevenstype op elke offsetpositie kunt lezen en schrijven. Omdat het geen vaste types aanneemt en fijne controle op byte-niveau mogelijk maakt, is het geschikt voor het analyseren van binaire protocollen en laag-niveau verwerking.
Hieronder volgt een voorbeeld van het aanmaken van een ArrayBuffer en het vervolgens creëren van een Uint8Array en DataView daarop.
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
- In deze code worden twee verschillende typen views—
Uint8ArrayenDataView—tegelijkertijd gebruikt op één enkele buffer. MetDataViewkun je flexibel waarden lezen en schrijven door willekeurige offsets en eindianheid op te geven.
Belangrijkste methoden van DataView
DataView is een interface voor het manipuleren van een ArrayBuffer op byte-niveau en biedt methoden om verschillende typen, zoals gehele getallen en floating-point getallen, te lezen en te schrijven.
De belangrijkste methoden zijn als volgt:.
- Lezen:
getUint8,getInt8,getUint16,getInt16,getUint32,getInt32,getFloat32,getFloat64 - Schrijven:
setUint8,setInt8,setUint16,setInt16,setUint32,setInt32,setFloat32,setFloat64
Al deze methoden nemen een 'byte offset' als eerste argument: get leest de waarde op die positie, en set schrijft de waarde naar die positie. Je kunt ook de eindianheid specificeren via het tweede argument bij het werken met 16-, 32-, of 64-bit data. In de praktijk is het het veiligst om altijd de eindianheid te specificeren volgens de dataspecificatie.
Het volgende voorbeeld laat zien hoe je een 32-bit geheel getal en een 32-bit floating-point getal in little-endian formaat naar een buffer schrijft en die vervolgens op dezelfde wijze terugleest.
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)
- Door de eindianheid expliciet te specificeren, kun je compatibiliteit garanderen met verschillende platformen en binaire specificaties.
Over Endianheid (Byte-volgorde)
Sommige protocollen, zoals die bij netwerken, gebruiken big-endian, terwijl veel CPU's en bestandsformaten vooral little-endian gebruiken. In DataView, als het tweede argument op true staat, wordt de data behandeld als little-endian; als het op false staat of wordt weggelaten, wordt het als big-endian behandeld.
In het volgende voorbeeld kun je zien hoe de byte-array in het geheugen verandert bij het schrijven van hetzelfde getal met zowel big-endian als little-endian formaten.
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]
- Door te begrijpen hoe de byte-volgorde verandert tussen big-endian en little-endian, kun je veel eenvoudiger communicatiedata of binaire formaten analyseren.
Stringinvoer/-uitvoer (Gebruik van TextEncoder en Decoder samen)
DataView is uitstekend voor het lezen en schrijven van getallen, maar kan niet direct overweg met strings. Het is gebruikelijk om strings te coderen met TextEncoder of TextDecoder (bijvoorbeeld naar UTF-8), en deze vervolgens via een Uint8Array in de buffer te plaatsen. Hieronder volgt een voorbeeld van het schrijven van een UTF-8 string naar een buffer en deze vervolgens weer teruglezen.
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);- Door strings om te zetten naar binair en, indien nodig, hun lengte vooraf te plaatsen, kun je strings van variabele lengte opslaan.
Praktisch voorbeeld: Coderen/decoderen van een aangepast binair formaat
Hieronder definiëren we een eenvoudig berichtformaat met een versienummer, ID en naam. We implementeren een encoderingsproces om berichten naar binaire gegevens om te zetten, en een decoderingsproces om het originele object uit de binaire gegevens te herstellen. In dit berichtformaat bevat de eerste byte de versie, de volgende 4 bytes bevatten de ID in little-endian formaat, de daaropvolgende byte de lengte van de naam en tenslotte wordt de naam in UTF-8 encoding opgeslagen.
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" }
- Dit is een typische implementatie voor het behandelen van een basisberichtformaat met een string van variabele lengte, en kan worden toegepast in veel praktische situaties zoals netwerkcommunicatie of het ontwerpen van aangepaste binaire bestanden.
Waarschuwingen en Best Practices
Bij het werken met binaire data via DataView zijn er diverse praktische aandachtspunten, zoals veiligheid, consistent gebruik van eindianheid en correcte omgang met typen, die verder gaan dan eenvoudigweg waarden lezen en schrijven. Vooral bij het verwerken van binaire data uit externe bronnen of bij het omgaan met grote gehele getallen is het belangrijk om je code zo te ontwerpen dat foutief lezen en buffer overruns worden voorkomen. Hieronder staan enkele nuttige aandachtspunten voor praktisch gebruik.
-
Grenscontrole
DataViewgeeft een foutmelding als een offset of grootte buiten de buffergrens valt. Controleer altijd de lengtes bij het werken met niet-vertrouwde binaire data. -
Specificeer altijd de eindianheid Specificeer altijd expliciet en consequent little-endian of big-endian in je code.
-
Typeconsistentie JavaScript-getallen zijn 64-bit IEEE-754 floating-point waarden. Methodes zoals
getUint32worden goed afgehandeld, maar er is geengetUint64, dus voor 64-bit gehele getallen is aparte behandeling nodig. -
Het behandelen van 64-bit integers Om 64-bit integers te beheren, moet je
BigIntgebruiken of de waarde handmatig opsplitsen in hoge en lage 32-bits delen. Hier is een eenvoudig voorbeeld van het lezen van een 64-bit unsigned integer.
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}- Met
BigIntkun je ook veilig gehele getallen behandelen die groter zijn dan 64 bits.
Gebruik in Node.js (samenwerking met Buffer)
Hoewel Buffer veel wordt gebruikt in Node.js, is het ook eenvoudig om te converteren tussen ArrayBuffer en DataView. Gebruik de buffer-eigenschap van Buffer-objecten of de Uint8Array constructor voor conversie.
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));- Dit kan worden gebruikt als hulpmiddel voor het uitwisselen van binaire data tussen Node.js en browsers.
Samenvatting
DataView is een krachtig mechanisme om flexibel binaire data te lezen en schrijven—vooral nuttig in situaties waarin je eindianheid en willekeurige posities wilt kunnen beheren. Door ArrayBuffer, TypedArray en DataView te combineren, kun je flexibel en nauwkeurig binaire data verwerken met TypeScript, wat uiteenlopende toepassingen mogelijk maakt, van protocolimplementatie tot bestandsanalyse. Door ook stringcodering en de verwerking van 64-bit integers toe te passen waar nodig, kun je nog praktischer werken met binaire handelingen.
Je kunt het bovenstaande artikel volgen met Visual Studio Code op ons YouTube-kanaal. Bekijk ook het YouTube-kanaal.