`DataView` i TypeScript

`DataView` i TypeScript

Denne artikel forklarer DataView i TypeScript.

Vi vil forklare DataView i TypeScript, fra det grundlæggende til praktisk brug, trin for trin.

YouTube Video

DataView i TypeScript

Ved at bruge DataView i TypeScript kan du udføre detaljerede læse- og skriveoperationer på byteniveau på et ArrayBuffer. DataView er yderst nyttig til lav-niveau binær behandling som f.eks. protokolimplementering, analyse af binære filer og afsendelse/modtagelse af binære data over WebSocket.

Grundlæggende begreber: Forskelle mellem ArrayBuffer, TypedArray og DataView

ArrayBuffer er en grundlæggende datastruktur, der bruges til at gemme en fast længde sekvens af bytes. TypedArrays såsom Uint8Array er visninger, der behandler bufferet som et array af en bestemt type, hvor hvert element har en fast type.

På den anden side er DataView en fleksibel visning, der tillader dig at læse og skrive værdier af enhver datatype på en hvilken som helst offsetposition. Fordi den ikke antager faste typer og tillader detaljeret kontrol på byteniveau, er den velegnet til at analysere binære protokoller og lav-niveau behandling.

Følgende er et eksempel på at oprette et ArrayBuffer og derefter oprette en Uint8Array og DataView fra det.

 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 kode bruges to forskellige typer visninger—Uint8Array og DataView—samtidigt på en enkelt buffer. Ved brug af DataView kan du fleksibelt læse og skrive værdier ved at angive vilkårlige positioner og byteorden (endianness).

Vigtigste metoder i DataView

DataView er et interface til at manipulere et ArrayBuffer på byteniveau og giver metoder til at læse og skrive forskellige typer som heltal og flydende tal.

De vigtigste metoder er som følger:.

  • Læs: getUint8, getInt8, getUint16, getInt16, getUint32, getInt32, getFloat32, getFloat64
  • Skriv: setUint8, setInt8, setUint16, setInt16, setUint32, setInt32, setFloat32, setFloat64

Alle disse metoder tager en 'byte-offset' som første argument: get læser værdien på den position, og set skriver værdien til den position. Du kan også angive byteorden (endianness) med det andet argument, når du håndterer 16-, 32- eller 64-bit data. I praksis er det sikrest altid at angive endianness i henhold til dataspecifikationen.

Følgende eksempel viser, hvordan man skriver et 32-bit heltal og et 32-bit flydende tal i little endian-format til en buffer og derefter læser dem tilbage 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 eksplicit at angive endianness kan du sikre kompatibilitet med forskellige platforme og binære specifikationer.

Om endianness (byteorden)

Nogle protokoller, såsom dem der bruges i netværk, benytter big-endian, mens mange CPU'er og filformater fortrinsvis benytter little-endian design. I DataView, hvis det andet argument er sat til true, behandles dataene som little endian; hvis det er sat til false eller udeladt, behandles de som big endian.

I det følgende eksempel kan du se, hvordan byte-arrayet i hukommelsen ændrer sig, når det samme tal skrives med både big-endian og little-endian formater.

 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]
  • At forstå, hvordan byteordenen ændres mellem big endian og little endian, vil gøre det meget nemmere at analysere kommunikationsdata eller binære formater.

Streng input/output (brug af TextEncoder og TextDecoder sammen)

DataView er fremragende til at læse og skrive tal, men håndterer ikke strenge direkte. Det er almindeligt at kode strenge med TextEncoder eller TextDecoder (f.eks. til UTF-8) og derefter kopiere dem ind i bufferet via Uint8Array. Følgende er et eksempel på at skrive en UTF-8 streng til en buffer og derefter læse den tilbage.

 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 at konvertere strenge til binært format og, om nødvendigt, tilføje deres længde først, kan du gemme strenge med variabel længde.

Praktisk eksempel: Kodning/afkodning af et brugerdefineret binært format

Nedenfor definerer vi et simpelt beskedformat, der indeholder et versionsnummer, et ID og et navn. Vi implementerer en kodningsproces til at konvertere beskeder til binære data og en afkodningsproces til at genskabe det oprindelige objekt fra det binære. I dette beskedformat lagrer den første byte versionen, de næste 4 bytes lagrer ID'et i little-endian format, den følgende byte indeholder navnets længde, og til sidst lagres navnet som UTF-8 kodet tekst.

 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 implementering til håndtering af et grundlæggende beskedformat med en streng af variabel længde, og det kan anvendes i mange praktiske situationer såsom netværkskommunikation eller brugerdefinerede binære filformater.

Advarsler og bedste praksis

Når du arbejder med binære data ved hjælp af DataView, er der flere praktiske forhold at tage hensyn til ud over blot læsning og skrivning af værdier—såsom sikkerhed, konsekvent brug af endianness og korrekt håndtering af datatyper. Især når du håndterer binære data fra eksterne kilder eller arbejder med store heltal, er det vigtigt at designe din kode, så du forhindrer forkerte læsninger og buffer-overløb. Nedenfor er nogle nyttige punkter at huske til praktisk brug.

  • Grænsekontrol DataView kaster en undtagelse, hvis en offset eller størrelse overskrider bufferens grænse. Tjek altid længder, når du arbejder med ikke-betroede binære data.

  • Angiv altid endianness Angiv altid eksplicit og konsekvent little endian eller big endian i din kode.

  • Typekonsistens JavaScript-tal er 64-bit IEEE-754 flydende værdier. Metoder som getUint32 håndteres korrekt, men der findes ikke getUint64, så speciel håndtering kræves for 64-bit heltal.

  • Håndtering af 64-bit heltal For at håndtere 64-bit heltal skal du bruge BigInt eller manuelt opdele værdien i høje og lave 32-bit dele. Her er et enkelt eksempel på læsning af et 64-bit usigneret heltal.

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 at bruge BigInt kan du også sikkert håndtere heltal større end 64 bit.

Brug i Node.js (interoperabilitet med Buffer)

Selvom Buffer ofte bruges i Node.js, er det også nemt at konvertere mellem ArrayBuffer og DataView. Brug buffer-egenskaben på Buffer-objekter eller Uint8Array-konstruktøren til 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 bruges som et værktøj til at udveksle binære data mellem Node.js og browsere.

Sammendrag

DataView er en kraftfuld mekanisme til frit at læse og skrive binære data—særligt nyttigt i situationer, hvor fleksibel kontrol som angivelse af endianness og adgang til vilkårlige positioner er nødvendig. Ved at kombinere ArrayBuffer, TypedArray og DataView kan du fleksibelt og præcist håndtere binære data med TypeScript, hvilket muliggør en bred vifte af anvendelser fra protokolimplementering til filanalyse. Ved også at inkludere strengkodning og håndtering af 64-bit heltal efter behov, kan du opnå endnu mere praktiske binære operationer.

Du kan følge med i ovenstående artikel ved hjælp af Visual Studio Code på vores YouTube-kanal. Husk også at tjekke YouTube-kanalen.

YouTube Video