`DataView` i TypeScript
Denne artikkelen forklarer DataView i TypeScript.
Vi vil forklare DataView i TypeScript, fra det grunnleggende til praktisk bruk, steg for steg.
YouTube Video
DataView i TypeScript
Ved å bruke DataView i TypeScript kan du utføre detaljerte lese- og skriveoperasjoner på bytenivå på en ArrayBuffer. DataView er svært nyttig for lavnivå binærbehandling, slik som protokollimplementering, analyse av binærfiler og sending/mottak av binærdata over WebSocket.
Grunnleggende konsepter: Forskjeller mellom ArrayBuffer, TypedArray og DataView
ArrayBuffer er en grunnleggende datastruktur som brukes til å lagre en sekvens av bytes med fast lengde. TypedArray-er som Uint8Array er visninger som behandler bufferen som et array av en bestemt type, der hvert element har en fast type.
På den annen side er DataView et fleksibelt syn som lar deg lese og skrive verdier av hvilken som helst datatype på en hvilken som helst offset-posisjon. Siden den ikke antar faste typer og gir kontroll på bytenivå, egner den seg godt til analyse av binære protokoller og lavnivå-prosessering.
Følgende er et eksempel på å lage en ArrayBuffer, og deretter lage en Uint8Array og DataView fra den.
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
- I denne koden brukes to forskjellige typer visninger—
Uint8ArrayogDataView—samtidig på én enkelt buffer. Ved å brukeDataViewkan du fleksibelt lese og skrive verdier ved å angi vilkårlig offset og byte-rekkefølge (endianness).
Hovedmetoder for DataView
DataView er et grensesnitt for å manipulere en ArrayBuffer på bytenivå, og tilbyr metoder for å lese og skrive ulike typer som heltall og flyttall.
Hovedmetodene er som følger:.
- Lese:
getUint8,getInt8,getUint16,getInt16,getUint32,getInt32,getFloat32,getFloat64 - Skrive:
setUint8,setInt8,setUint16,setInt16,setUint32,setInt32,setFloat32,setFloat64
Alle disse metodene tar en ‘byte offset’ som første argument: get leser verdien på den posisjonen, og set skriver verdien til den posisjonen. Du kan også angi endianness som andre argument når du håndterer 16-, 32- eller 64-bits data. I praksis er det tryggest å alltid angi endianness i henhold til dataspesifikasjonen.
Følgende eksempel viser hvordan du kan skrive et 32-bits heltall og et 32-bits flyttall i little-endian-format til en buffer, og deretter lese dem tilbake i samme format.
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)
- Ved å eksplisitt angi endianness, kan du sikre kompatibilitet med ulike plattformer og binære spesifikasjoner.
Om endianness (bytte-rekkefølge)
Noen protokoller, for eksempel de som brukes i nettverk, bruker big-endian, mens mange CPU-er og filformater hovedsakelig bruker little-endian-design. I DataView, hvis det andre argumentet er satt til true, behandles data som little endian; hvis det settes til false eller utelates, behandles data som big endian.
I det følgende eksemplet kan du se hvordan byte-matrisen i minnet endrer seg når det samme tallet skrives med både big-endian- og little-endian-format.
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]
- Å forstå hvordan byte-rekkefølgen endres mellom big endian og little endian gjør det mye enklere å analysere kommunikasjonsdata eller binære formater.
Streng inn-/ut-data (Bruke TextEncoder og Decoder sammen)
DataView er utmerket for å lese og skrive tall, men håndterer ikke strenger direkte. Det er vanlig å kode strenger med TextEncoder eller TextDecoder (for eksempel til UTF-8), og deretter kopiere dem inn i bufferen via Uint8Array. Følgende er et eksempel på å skrive en UTF-8-streng til en buffer og deretter lese den tilbake.
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);- Ved å konvertere strenger til binær, og eventuelt legge til lengden først, kan du lagre strenger med variabel lengde.
Praktisk eksempel: Koding/avkoding av et tilpasset binært format
Nedenfor definerer vi et enkelt meldingsformat som inneholder et versjonsnummer, en ID og et navn. Vi implementerer en kodingsprosess for å konvertere meldinger til binære data, og en dekodingsprosess for å gjenopprette det opprinnelige objektet fra de binære dataene. I dette meldingsformatet lagrer den første byten versjonen, de neste 4 bytene lagrer ID-en i little-endian-format, den påfølgende byten inneholder navnets lengde, og til slutt lagres navnet som UTF-8-koding.
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" }
- Dette er en typisk implementasjon for å håndtere et grunnleggende meldingsformat med en streng med variabel lengde, og kan brukes i mange praktiske situasjoner som nettverkskommunikasjon eller egendefinerte binærfildesign.
Forsiktighetsregler og beste praksis
Når du arbeider med binærdata ved bruk av DataView, er det flere praktiske hensyn du bør ta i betraktning utover bare å lese og skrive verdier—slik som sikkerhet, konsekvent bruk av endianness og korrekt håndtering av typer. Spesielt når du håndterer binærdata mottatt fra eksterne kilder eller arbeider med store heltall, er det viktig å designe koden din slik at du forhindrer feil lesing og buffer-overløp. Nedenfor er noen nyttige punkter å huske på for praktisk bruk.
-
Grensesjekk
DataViewkaster et unntak hvis en offset eller størrelse overstiger bufferens grense. Sjekk alltid lengder når du håndterer ikke-tillit binærdata. -
Angi alltid endianness Angi alltid eksplisitt og konsekvent enten little endian eller big endian i koden din.
-
Typekonsistens JavaScript-tall er 64-bits IEEE-754 flytende-punkt-verdier. Metoder som
getUint32håndteres som forventet, men det finnes ingengetUint64, så spesiell håndtering kreves for 64-bits heltall. -
Håndtering av 64-bits heltall For å håndtere 64-bits heltall må du bruke
BigInteller manuelt splitte verdien i høy og lav 32-bits del. Her er et enkelt eksempel på å lese et 64-bits usignert heltall.
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}- Ved å bruke
BigIntkan du også trygt håndtere heltall større enn 64 biter.
Bruk i Node.js (Samarbeid med Buffer)
Selv om Buffer ofte brukes i Node.js, er det også enkelt å konvertere mellom ArrayBuffer og DataView. Bruk buffer-egenskapen til Buffer-objekter eller Uint8Array-konstruktøren for konvertering.
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));- Dette kan brukes som et verktøy for å utveksle binærdata mellom Node.js og nettlesere.
Sammendrag
DataView er en kraftig mekanisme for å fritt lese og skrive binærdata—særlig nyttig i situasjoner hvor fleksibel kontroll som spesifisering av endianness og tilgang til vilkårlige posisjoner er nødvendig. Ved å kombinere ArrayBuffer, TypedArray og DataView kan du fleksibelt og nøyaktig håndtere binærdata med TypeScript, og åpne for et bredt spekter av bruksområder fra protokollimplementering til filanalyse. Ved også å inkludere strengkoding og håndtering av 64-bits heltall etter behov kan du oppnå enda mer praktiske binæroperasjoner.
Du kan følge med på artikkelen ovenfor ved å bruke Visual Studio Code på vår YouTube-kanal. Vennligst sjekk ut YouTube-kanalen.