`DataView` i TypeScript

`DataView` i TypeScript

Den här artikeln förklarar DataView i TypeScript.

Vi kommer att förklara DataView i TypeScript, från grunderna till praktisk användning, steg för steg.

YouTube Video

DataView i TypeScript

Genom att använda DataView i TypeScript kan du utföra finjusterade läs- och skrivoperationer på byte-nivå på en ArrayBuffer. DataView är extremt användbar för lågnivåhantering av binärdata såsom protokollimplementation, analys av binära filer och sändning/mottagning av binärdata över WebSocket.

Grundläggande koncept: Skillnader mellan ArrayBuffer, TypedArray och DataView

ArrayBuffer är en grundläggande datastruktur som används för att lagra en sekvens av byte med fast längd. TypedArray som Uint8Array är vyer som behandlar bufferten som en array av en viss typ, där varje element har en fast datatyp.

Å andra sidan är DataView en flexibel vy som låter dig läsa och skriva värden av vilken datatyp som helst på vilken offsetposition som helst. Eftersom den inte antar fasta typer och möjliggör finjusterad kontroll på bytnivå, är den lämplig för att analysera binära protokoll och lågnivåbearbetning.

Följande är ett exempel på hur man skapar en ArrayBuffer och sedan skapar en Uint8Array och en DataView från 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 denna kod används två olika sorters vyer—Uint8Array och DataView—samtidigt på en och samma buffer. Med DataView kan du flexibelt läsa och skriva värden genom att ange godtyckliga positioner och bytteordning (endianness).

Huvudmetoder för DataView

DataView är ett gränssnitt för att manipulera en ArrayBuffer på bytenivå, och ger metoder för att läsa och skriva olika typer såsom heltal och flyttal (float).

De viktigaste metoderna är följande:.

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

Alla dessa metoder tar en 'byte offset' som första argument: get läser värdet på den positionen och set skriver värdet till den positionen. Du kan också ange bytteordning (endianness) som andra argument när du hanterar 16-, 32- eller 64-bitars data. I praktiken är det säkrast att alltid ange bytteordning (endianness) enligt dataspecifikationen.

Följande exempel visar hur man skriver ett 32-bitars heltal och ett 32-bitars flyttal till en buffer i little endian-format och sedan läser tillbaka dem i samma 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)
  • Genom att uttryckligen ange bytteordningen kan du säkerställa kompatibilitet mellan olika plattformar och binära specifikationer.

Om bytteordning (endianness)

Vissa protokoll, såsom de som används i nätverk, använder big-endian, medan många CPU:er och filformat främst använder little-endian. I DataView, om det andra argumentet är satt till true, behandlas datan som little endian; om det är false eller utelämnas behandlas det som big endian.

I följande exempel kan du se hur byte-arrangemanget i minnet förändras när samma tal skrivs med både big-endian och 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]
  • Att förstå hur bytteordningen ändras mellan big endian och little endian gör det mycket enklare att analysera kommunikationsdata eller binära format.

Strängin- och utmatning (med TextEncoder och TextDecoder tillsammans)

DataView är utmärkt för att läsa och skriva siffror, men hanterar inte strängar direkt. Det är vanligt att koda strängar med TextEncoder eller TextDecoder (till exempel i UTF-8), och sedan kopiera dem till buffer via Uint8Array. Följande är ett exempel på att skriva en UTF-8-sträng till en buffer och sedan läsa tillbaka den.

 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);
  • Genom att konvertera strängar till binärt och vid behov lägga till deras längd före själva strängen, kan du lagra strängar med variabel längd.

Praktiskt exempel: Kodning/avkodning av ett anpassat binärt format

Nedan definierar vi ett enkelt meddelandeformat som innehåller ett versionsnummer, ett ID och ett namn. Vi implementerar en kodningsprocess för att konvertera meddelanden till binära data, och en avkodningsprocess för att återställa det ursprungliga objektet från binären. I detta meddelandeformat lagras versionen i den första byten, de följande fyra byten lagrar ID:t i little-endian-format, nästa byte innehåller namnets längd, och till sist lagras namnet som UTF-8-kodning.

 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" }
  • Detta är en typisk implementation för att hantera ett enkelt meddelandeformat med en sträng av variabel längd, och kan användas i många praktiska sammanhang såsom nätverkskommunikation eller egna binära filformat.

Försiktighetsåtgärder och rekommenderade arbetssätt

När du arbetar med binärdata med DataView finns det flera praktiska punkter att tänka på utöver själva läsningen och skrivningen, som säkerhet, konsekvent användning av endianness och korrekt hantering av typer. I synnerhet när du hanterar binärdata från externa källor eller arbetar med stora heltal, är det viktigt att utforma din kod så att felaktiga avläsningar och överskridningar av bufferten undviks. Nedan finns några användbara punkter att komma ihåg för praktisk användning.

  • Gränskontroll DataView kastar ett undantag om ett offset eller storlek överskrider buffertens gräns. Kontrollera alltid längder när du hanterar opålitlig binärdata.

  • Ange alltid bytteordning (endianness) Ange alltid uttryckligen och konsekvent little endian eller big endian i din kod.

  • Typkonsekvens JavaScript-nummer är 64-bitars IEEE-754 flyttal (float). Metoder som getUint32 hanteras korrekt, men det finns inget getUint64, så särskild hantering krävs för 64-bitars heltal.

  • Hantering av 64-bitars heltal För att hantera 64-bitars heltal måste du använda BigInt eller manuellt dela upp värdet i höga och låga 32-bitar. Här är ett enkelt exempel på att läsa ett 64-bitars osignerat 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}
  • Med BigInt kan du även hantera heltal som är större än 64 bitar på ett säkert sätt.

Användning i Node.js (samarbete med Buffer)

Även om Buffer ofta används i Node.js är det också enkelt att konvertera mellan ArrayBuffer och DataView. Använd buffer-egenskapen hos Buffer-objekt eller Uint8Array-konstruktorn för 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));
  • Detta kan användas för att utbyta binärdata mellan Node.js och webbläsare.

Sammanfattning

DataView är en kraftfull mekanism för att fritt läsa och skriva binärdata—särskilt användbar i situationer där flexibel kontroll, som att ange bytteordning eller komma åt godtyckliga positioner, behövs. Genom att kombinera ArrayBuffer, TypedArray och DataView kan du hantera binärdata flexibelt och exakt i TypeScript, vilket möjliggör allt från protokollimplementation till filanalys. Genom att även inkludera strängkodning och hantering av 64-bitars heltal när det behövs, kan du uppnå än mer praktiska binärdatamanipulationer.

Du kan följa med i artikeln ovan med hjälp av Visual Studio Code på vår YouTube-kanal. Vänligen kolla även in YouTube-kanalen.

YouTube Video