Un'altra bicicletta: guardemu stringhe Unicode 30-60% più compatte cà UTF-8

Un'altra bicicletta: guardemu stringhe Unicode 30-60% più compatte cà UTF-8

Sè vo site un sviluppatore è site affruntatu cù u compitu di sceglie una codificazione, allora Unicode serà quasi sempre a suluzione ghjusta. U metudu di rapprisintazioni specifichi dipende di u cuntestu, ma a maiò spessu ci hè una risposta universale ancu quì - UTF-8. A cosa bona hè chì permette di utilizà tutti i caratteri Unicode senza spende troppu assai bytes in a maiò parte di i casi. Hè veru, per e lingue chì utilizanu più di l'alfabetu latinu, "micca troppu" hè almenu dui bytes per caratteru. Pudemu fà megliu senza vultà à e codificazioni preistoriche chì ci limitanu à solu 256 caratteri dispunibili?

Sottu pruponu di familiarizàvi cù u mo tentativu di risponde à sta quistione è implementà un algoritmu relativamente simplice chì vi permette di almacenà e linee in a maiò parte di e lingue di u mondu senza aghjunghje a redundanza chì hè in UTF-8.

Disclaimer. Faraghju subitu uni pochi di riservazioni impurtanti: a suluzione descritta ùn hè micca pruposta cum'è un sustitutu universale per UTF-8, hè solu adattatu in una lista ristretta di casi (più nantu à elli sottu), è in nisun casu ùn deve esse usatu per interagisce cù l'API di terzu (chì ùn ne sanu mancu). A maiò spessu, l'algoritmi di cumpressione generale (per esempiu, deflate) sò adattati per u almacenamentu compactu di grandi volumi di dati di testu. Inoltre, digià in u prucessu di creà a mo suluzione, aghju trovu un standard esistente in Unicode stessu, chì risolve u stessu prublema - hè un pocu più cumplicatu (è spessu peghju), ma hè sempre un standard accettatu, è micca solu mette. inseme nantu à u ghjinochju. Vi dicu ancu di ellu.

À propositu di Unicode è UTF-8

Per principià, uni pochi di parolle nantu à ciò chì hè Unicode и UTF-8.

Comu sapete, i codificazioni di 8-bit eranu populari. Cù elli, tuttu era simplice: 256 caratteri ponu esse numerati cù numeri da 0 à 255, è numeri da 0 à 255 ponu esse ovviamente rapprisintati cum'è un byte. Se vulemu à u principiu, a codificazione ASCII hè cumplettamente limitata à 7 bits, cusì u bit più significativu in a so rapprisintazioni di byte hè cero, è a maiò parte di e codificazioni di 8-bit sò cumpatibili cun ella (differiscenu solu in u "superior". parte, induve u bit più significativu hè unu).

Cumu l'Unicode differisce da queste codificazioni è perchè ci sò tante rapprisentazioni specifiche assuciate cù questu - UTF-8, UTF-16 (BE è LE), UTF-32? Chjamemu in ordine.

U standard Unicode di basa descrive solu a currispundenza trà caratteri (è in certi casi, cumpunenti individuali di caratteri) è i so numeri. E ci sò assai numeri pussibuli in questu standard - da 0x00 à 0x10FFFF (1 pezzi). Sè avemu vulsutu mette un numeru in un tali intervallu in una variàbile, nè 114 nè 112 byte ùn ci bastarianu. E postu chì i nostri prucessori ùn sò micca assai cuncepiti per travaglià cù numeri di trè byte, seremu furzati à aduprà quant'è 1 bytes per caratteru! Questu hè UTF-2, ma hè precisamente per via di questa "spertura" chì stu formatu ùn hè micca populari.

Fortunatamente, l'ordine di caratteri in Unicode ùn hè micca casuale. U so gruppu tutale hè divisu in 17 "aerei", ognunu di i quali cuntene 65536 (0x10000) "punti codice" U cuncettu di un "puntu di codice" quì hè simplicemente numeru di caratteru, assignatu à ellu da Unicode. Ma, cum'è dettu sopra, in Unicode ùn sò micca solu i caratteri individuali sò numerati, ma ancu i so cumpunenti è i marchi di serviziu (è qualchì volta nunda currisponde à u numeru - forsi per u mumentu, ma per noi questu ùn hè micca cusì impurtante), cusì hè più currettu sempre parlà specificamente nantu à u numeru di numeri stessi, è micca simboli. In ogni casu, in seguitu, per a brevità, spessu aduprà a parolla "simbulu", chì implica u terminu "puntu di codice".

Un'altra bicicletta: guardemu stringhe Unicode 30-60% più compatte cà UTF-8
i piani Unicode. Comu pudete vede, a maiò parte di questu (aerei 4 à 13) hè sempre inutilizatu.

Ciò chì hè più notevuli hè chì tutta a "polpa" principale si trova in u pianu zero, hè chjamatu "Pianu multilingue di base". Se una linea cuntene testu in una di e lingue muderne (cumpresu u Cinese), ùn anderete micca più di stu pianu. Ma ùn pudete micca taglià ancu u restu di Unicode - per esempiu, l'emoji sò principalmente situati à a fine di u prossimu avionu "Pianu supplementu multilingue"(si estende da 0x10000 à 0x1FFFF). Allora UTF-16 face questu: tutti i caratteri chì cadenu in Pianu multilingue di base, sò codificati "cum'è" cù un numeru currispundente di dui byte. In ogni casu, alcuni di i numeri in questa gamma ùn indicanu micca caratteri specifichi, ma indicanu chì dopu à stu paru di byte avemu bisognu di cunsiderà un altru - cumminendu i valori di questi quattru byte inseme, avemu un numeru chì copre. tutta a gamma Unicode valida. Questa idea hè chjamata "coppie surrogate" - pudete avè intesu parlà di elli.

Allora UTF-16 richiede dui o (in casi assai rari) quattru byte per "puntu di codice". Questu hè megliu cà l'usu di quattru byte in tuttu u tempu, ma u latinu (è altri caratteri ASCII) quandu codificati in questu modu perde a mità di u spaziu in zeri. UTF-8 hè designatu per correggerà questu: ASCII in questu occupa, cum'è prima, solu un byte; codici da 0x80 à 0x7FF - dui bytes; da 0x800 à 0xFFFF - trè, è da 0x10000 à 0x10FFFF - quattru. Da una banda, l'alfabetu latinu hè diventatu bonu: a cumpatibilità cù l'ASCII hè tornata, è a distribuzione hè più uniforme "sparghje" da 1 à 4 bytes. Ma l'alfabeti altru ch'è u latinu, sfortunatamente, ùn anu micca benefiziu in ogni modu cumparatu cù UTF-16, è parechji necessitanu avà trè byte invece di dui - a gamma coperta da un record di dui byte hè ristretta da 32 volte, cù 0xFFFF à 0x7FF, è nè cinese nè, per esempiu, georgianu sò inclusi in questu. Cirillicu è cinque altri alfabeti - hurrah - furtunatu, 2 bytes per caratteru.

Perchè succede questu? Videmu cumu UTF-8 rapprisenta i codici di caratteri:
Un'altra bicicletta: guardemu stringhe Unicode 30-60% più compatte cà UTF-8
Direttamente per rapprisintà i numeri, i bits marcati cù u simbulu sò usati quì x. Pò esse vistu chì in un record di dui byte ci sò solu 11 tali bits (fora di 16). I pezzi principali quì anu solu una funzione ausiliaria. In u casu di un registru di quattru byte, 21 da 32 bits sò attribuiti per u numeru di u codice - pare chì trè byte (chì dà un totale di 24 bits) seranu abbastanza, ma i marcatori di serviziu manghjanu troppu.

Hè questu male? Micca essatamente. Per una banda, se avemu cura assai di u spaziu, avemu algoritmi di cumpressione chì ponu facilmente sguassà tutte l'entropia extra è a redundanza. Per d 'altra banda, u scopu di Unicode era di furnisce a codificazione più universale pussibule. Per esempiu, pudemu affidà una linea codificata in UTF-8 à u codice chì prima hà travagliatu solu cù ASCII, è ùn avè micca paura di vede un caratteru da a gamma ASCII chì ùn hè in realtà micca quì (dopu tuttu, in UTF-8 tutti. bytes partendu da u bit zero - questu hè esattamente ciò chì hè ASCII). E se vulemu di colpu taglià una piccula cuda da una grande stringa senza decodificà da u principiu (o restaurà una parte di l'infurmazioni dopu à una sezione dannata), hè faciule per noi di truvà l'offset induve un caratteru principia (hè abbastanza. per saltà i byte chì anu un pocu prefissu 10).

Perchè allora inventà qualcosa di novu?

À u listessu tempu, ci sò occasionalmente situazioni quandu l'algoritmi di compressione cum'è deflate sò pocu applicabili, ma vulete ottene un almacenamentu compactu di corde. In modu persunale, aghju scontru stu prublema quandu pensava à custruisce arbre di prefissu cumpressu per un grande dizziunariu chì include parolle in lingue arbitrarie. Da una banda, ogni parolla hè assai corta, cusì cumpressione serà inefficace. Per d 'altra banda, l'implementazione di l'arburu chì aghju cunsideratu hè stata cuncepita in modu chì ogni byte di a stringa almacenata generò un vertice di l'arburu separatu, cusì minimizà u so numeru era assai utile. In a mo biblioteca Az.js (Com'è in pimorfia 2, nantu à quale hè basatu) un prublema simili pò esse risolta simpliciamente - strings packed into DAWG-dizziunariu, cullucatu quì in bonu vechju CP1251. Ma, cum'è faciule da capisce, questu funziona bè solu per un alfabetu limitatu - una linea in cinese ùn pò micca esse aghjuntu à un tali dizziunariu.

Separatamente, vogliu nutà una sfumatura più dispiacevule chì si sviluppa quandu si usa UTF-8 in una struttura di dati cusì. A stampa sopra mostra chì quandu un caratteru hè scrittu cum'è dui byte, i bits ligati à u so numeru ùn venenu micca in una fila, ma sò siparati da un paru di bit. 10 à mezu: 110xxxxx 10xxxxxx. Per quessa, quandu i 6 bits più bassi di u sicondu byte overflow in u codice di caratteri (vale à dì, una transizione si trova 1011111110000000), allora u primu byte cambia ancu. Ci hè chì a lettera "p" hè indicata da byte 0xD0 0xBF, è a prossima "r" hè digià 0xD1 0x80. In un arbulu di prefissu, questu porta à a divisione di u node parent in dui - unu per u prefissu. 0xD0, è un altru per 0xD1 (Ancu se tuttu l'alfabetu cirillicu puderia esse codificatu solu da u secondu byte).

Chì aghju avutu

In fronte à stu prublema, decisu di praticà i ghjoculi cù bits, è à u stessu tempu cunniscite un pocu megliu cù a struttura di Unicode in tuttu. U risultatu era u formatu di codificazione UTF-C ("C" per fundute), chì ùn spende micca più di 3 bytes per puntu di codice, è assai spessu permette di passà solu un byte extra per tutta a linea codificata. Questu porta à u fattu chì in parechji alfabeti non-ASCII, tali codificazioni risultanu esse 30-60% più compactu cà UTF-8.

Aghju prisentatu esempi di implementazione di algoritmi di codificazione è decodificazione in a forma Biblioteche JavaScript è Go, pudete aduprà liberamente in u vostru codice. Ma aghju sempre enfatizatu chì in un certu sensu stu formatu resta una "bicicletta", è ùn ricumandemu micca di utilizà. senza capisce perchè avete bisognu. Questu hè sempre più un esperimentu chè una seria "migliura di UTF-8". In ogni casu, u codice hè scrittu bè, cuncisu, cù un gran numaru di cumenti è una copertura di teste.

Un'altra bicicletta: guardemu stringhe Unicode 30-60% più compatte cà UTF-8
Risultati di teste è paraguni cù UTF-8

Aghju ancu fattu pagina demo, induve pudete evaluà u rendiment di l'algoritmu, è dopu vi dicu più nantu à i so principii è u prucessu di sviluppu.

Eliminazione di bit redundant

Aghju pigliatu UTF-8 cum'è una basa, sicuru. U primu è u più ovvi chì pò esse cambiatu in questu hè di riduce u numeru di bits di serviziu in ogni byte. Per esempiu, u primu byte in UTF-8 principia sempre cù l'una o l'altra 0, o cun 11 - un prefissu 10 Solu i seguenti byte l'anu. Sustituemu u prefissu 11 nantu 1, è per i prossimi bytes sguassemu i prefissi cumpletamente. Chì succede ?

0xxxxxxx - 1 byte
10xxxxxx xxxxxxxx - 2 bytes
110xxxxx xxxxxxxx xxxxxxxx - 3 bytes

Aspetta, induve hè u record di quattru byte? Ma ùn hè più necessariu - quandu scrivite in trè byte, avemu avà 21 bits dispunibili è questu hè abbastanza per tutti i numeri finu à 0x10FFFF.

Chì avemu sacrificatu quì ? A più impurtante hè a deteczione di e fruntiere di caratteri da un locu arbitrariu in u buffer. Ùn pudemu micca indicà un byte arbitrariu è truvà u principiu di u prossimu caratteru da ellu. Questa hè una limitazione di u nostru furmatu, ma in pratica hè raramente necessariu. Di solitu simu capaci di curriri à traversu u buffer da u principiu (in particulare quandu si tratta di linee brevi).

A situazione cù copre lingue cù 2 byte hè ancu diventata megliu: avà u furmatu di dui byte dà una gamma di 14 bit, è questi sò codici finu à 0x3FFF. I Cinesi sò sfurtunati (i so caratteri sò soprattuttu da 0x4E00 à 0x9FFF), ma i georgiani è parechji altri populi si diverte più - e so lingue sò ancu in 2 bytes per caratteru.

Entre in u statu di codificatore

Pensemu avà à e pruprietà di e linee stessi. U dizziunariu cuntene più spessu parolle scritte in caratteri di u stessu alfabbetu, è questu hè ancu veru per parechji altri testi. Saria bonu per indicà stu alfabbetu una volta, è poi indicà solu u numeru di a lettera in questu. Videmu s'ellu ci aiuterà a disposizione di caratteri in a tavola Unicode.

Cumu l'anu dettu sopra, Unicode hè divisu in aviò 65536 codici ognunu. Ma questu ùn hè micca una divisione assai utile (cum'è digià dettu, più spessu simu in u pianu zero). Più interessante hè a divisione per blocchi. Questi intervalli ùn anu più una lunghezza fissa, è sò più significati - in regula, ognunu combina caratteri da u stessu alfabetu.

Un'altra bicicletta: guardemu stringhe Unicode 30-60% più compatte cà UTF-8
Un blocu chì cuntene caratteri di l'alfabetu bengalese. Sfurtunatamente, per ragioni storichi, questu hè un esempiu di imballaggio micca assai densu - 96 caratteri sò spargugliati caoticamente in 128 punti di codice di bloccu.

L'iniziu di blocchi è e so dimensioni sò sempre multiplici di 16 - questu hè fattu solu per comodità. Inoltre, parechji blocchi cumincianu è finiscinu nantu à i valori chì sò multiplici di 128 o ancu 256 - per esempiu, l'alfabetu cirillicu di basa piglia 256 bytes da 0x0400 à 0x04FF. Questu hè abbastanza cunvene: se salvemu u prefissu una volta 0x04, allura ogni caratteru cirillicu pò esse scrittu in un byte. Hè veru, cusì perdemu l'uppurtunità di vultà in ASCII (è à qualsiasi altri caratteri in generale). Dunque facemu questu:

  1. Dui byte 10yyyyyy yxxxxxxx micca solu denote un simbulu cù un numeru yyyyyy yxxxxxxx, ma ancu cambià alfabetu attuale nantu yyyyyy y0000000 (vale à dì, ricurdemu di tutti i pezzi eccettu i menu significativi 7 bit);
  2. Un byte 0xxxxxxx questu hè u caratteru di l'alfabetu attuale. Solu deve esse aghjuntu à l'offset chì avemu ricurdatu in u passu 1. Mentre ùn avemu micca cambiatu l'alfabetu, l'offset hè cero, cusì mantenemu a cumpatibilità cù ASCII.

Cume per i codici chì necessitanu 3 byte:

  1. Trè byte 110yyyyy yxxxxxxx xxxxxxxx indicà un simbulu cù un numeru yyyyyy yxxxxxxx xxxxxxxx, cambià alfabetu attuale nantu yyyyyy y0000000 00000000 (ricurdava tuttu fora di i più ghjovani 15 bit), è verificate a casella chì simu avà longu modu (quandu cambiate l'alfabetu torna à un doppiu byte, reseteremu sta bandiera);
  2. Dui byte 0xxxxxxx xxxxxxxx in modu longu hè u caratteru di l'alfabetu attuale. In listessu modu, aghjunghjemu cù l'offset da u passu 1. L'unica diferenza hè chì avà leghjemu dui bytes (perchè avemu cambiatu à questu modu).

Sona bè: avà, mentre avemu bisognu di codificà caratteri da a listessa gamma Unicode 7-bit, passemu 1 byte extra à u principiu è un totale di un byte per caratteru.

Un'altra bicicletta: guardemu stringhe Unicode 30-60% più compatte cà UTF-8
U travagliu da una di e versioni prima. Dighjà spessu batte UTF-8, ma ci hè sempre spaziu per migliurà.

Chì hè peghju ? Prima, avemu una cundizione, vale à dì offset di l'alfabetu attuale è checkbox modu longu. Questu ci limita ancu più: avà i stessi caratteri ponu esse codificati in modu diversu in diversi cuntesti. A ricerca di substrings, per esempiu, duverà esse fattu tenendu questu in contu, è micca solu paragunendu byte. Siconda, quandu avemu cambiatu l'alfabetu, hè diventatu male cù a codificazione di caratteri ASCII (è questu ùn hè micca solu l'alfabetu latinu, ma ancu a puntuazione basica, cumprese spazii) - anu bisognu di cambià l'alfabetu novu à 0, vale à dì, novu un byte extra (è dopu un altru per vultà à u nostru puntu principale).

Un alfabetu hè bonu, dui hè megliu

Pruvemu di cambià un pocu i nostri prefissi di pocu, stringhjendu in unu di più à i trè descritti sopra:

0xxxxxxx - 1 byte in modu normale, 2 in modu longu
11xxxxxx - 1 byte
100xxxxx xxxxxxxx - 2 bytes
101xxxxx xxxxxxxx xxxxxxxx - 3 bytes

Un'altra bicicletta: guardemu stringhe Unicode 30-60% più compatte cà UTF-8

Avà in un registru di dui byte ci hè un pocu menu dispunibule - punti di codice finu à 0x1FFFè micca 0x3FFF. Tuttavia, hè sempre notevolmente più grande ch'è in i codici UTF-8 à doppiu byte, a maiò parte di e lingue cumune si adattanu sempre, a perdita più notevole hè cascata. hiragana и katakana, i Giapponesi sò tristi.

Chì ghjè stu novu codice? 11xxxxxx? Questu hè un picculu "stash" di 64 caratteri in grandezza, cumplementa u nostru alfabetu principale, cusì l'aghju chjamatu auxiliary (auxiliare) alfabetu. Quandu cambiamu l'alfabetu attuale, un pezzu di l'alfabbetu anticu diventa ausiliariu. Per esempiu, avemu cambiatu da ASCII à Cyrillic - u stash cuntene avà 64 caratteri chì cuntenenu Alfabetu latinu, numeri, spaziu è virgula (inserzioni più frequenti in testi non-ASCII). Turnate à ASCII - è a parte principale di l'alfabetu cirillicu diventerà l'alfabetu ausiliariu.

Grazie à l'accessu à dui alfabeti, pudemu trattà un gran numaru di testi cù costi minimi per cambià l'alfabetu (a puntuazione a più spessu porta à un ritornu à l'ASCII, ma dopu avè ottene assai caratteri non-ASCII da l'alfabetu supplementu, senza cambiendu di novu).

Bonus: prefixing u sub-alfabetu 11xxxxxx è scegliendu u so offset iniziale per esse 0xC0, avemu a cumpatibilità parziale cù CP1252. In altre parolle, parechji (ma micca tutti) testi di l'Europa Occidentale codificati in CP1252 pareranu listessi in UTF-C.

Quì, però, nasce una difficultà: cumu uttene un ausiliariu da l'alfabetu principale? Pudete lascià u listessu offset, ma - ahimè - quì a struttura Unicode hè digià ghjucatu contru à noi. Assai spessu a parte principale di l'alfabetu ùn hè micca à u principiu di u blocu (per esempiu, a capitale russa "A" hà u codice 0x0410, anche si u bloccu cirillicu principia cù 0x0400). Cusì, dopu avè pigliatu i primi 64 caratteri in u stash, pudemu perde l'accessu à a parte di a cuda di l'alfabetu.

Per risolve stu prublema, aghju passatu manualmente alcuni blocchi chì currispondenu à diverse lingue, è specificatu l'offset di l'alfabetu ausiliariu in u principale per elli. L'alfabbetu latinu, cum'è eccezzioni, era generalmente riordinatu cum'è basa64.

Un'altra bicicletta: guardemu stringhe Unicode 30-60% più compatte cà UTF-8

Tocchi finali

Pensemu infine à induve altru pudemu migliurà qualcosa.

Nota chì u furmatu 101xxxxx xxxxxxxx xxxxxxxx permette di codificà numeri finu à 0x1FFFFF, è Unicode finisci prima, à 0x10FFFF. In altre parolle, l'ultimu puntu di codice serà rapprisintatu cum'è 10110000 11111111 11111111. Dunque, pudemu dì chì se u primu byte hè di a forma 1011xxxx (Induva xxxx più grande di 0), allora significa qualcosa d'altru. Per esempiu, pudete aghjunghje altri caratteri 15 quì chì sò sempre dispunibili per a codificazione in un byte, ma aghju decisu di fà in modu diversu.

Fighjemu quelli blocchi Unicode chì necessitanu trè byte avà. In fondu, cum'è digià citatu, questi sò caratteri chinesi - ma hè difficiule di fà qualcosa cun elli, ci sò 21 mila. Ma hiragana è katakana anu vulatu ancu quì - è ùn sò più tanti, menu di dui centu. E, postu chì avemu ricurdatu di u Giapponese, ci sò ancu emojis (in fatti, sò spargugliati in parechji lochi in Unicode, ma i blocchi principali sò in a gamma). 0x1F300 - 0x1FBFF). Se pensate à u fattu chì avà ci sò emojis chì sò assemblati da parechji punti di codice à una volta (per esempiu, l'emoji ‍‍‍Un'altra bicicletta: guardemu stringhe Unicode 30-60% più compatte cà UTF-8 custituitu da quant'è 7 codici!), Tandu diventa una vergogna cumpleta di passà trè byte nantu à ognunu (7 × 3 = 21 bytes per una icona, un incubo).

Dunque, selezziunà uni pochi intervalli selezziunati chì currispondenu à emoji, hiragana è katakana, rinumerali in una lista cuntinuu è codificate cum'è dui byte invece di trè:

1011xxxx xxxxxxxx

Grande: l'emoji sopra citataUn'altra bicicletta: guardemu stringhe Unicode 30-60% più compatte cà UTF-8, custituitu di punti di codice 7, pigghia 8 bytes in UTF-25, è l'avemu adattatu 14 (esattamente dui byte per ogni puntu di codice). In modu, Habr hà ricusatu di digerisce (sia in u vechju è in u novu editore), perchè aghju avutu à inserisce cù una stampa.

Pruvemu di risolve un altru prublema. Comu ricurdamu, l'alfabetu di basa hè essenzialmente altu 6 bits, chì avemu tenutu in mente è cola à u codice di ogni prossimu simbulu decoded. In u casu di caratteri chinesi chì sò in u bloccu 0x4E00 - 0x9FFF, questu hè o bit 0 o 1. Questu ùn hè micca assai cunvenutu: avemu bisognu di cambià constantemente l'alfabetu trà sti dui valori (vale à dì passà trè byte). Ma nutate chì in u modu longu, da u codice stessu pudemu sottrae u numeru di caratteri chì codificamu cù u modu curtu (dopu à tutti i trucchi descritti sopra, questu hè 10240) - allora a gamma di i jeroglifi cambierà à 0x2600 - 0x77FF, è in questu casu, in tuttu stu intervallu sanu, u più significativu 6 bits (fora di 21) serà uguali à 0. Cusì, sequenze di jeroglifi utilizanu dui bytes per ieroglifi (chì hè ottimali per una gamma cusì grande), senza pruvucannu cambiamenti di l'alfabetu.

Soluzioni alternative: SCSU, BOCU-1

L'esperti Unicode, avè appena lettu u tìtulu di l'articulu, prubabilmente si affrettaranu à ricurdà chì direttamente trà i standard Unicode ci hè Schema di Cumpressione Standard per Unicode (SCSU), chì descrive un metudu di codificazione assai simili à quellu descrittu in l'articulu.

Admessu onestamente: aghju amparatu nantu à a so esistenza solu dopu chì eru prufonda immersa à scrive a mo decisione. Se l'aghju cunnisciutu da u principiu, probabilmente avaria pruvatu à scrive una implementazione invece di vene cun u mo propiu approcciu.

Ciò chì hè interessante hè chì SCSU usa idee assai simili à quelli chì sò venuti cun mè stessu (invece di u cuncettu di "alfabeti" usanu "finestri", è ci sò più dispunibuli di quelli chì aghju). À u listessu tempu, stu formatu hà ancu disadvantages: hè un pocu più vicinu à l'algoritmi di compressione cà quelli di codificazione. In particulare, u standard dà assai metudi di rapprisintazioni, ma ùn dice micca cumu sceglie l'ottima - per questu, l'encoder deve aduprà un tipu d'euristica. Cusì, un codificatore SCSU chì pruduce un bonu imballaggio serà più cumplessu è più ingombrante chì u mo algoritmu.

Per paragunà, aghju trasfirutu una implementazione relativamente simplice di SCSU à JavaScript - in quantu à u voluminu di codice, hè risultatu paragunabile à u mo UTF-C, ma in certi casi u risultatu era decine di per centu peggiu (a volte pò esse superatu, ma micca assai). Per esempiu, i testi in ebraicu è grecu sò stati codificati da UTF-C 60% megliu cà SCSU (probabilmente per via di i so alfabeti compatti).

Separatamente, aghju aghjustatu chì, in più di SCSU, ci hè ancu un altru modu per rapprisintà Unicode in modu compactu - BOCU-1, ma hà u scopu di a cumpatibilità MIME (chì ùn aghju micca bisognu) è piglia un accostu un pocu sfarente à a codificazione. Ùn aghju micca valutatu a so efficacità, ma mi pari chì hè improbabile di esse più altu ch'è SCSU.

Migliuramenti pussibuli

L'algoritmu ch'e aghju prisintatu ùn hè micca universale da u disignu (questu hè probabilmente induve i mo scopi divergenu più da i scopi di u Consorziu Unicode). Aghju digià dettu ch'ellu hè statu sviluppatu principarmenti per un compitu (almacenà un dizziunariu multilingue in un arbulu di prefissu), è alcune di e so funziunalità pò esse micca bè adattatu per altre attività. Ma u fattu chì ùn hè micca un standard pò esse un plus - pudete facilmente mudificà per adattà à i vostri bisogni.

Per esempiu, in u modu ovviu, pudete sbarazzà di a prisenza di u statu, fate codificazione senza statu - solu ùn aghjurnà micca e variàbili offs, auxOffs и is21Bit in l'encoder è decoder. In questu casu, ùn serà micca pussibule di imballà in modu efficace sequenze di caratteri di u stessu alfabetu, ma ci sarà una guaranzia chì u stessu caratteru hè sempre codificatu cù i stessi byte, indipendentemente da u cuntestu.

Inoltre, pudete adattà l'encoder à una lingua specifica cambiendu u statu predeterminatu - per esempiu, cuncintratu nantu à i testi russi, stabilisce l'encoder è u decoder à u principiu. offs = 0x0400 и auxOffs = 0. Questu hè soprattuttu sensu in u casu di modu senza statu. In generale, questu serà simile à l'usu di l'antica codificazione di ottu bit, ma senza caccià a capacità di inserisce caratteri da tutti l'Unicode cum'è necessariu.

Un altru svantaghju mintuatu prima hè chì in u grande testu codificatu in UTF-C ùn ci hè micca un modu rapidu per truvà u cunfini di caratteri più vicinu à un byte arbitrariu. Se tagliate l'ultimi, per dì, 100 bytes da u buffer codificatu, rischiate di ottene basura chì ùn pudete micca fà nunda. A codificazione ùn hè micca pensata per almacenà logs multi-gigabyte, ma in generale questu pò esse currettu. Byte 0xBF ùn deve mai apparisce cum'è u primu byte (ma pò esse u sicondu o terzu). Dunque, quandu codificà, pudete inserisce a sequenza 0xBF 0xBF 0xBF ogni, dì, 10 KB - allora, se avete bisognu di truvà un cunfini, serà abbastanza per scansà u pezzu sceltu finu à truvà un marcatu simili. Dopu à l'ultimu 0xBF hè garantitu per esse u principiu di un caratteru. (Quandu a decodifica, sta sequenza di trè byte, di sicuru, deve esse ignorata.)

Per sintetizà

Sì avete lettu finu à quì, felicitazioni! Spergu chì, cum'è mè, avete amparatu qualcosa di novu (o rinfriscatu a vostra memoria) nantu à a struttura di Unicode.

Un'altra bicicletta: guardemu stringhe Unicode 30-60% più compatte cà UTF-8
Pagina Demo. L'esempiu di l'ebreu mostra i vantaghji nantu à UTF-8 è SCSU.

A ricerca sopra descritta ùn deve esse cunsiderata cum'è una violazione di i normi. Tuttavia, sò generalmente cuntentu cù i risultati di u mo travagliu, cusì sò cuntentu cun elli sparte: per esempiu, una biblioteca JS minificata pesa solu 1710 bytes (è ùn hà micca dipendenze, sicuru). Cumu l'aghju dettu sopra, u so travagliu pò esse truvatu in pagina demo (ci hè ancu un inseme di testi nantu à quale pò esse paragunatu cù UTF-8 è SCSU).

Infine, una volta attiraraghju l'attenzione à i casi in quale UTF-C hè utilizatu micca i bisognu:

  • Sì i vostri linii sò abbastanza longu (da 100-200 caratteri). In questu casu, avete da pensà à utilizà algoritmi di cumpressione cum'è deflate.
  • Sè avete bisognu A trasparenza ASCII, vale à dì, hè impurtante per voi chì e sequenze codificate ùn cuntenenu micca codici ASCII chì ùn eranu micca in a stringa originale. A necessità di questu pò esse evitata se, quandu interagisce cù API di terzu (per esempiu, travagliendu cù una basa di dati), passa u risultatu di codificazione cum'è un inseme astrattu di bytes, è micca cum'è strings. Altrimenti, rischiate di ottene vulnerabilità inespettate.
  • Se vulete esse capace di truvà rapidamente e fruntiere di caratteri à un offset arbitrariu (per esempiu, quandu una parte di una linea hè dannata). Questu pò esse fattu, ma solu scannendu a linea da u principiu (o applicà a mudificazione descritta in a sezione precedente).
  • Sè avete bisognu di fà rapidamente operazioni nantu à u cuntenutu di e stringhe (ordinate, cercate substrings in elli, concatenate). Questu hè bisognu di stringhe per esse decodificate prima, cusì UTF-C serà più lento di UTF-8 in questi casi (ma più veloce di l'algoritmi di compressione). Siccomu a listessa stringa hè sempre codificata in u listessu modu, a comparazione esatta di decodificazione ùn hè micca necessariu è pò esse fatta nantu à una basa di byte per byte.

aghjuntu: l 'utilizatori Tyomitch in i cumenti sottu hà publicatu un graficu chì evidenzia i limiti di applicabilità di UTF-C. Mostra chì UTF-C hè più efficace ch'è un algoritmu di compressione generale (una variazione di LZW) sempre chì a stringa imballata hè più corta. ~ 140 caratteri (in ogni casu, aghju nutatu chì u paragone hè statu fattu nantu à un testu; per altre lingue u risultatu pò esse diversu).
Un'altra bicicletta: guardemu stringhe Unicode 30-60% più compatte cà UTF-8

Source: www.habr.com

Add a comment