Cum să citiți și să scrieți date binare pentru formatele personalizate de fișiere

În articolul meu precedent, Creați formate personalizate de fișiere binare pentru datele jocului dvs., am abordat subiectul utilizând formate personalizate de fișiere binare pentru stocarea activelor și a resurselor jocului. În acest scurt tutorial vom analiza rapid modul în care puteți citi și scrie date binare.

Notă: Acest tutorial folosește pseudo-cod pentru a demonstra cum să citească și să scrie date binare, dar codul poate fi tradus cu ușurință în orice limbaj de programare care acceptă operațiile de bază I / O ale fișierelor.


Operatori biți

Dacă acest lucru este un teritoriu necunoscut pentru dvs., veți observa câțiva operatori ciudați fiind folosiți în cod, în special în &, |, << și >> operatori. Acestea sunt operatori standard de biți, disponibili în majoritatea limbajelor de programare, care sunt utilizați pentru manipularea valorilor binare.

postări asemănatoare
Pentru mai multe informații despre operatorii de biți, consultați:
  • Înțelegerea operatorilor de biți
  • Documentația pentru limba dvs. de programare de alegere

Endianitate și fluxuri

Înainte de a putea citi și scrie cu succes datele binare, există două concepte importante pe care trebuie să le înțelegem: endianness și fluxuri.

Endianitatea dictează ordinea valorilor mai multor octeți într-un fișier sau într-o bucată de memorie. De exemplu, dacă am avea o valoare pe 16 biți 0x1020, această valoare poate fi stocată fie ca 0x10 urmat de 0x20 (big-endian) sau 0x20 urmat de 0x10 (Little-endian).

Fluxurile sunt obiecte de tip array care conțin o secvență de octeți (sau biți în unele cazuri). Datele binare sunt citite și scrise în aceste fluxuri. Majoritatea programărilor vor oferi o implementare a fluxurilor binare într-o formă sau alta; unii sunt mai complicați decât alții, dar toți aceștia fac în mod esențial același lucru.


Citirea datelor binare

Să începem prin definirea unor proprietăți în codul nostru. În mod ideal, toate acestea ar trebui să fie proprietăți private:

 __stream // Obiectul array-like care conține octeții __endian // Endianitatea datelor în fluxul __length // Numărul de octeți din fluxul __position // Poziția următorului octet de citit din flux

Iată un exemplu despre cum poate arăta un constructor de clasă de bază:

 clasa de dateInput (flux, endian) __stream = flux __endian = endian __length = stream.length __position = 0

Următoarele funcții vor citi numerele nesemnate din flux:

 // Citeste o functie integrala nesemnificati pe 8 biti readU8 () // Aruncati o exceptie daca nu mai exista octeti disponibili pentru a citi daca (__position> = __length) arunca noua Exceptie ("...") (__endian == BIG_ENDIAN) value = 0 // Endianitatea trebuie să fie tratată pentru valori multiple-byte dacă (__endian == BIG_ENDIAN) value | = readU8 () << 8 value |= readU8() << 0  else  // LITTLE_ENDIAN value |= readU8() << 0 value |= readU8() << 8  return value  // Reads an unsigned 24-bit integer function readU24()  value = 0 if( __endian == BIG_ENDIAN )  value |= readU8() << 16 value |= readU8() << 8 value |= readU8() << 0  else  value |= readU8() << 0 value |= readU8() << 8 value |= readU8() << 16  return value  // Reads an unsigned 32-bit integer function readU32()  value = 0 if( __endian == BIG_ENDIAN )  value |= readU8() << 24 value |= readU8() << 16 value |= readU8() << 8 value |= readU8() << 0  else  value |= readU8() << 0 value |= readU8() << 8 value |= readU8() << 16 value |= readU8() << 24  return value 

Aceste funcții vor citi numere întregi din flux:

 / / Citiți valoarea valorii nesemnate = readU8 () // Verificați dacă primul bit (cel mai semnificativ) indică o valoare negativă dacă (valoarea >> 7 == 1) // Utilizați "Suplimentul celor doi" pentru a converti valoarea valorii = ~ (valoarea ^ 0xFF) valoarea returnată // Citește o funcție integrată de 16 biți semnată readS16 () value = readU16 = Valoare = 0 value = ~ (valoare ^ 0xFFFF) valoarea returnata // citeste o functie integrata 24-bit citita 24 (valoare = 0xFFFFFF) valoarea returnata // citeste o functie integrata pe 32 de biti semnate readS32 () value = readU32 () daca (valoare >> 31 == 1) value = ~ value ^ 0xFFFFFFFF

Scrierea datelor binare

Să începem prin definirea unor proprietăți în codul nostru. (Acestea sunt mai mult sau mai puțin identice cu proprietățile pe care le-am definit pentru citirea datelor binare.) În mod ideal, toate acestea ar trebui să fie proprietăți private:

 __stream // Obiectul array-like care va conține octeții __endian // Endianitatea datelor din fluxul __position // Poziția următorului octet pentru a scrie în flux

Iată un exemplu despre cum poate arăta un constructor de clasă de bază:

 clasa de dateOutput (flux, endian) __stream = flux __endian = endian __position = 0

Următoarele funcții vor scrie numere nesemnate în flux:

 // scrie o notă interogantă unsigned pe 8 biți writeU8 (valoare) // Asigură valoarea nesemnată și într-o valoare a intervalului de 8 biți & = 0xFF // Adaugă valoarea în flux și mărește proprietatea __position. __stream [__position ++] = value // scrie o functie intrega nesemnificata pe 16 biți writeU16 (valoare) value & = 0xFFFF // Endianness trebuie manipulată pentru valori multiple byte dacă (__endian == BIG_ENDIAN) writeU8 valoare >> 8) writeU8 (valoare >> 0) altfel // LITTLE_ENDIAN writeU8 (valoare >> 0) writeU8 (valoare >> 8) // Scrieți o notă integeră nesemnată de 24 de biți writeU24 (valoare) & = 0xFFFFFF dacă (__endian == BIG_ENDIAN) writeU8 (valoare >> 16) writeU8 (valoare >> 8) writeU8 (valoare >> 0) else8 (valoarea >> 16) // scrie o valoare nesemnată pe 32 de biți a funcției întregi writeU32 (valoare) value & = 0xFFFFFFFF dacă (__endian == BIG_ENDIAN) writeU8 (valoare >> 24) writeU8 (value >> 16) writeU8 (valoare >> 8) writeU8 (valoare >> 0) else writeU8 (valoare >> 0) writeU8 (valoare >> 8) writeU8 (valoare >> 16) writeU8

Și, din nou, aceste funcții vor scrie numere întregi în flux. (Funcțiile sunt de fapt aliasuri ale writeU * () funcții, dar asigură compatibilitatea API cu * READS () funcții.)

 // scrie o valoare semnată pe 8 biți writeS8 (valoare) writeU8 (valoare) // scrie o funcție de valoare pe 16 biți semnată writeS16 (valoare) writeU16 (valoare) // scrie o funcție de valoare pe 24 de biți semnată writeS24 (valoare) writeU24 (valoare) // scrie o funcție de valoare pe 32 de biți semnată writeS32 (valoare) writeU32 (valoare)

Notă: Aceste pseudonime funcționează deoarece datele binare sunt întotdeauna stocate ca valori nesemnate; de exemplu, un singur octet va avea întotdeauna o valoare în intervalul de la 0 la 255. Conversia la valorile semnate se face atunci când datele sunt citite dintr-un flux.


Concluzie

Scopul meu cu acest tutorial scurt a fost de a completa articolul meu anterior despre crearea de fișiere binare pentru datele jocului dvs. cu câteva exemple de cum se face citirea și scrierea. Sper că acest lucru este realizat; dacă aveți mai multe informații despre subiect, vă rugăm să vorbiți în comentarii!