Vēl viens velosipēds: mēs glabājam Unicode virknes par 30ā€“60% kompaktākas nekā UTF-8

Vēl viens velosipēds: mēs glabājam Unicode virknes par 30ā€“60% kompaktākas nekā UTF-8

Ja esat izstrādātājs un jÅ«s saskaraties ar uzdevumu izvēlēties kodējumu, Unicode gandrÄ«z vienmēr bÅ«s pareizais risinājums. Konkrētā attēlojuma metode ir atkarÄ«ga no konteksta, taču visbiežāk arÄ« Å”eit ir universāla atbilde - UTF-8. Labā lieta ir tā, ka tas ļauj izmantot visas Unikoda rakstzÄ«mes bez tēriņiem pārāk daudz vairumā gadÄ«jumu daudz baitu. Tiesa, valodām, kurās tiek lietots vairāk nekā tikai latīņu alfabēts, ā€œne pārāk daudzā€ ir vismaz divi baiti katrai rakstzÄ«mei. Vai mēs varam darÄ«t labāk, neatgriežoties pie aizvēsturiskiem kodumiem, kas ierobežo tikai 256 pieejamās rakstzÄ«mes?

Zemāk es ierosinu iepazÄ«ties ar manu mēģinājumu atbildēt uz Å”o jautājumu un ieviest salÄ«dzinoÅ”i vienkārÅ”u algoritmu, kas ļauj saglabāt lÄ«nijas lielākajā daļā pasaules valodu, nepievienojot UTF-8 dublÄ“Å”anos.

Atruna. Es nekavējoties izdarÄ«Å”u dažas svarÄ«gas atrunas: aprakstÄ«tais risinājums netiek piedāvāts kā universāls UTF-8 aizstājējs, tas ir piemērots tikai Å”aurā gadÄ«jumu sarakstā (vairāk par tiem tālāk), un nekādā gadÄ«jumā to nedrÄ«kst izmantot, lai mijiedarbotos ar treÅ”o puÅ”u API (kuri par to pat nezina). Visbiežāk liela apjoma teksta datu kompaktai glabāŔanai ir piemēroti vispārējas nozÄ«mes saspieÅ”anas algoritmi (piemēram, deflācija). Turklāt jau sava risinājuma tapÅ”anas procesā es atradu jau esoÅ”u standartu paŔā Unicode, kas atrisina to paÅ”u problēmu - tas ir nedaudz sarežģītāks (un bieži vien sliktāks), bet tomēr tas ir pieņemts standarts, nevis vienkārÅ”i ievietots. kopā uz ceļa. Es tev arÄ« pastāstÄ«Å”u par viņu.

Par Unicode un UTF-8

Sākumā daži vārdi par to, kas tas ir Unikode Šø UTF-8.

Kā zināms, 8 bitu kodējumi agrāk bija populāri. Ar tiem viss bija vienkārÅ”i: 256 rakstzÄ«mes var numurēt ar cipariem no 0 lÄ«dz 255, un skaitļus no 0 lÄ«dz 255 acÄ«mredzot var attēlot kā vienu baitu. Ja mēs atgriežamies paŔā sākumā, tad ASCII kodējums ir pilnÄ«bā ierobežots lÄ«dz 7 bitiem, tāpēc nozÄ«mÄ«gākais bits tā baitu attēlojumā ir nulle, un lielākā daļa 8 bitu kodējumu ir ar to saderÄ«gi (tie atŔķiras tikai ar ā€œaugŔējoā€ daļa, kur nozÄ«mÄ«gākais bits ir viens ).

Ar ko Unicode atŔķiras no Å”iem kodējumiem un kāpēc ar to ir saistÄ«ti tik daudzi specifiski attēlojumi - UTF-8, UTF-16 (BE un LE), UTF-32? Sakārtosim to secÄ«bā.

Unikoda pamatstandarts apraksta tikai atbilstÄ«bu starp rakstzÄ«mēm (un dažos gadÄ«jumos atseviŔķiem rakstzÄ«mju komponentiem) un to numuriem. Un Å”ajā standartā ir daudz iespējamo skaitļu - no 0x00 lÄ«dz 0x10FFFF (1 114 112 gab.). Ja mēs gribētu ielikt mainÄ«gajā skaitli Ŕādā diapazonā, mums nepietiktu ne ar 1, ne 2 baitiem. Un tā kā mÅ«su procesori nav Ä«paÅ”i paredzēti darbam ar trÄ«s baitu cipariem, mēs bÅ«tu spiesti izmantot pat 4 baitus uz vienu rakstzÄ«mi! Tas ir UTF-32, taču tieÅ”i Ŕīs ā€œizŔķērdÄ«basā€ dēļ Å”is formāts nav populārs.

Par laimi, rakstzÄ«mju secÄ«ba Unicode nav nejauÅ”a. Viņu viss komplekts ir sadalÄ«ts 17 "lidmaŔīnas", no kuriem katrs satur 65536 (0x10000) Ā«koda punkti" ā€œKoda punktaā€ jēdziens Å”eit ir vienkārÅ”s rakstzÄ«mju numurs, ko tam pieŔķīris Unicode. Bet, kā minēts iepriekÅ”, Unicode ir numurētas ne tikai atseviŔķas rakstzÄ«mes, bet arÄ« to sastāvdaļas un servisa zÄ«mes (un dažreiz arÄ« nekas neatbilst ciparam - varbÅ«t pagaidām, bet mums tas nav tik svarÄ«gi), tāpēc pareizāk vienmēr runāt konkrēti par paÅ”u skaitļu skaitu, nevis simboliem. Tomēr turpmāk tekstā Ä«suma labad bieži izmantoÅ”u vārdu ā€œsimbolsā€, kas nozÄ«mē terminu ā€œkoda punktsā€.

Vēl viens velosipēds: mēs glabājam Unicode virknes par 30ā€“60% kompaktākas nekā UTF-8
Unikoda lidmaŔīnas. Kā redzat, lielākā daļa (4. līdz 13. lidmaŔīnas) joprojām ir neizmantota.

Ievērojamākais ir tas, ka visa galvenā "celuloze" atrodas nulles plaknē, to sauc par "Pamata daudzvalodu plakne". Ja rindā ir teksts kādā no mÅ«sdienu valodām (ieskaitot Ä·Ä«nieÅ”u), jÅ«s netiksiet tālāk par Å”o plakni. Bet jÅ«s nevarat arÄ« nogriezt pārējo Unikoda daļu - piemēram, emocijzÄ«mes galvenokārt atrodas valodas beigās nākamā lidmaŔīna"Papildu daudzvalodu plakne"(tas stiepjas no 0x10000 lÄ«dz 0x1FFFF). Tātad UTF-16 dara to: visas rakstzÄ«mes, kas ietilpst Pamata daudzvalodu plakne, ir kodēti ā€œkā irā€ ar atbilstoÅ”u divu baitu numuru. Tomēr daži skaitļi Å”ajā diapazonā vispār nenorāda konkrētas rakstzÄ«mes, bet norāda, ka pēc Ŕī baitu pāra mums ir jāņem vērā vēl viens - apvienojot Å”o četru baitu vērtÄ«bas kopā, mēs iegÅ«stam skaitli, kas aptver viss derÄ«gais Unikoda diapazons. Å o ideju sauc par "surogātpāriem" ā€” jÅ«s, iespējams, esat par tiem dzirdējuÅ”i.

Tātad UTF-16 katram "koda punktam" ir nepiecieÅ”ami divi vai (ļoti retos gadÄ«jumos) četri baiti. Tas ir labāk, nekā visu laiku izmantot četrus baitus, taču latīņu valoda (un citas ASCII rakstzÄ«mes), ja tā tiek kodēta, pusi no vietas iztērē uz nullēm. UTF-8 ir paredzēts, lai to labotu: ASCII tajā, tāpat kā iepriekÅ”, aizņem tikai vienu baitu; kodi no 0x80 lÄ«dz 0x7FF - divi baiti; no 0x800 lÄ«dz 0xFFFF - trÄ«s, un no 0x10000 lÄ«dz 0x10FFFF - četras. No vienas puses, latīņu alfabēts ir kļuvis labs: ir atgriezusies saderÄ«ba ar ASCII, un sadalÄ«jums ir vienmērÄ«gāk ā€œizkliedētsā€ no 1 lÄ«dz 4 baitiem. Diemžēl citiem alfabētiem, izņemot latīņu alfabētu, nav nekāda labuma salÄ«dzinājumā ar UTF-16, un daudziem tagad ir nepiecieÅ”ami trÄ«s baiti, nevis divi ā€” divu baitu ierakstu aptvertais diapazons ir samazinājies par 32 reizēm. 0xFFFF lÄ«dz 0x7FF, un tajā nav iekļauta ne Ä·Ä«nieÅ”u, ne, piemēram, gruzÄ«nu valoda. Kirilica un pieci citi alfabēti - urrā - laimÄ«gs, 2 baiti katrai rakstzÄ«mei.

Kāpēc tas notiek? Apskatīsim, kā UTF-8 apzīmē rakstzīmju kodus:
Vēl viens velosipēds: mēs glabājam Unicode virknes par 30ā€“60% kompaktākas nekā UTF-8
TieÅ”i skaitļu attēloÅ”anai Å”eit tiek izmantoti biti, kas apzÄ«mēti ar simbolu x. Var redzēt, ka divu baitu ierakstā ir tikai 11 Ŕādi biti (no 16). Å eit vadoÅ”ajiem bitiem ir tikai palÄ«gfunkcija. Četru baitu ieraksta gadÄ«jumā koda punkta numuram tiek atvēlēts 21 no 32 bitiem - Ŕķiet, ka pietiktu ar trim baitiem (kas kopā dod 24 bitus), taču servisa marÄ·ieri apēd pārāk daudz.

Vai tas ir slikti? Ne Ä«sti. No vienas puses, ja mums ļoti rÅ«p telpa, mums ir saspieÅ”anas algoritmi, kas var viegli novērst visu papildu entropiju un dublÄ“Å”anos. No otras puses, Unicode mērÄ·is bija nodroÅ”ināt pēc iespējas universālāko kodÄ“Å”anu. Piemēram, mēs varam uzticēt UTF-8 kodētu rindiņu kodam, kas iepriekÅ” darbojās tikai ar ASCII, un nebaidÄ«ties, ka tā redzēs rakstzÄ«mi no ASCII diapazona, kuras patiesÄ«bā nav (galu galā UTF-8 baiti, kas sākas ar nulles bitu ā€” tieÅ”i tas ir ASCII). Un, ja mēs pēkŔņi vēlamies no lielas virknes nogriezt mazu asti, neatÅ”ifrējot to no paÅ”a sākuma (vai atjaunot daļu informācijas pēc bojātas sadaļas), mums ir viegli atrast nobÄ«di, kur sākas rakstzÄ«me (pietiek lai izlaistu baitus, kuriem ir bits prefikss 10).

Kāpēc tad izgudrot kaut ko jaunu?

Tajā paŔā laikā dažkārt ir situācijas, kad kompresijas algoritmi, piemēram, deflācija, ir slikti piemērojami, bet vēlaties panākt kompaktu virkņu glabāŔanu. PersonÄ«gi es saskāros ar Å”o problēmu, domājot par bÅ«vniecÄ«bu saspiests prefiksu koks lielai vārdnÄ«cai, kurā ir vārdi patvaļīgās valodās. No vienas puses, katrs vārds ir ļoti Ä«ss, tāpēc tā saspieÅ”ana bÅ«s neefektÄ«va. No otras puses, koka ievieÅ”ana, kuru es uzskatÄ«ju, tika izstrādāta tā, lai katrs saglabātās virknes baits Ä£enerētu atseviŔķu koka virsotni, tāpēc to skaita samazināŔana bija ļoti noderÄ«ga. Manā bibliotēkā Az.js (Kā pimorfija2, uz kuriem tas ir balstÄ«ts) lÄ«dzÄ«gu problēmu var atrisināt vienkārÅ”i - virknes iepakotas DAWG-vārdnÄ«ca, kas tur tiek glabāta vecais labais CP1251. Bet, kā tas ir viegli saprotams, tas labi darbojas tikai ierobežotam alfabētam ā€” Ŕādai vārdnÄ«cai nevar pievienot rindiņu Ä·Ä«nieÅ”u valodā.

AtseviŔķi es vēlētos atzÄ«mēt vēl vienu nepatÄ«kamu niansi, kas rodas, izmantojot UTF-8 Ŕādā datu struktÅ«rā. AugŔējā attēlā redzams, ka, rakstot rakstzÄ«mi kā divus baitus, ar tās numuru saistÄ«tie biti nenāk pēc kārtas, bet tiek atdalÄ«ti ar bitu pāri. 10 vidÅ«: 110xxxxx 10xxxxxx. Sakarā ar to, kad otrā baita apakŔējie 6 biti pārplÅ«st rakstzÄ«mju kodā (t.i., notiek pāreja 10111111 ā†’ 10000000), mainās arÄ« pirmais baits. Izrādās, ka burts ā€œpā€ tiek apzÄ«mēts ar baitiem 0xD0 0xBF, un nākamais ā€œrā€ jau ir 0xD1 0x80. Prefiksu kokā tas noved pie vecākmezgla sadalÄ«Å”anas divās daļās ā€” vienu prefiksam 0xD0, un vēl viens par 0xD1 (lai gan visu kirilicas alfabētu varēja kodēt tikai ar otro baitu).

Ko es dabūju

Saskaroties ar Å”o problēmu, es nolēmu praktizēt spēles ar bitiem un tajā paŔā laikā nedaudz labāk iepazÄ«ties ar Unicode struktÅ«ru kopumā. Rezultātā tika izveidots UTF-C kodÄ“Å”anas formāts (ā€œCā€ ā€” kompakts), kas vienam koda punktam tērē ne vairāk kā 3 baitus un ļoti bieži ļauj tērēt tikai viens papildu baits visai kodētajai lÄ«nijai. Tas noved pie tā, ka daudzos alfabētā, kas nav ASCII alfabēts, Ŕāds kodējums izrādās 30ā€“60% kompaktāks nekā UTF-8.

Esmu parādÄ«jis kodÄ“Å”anas un dekodÄ“Å”anas algoritmu ievieÅ”anas piemērus formā JavaScript un Go bibliotēkas, varat tos brÄ«vi izmantot savā kodā. Bet tomēr uzsvērÅ”u, ka savā ziņā Å”is formāts paliek ā€œvelosipēdsā€, un es to neiesaku izmantot neapzinoties, kāpēc jums tas ir vajadzÄ«gs. Tas joprojām ir vairāk eksperiments nekā nopietns "UTF-8 uzlabojums". Neskatoties uz to, kods tur ir uzrakstÄ«ts glÄ«ti, kodolÄ«gi, ar lielu skaitu komentāru un testa pārklājumu.

Vēl viens velosipēds: mēs glabājam Unicode virknes par 30ā€“60% kompaktākas nekā UTF-8
Testa rezultāti un salīdzinājums ar UTF-8

Es arÄ« darÄ«ju demonstrācijas lapa, kur var novērtēt algoritma darbÄ«bu, un tad pastāstÄ«Å”u vairāk par tā principiem un izstrādes procesu.

Lieku bitu likvidēŔana

Par pamatu, protams, ņēmu UTF-8. Pirmā un acīmredzamākā lieta, ko tajā var mainīt, ir samazināt pakalpojumu bitu skaitu katrā baitā. Piemēram, pirmais baits UTF-8 vienmēr sākas ar vienu vai otru 0, vai ar 11 - prefikss 10 Tas ir tikai nākamajos baitos. Aizstāsim prefiksu 11 par 1, un nākamajiem baitiem mēs pilnībā noņemsim prefiksus. Kas notiks?

0xxxxxxx ā€” 1 baits
10xxxxxx xxxxxxxx - 2 baiti
110xxxxx xxxxxxxx xxxxxxxx - 3 baiti

Pagaidiet, kur ir četru baitu ieraksts? Bet tas vairs nav vajadzīgs - rakstot trīs baitos, mums tagad ir pieejams 21 bits un ar to pietiek visiem cipariem līdz 0x10FFFF.

Ko mēs Å”eit esam upurējuÅ”i? VissvarÄ«gākais ir rakstzÄ«mju robežu noteikÅ”ana no patvaļīgas atraÅ”anās vietas buferÄ«. Mēs nevaram norādÄ«t uz patvaļīgu baitu un no tā atrast nākamās rakstzÄ«mes sākumu. Tas ir mÅ«su formāta ierobežojums, taču praksē tas ir reti nepiecieÅ”ams. Mēs parasti spējam izskriet buferi jau no paÅ”a sākuma (Ä«paÅ”i, ja runa ir par Ä«sām lÄ«nijām).

Situācija ar valodu pārklājumu ar 2 baitiem arÄ« ir kļuvusi labāka: tagad divu baitu formāts nodroÅ”ina 14 bitu diapazonu, un tie ir kodi lÄ«dz 0x3FFF. ĶīnieÅ”iem nav paveicies (viņu rakstzÄ«mes lielākoties ir no 0x4E00 lÄ«dz 0x9FFF), taču gruzÄ«niem un daudzām citām tautām ir daudz jautrÄ«bas ā€“ arÄ« viņu valodas iekļaujas 2 baitos uz vienu rakstzÄ«mi.

Ievadiet kodētāja stāvokli

Tagad padomāsim par paÅ”u lÄ«niju Ä«paŔībām. VārdnÄ«cā visbiežāk ir vārdi, kas rakstÄ«ti ar viena un tā paÅ”a alfabēta rakstzÄ«mēm, un tas attiecas arÄ« uz daudziem citiem tekstiem. BÅ«tu labi vienreiz norādÄ«t Å”o alfabētu un pēc tam norādÄ«t tikai tajā esoŔā burta numuru. Redzēsim, vai rakstzÄ«mju izkārtojums Unikoda tabulā mums palÄ«dzēs.

Kā minēts iepriekÅ”, Unicode ir sadalÄ«ts lidmaŔīna 65536 kodi katrā. Bet tas nav Ä«paÅ”i noderÄ«gs sadalÄ«jums (kā jau teikts, visbiežāk mēs atrodamies nulles plaknē). Interesantāks ir dalÄ«jums pēc bloki. Å iem diapazoniem vairs nav fiksēta garuma, un tie ir nozÄ«mÄ«gāki ā€” parasti katrs apvieno rakstzÄ«mes no viena un tā paÅ”a alfabēta.

Vēl viens velosipēds: mēs glabājam Unicode virknes par 30ā€“60% kompaktākas nekā UTF-8
Bloks, kurā ir bengāļu alfabēta rakstzÄ«mes. Diemžēl vēsturisku iemeslu dēļ Å”is ir ne pārāk blÄ«va iepakojuma piemērs ā€“ 96 rakstzÄ«mes ir haotiski izkaisÄ«tas pa 128 bloka koda punktiem.

Bloku pirmsākumi un to izmēri vienmēr ir 16 reizes ā€“ tas tiek darÄ«ts vienkārÅ”i ērtÄ«bas labad. Turklāt daudzi bloki sākas un beidzas ar vērtÄ«bām, kas reizinās ar 128 vai pat 256 - piemēram, pamata kirilicas alfabēts aizņem 256 baitus no 0x0400 lÄ«dz 0x04FF. Tas ir diezgan ērti: ja vienreiz saglabājam prefiksu 0x04, tad vienā baitā var ierakstÄ«t jebkuru kirilicas rakstzÄ«mi. Tiesa, tādā veidā mēs zaudēsim iespēju atgriezties pie ASCII (un vispār pie jebkādām citām rakstzÄ«mēm). Tāpēc mēs rÄ«kojamies Ŕādi:

  1. Divi baiti 10yyyyyy yxxxxxxx ne tikai apzÄ«mē simbolu ar skaitli yyyyyy yxxxxxxx, bet arÄ« mainÄ«t paÅ”reizējais alfabēts par yyyyyy y0000000 (t.i., mēs atceramies visus bitus, izņemot vismazāk nozÄ«mÄ«gus 7 bits);
  2. Viens baits 0xxxxxxx tas ir paÅ”reizējā alfabēta raksturs. Tas vienkārÅ”i jāpievieno nobÄ«dei, ko atcerējāmies 1. darbÄ«bā. Lai gan mēs nemainÄ«jām alfabētu, nobÄ«de ir nulle, tāpēc saglabājām saderÄ«bu ar ASCII.

Tāpat kodiem, kuriem nepiecieŔami 3 baiti:

  1. TrÄ«s baiti 110yyyyy yxxxxxxx xxxxxxxx norādiet simbolu ar skaitli yyyyyy yxxxxxxx xxxxxxxx, mainÄ«t paÅ”reizējais alfabēts par yyyyyy y0000000 00000000 (atcerējās visu, izņemot jaunākos 15 bits) un atzÄ«mējiet izvēles rÅ«tiņu, kurā paÅ”laik atrodamies garÅ” režīms (mainot alfabētu atpakaļ uz divbaitu, mēs atiestatÄ«sim Å”o karogu);
  2. Divi baiti 0xxxxxxx xxxxxxxx garajā režīmā tas ir paÅ”reizējā alfabēta raksturs. LÄ«dzÄ«gi mēs to pievienojam ar nobÄ«di no 1. soļa. VienÄ«gā atŔķirÄ«ba ir tā, ka tagad mēs nolasām divus baitus (jo mēs pārslēdzāmies uz Å”o režīmu).

Izklausās labi: tagad, kamēr mums ir jākodē rakstzÄ«mes no tā paÅ”a 7 bitu unikoda diapazona, sākumā mēs iztērējam 1 papildu baitu un kopā vienu baitu katrai rakstzÄ«mei.

Vēl viens velosipēds: mēs glabājam Unicode virknes par 30ā€“60% kompaktākas nekā UTF-8
Darbojas no vienas no iepriekŔējām versijām. Tas jau tagad bieži pārspēj UTF-8, bet vēl ir kur uzlabot.

Kas ir sliktāk? Pirmkārt, mums ir nosacÄ«jums, proti paÅ”reizējā alfabēta nobÄ«de un izvēles rÅ«tiņa garais režīms. Tas mÅ«s vēl vairāk ierobežo: tagad vienas un tās paÅ”as rakstzÄ«mes dažādos kontekstos var tikt kodētas atŔķirÄ«gi. Piemēram, apakÅ”virkņu meklÄ“Å”ana bÅ«s jāveic, ņemot vērā to, nevis tikai salÄ«dzinot baitus. Otrkārt, tiklÄ«dz mēs mainÄ«jām alfabētu, tas kļuva slikts ar ASCII rakstzÄ«mju kodējumu (un tas ir ne tikai latīņu alfabēts, bet arÄ« pamata pieturzÄ«mes, ieskaitot atstarpes) - tām atkal ir jāmaina alfabēts uz 0, tas ir, atkal papildu baits (un pēc tam vēl viens, lai atgrieztos pie galvenā jautājuma).

Viens alfabēts ir labs, divi ir labāki

Mēģināsim nedaudz mainÄ«t savus bitu prefiksus, saspiežot vēl vienu lÄ«dz trim iepriekÅ” aprakstÄ«tajiem:

0xxxxxxx ā€” 1 baits parastajā režīmā, 2 garajā režīmā
11xxxxxx ā€” 1 baits
100xxxxx xxxxxxxx - 2 baiti
101xxxxx xxxxxxxx xxxxxxxx - 3 baiti

Vēl viens velosipēds: mēs glabājam Unicode virknes par 30ā€“60% kompaktākas nekā UTF-8

Tagad divu baitu ierakstā ir pieejams par vienu bitu mazāk ā€” kods norāda lÄ«dz 0x1FFFUn ne 0x3FFF. Tomēr tas joprojām ir ievērojami lielāks nekā divbaitu UTF-8 kodos, lielākā daļa izplatÄ«to valodu joprojām iekļaujas, visievērojamākie zaudējumi ir izkrituÅ”i hiragana Šø katakana, japāņi ir skumji.

Kas ir Å”is jaunais kods? 11xxxxxx? Å Ä« ir maza ā€œatlicinātaā€ ar 64 rakstzÄ«mēm, tā papildina mÅ«su galveno alfabētu, tāpēc es to nosaucu par palÄ«gu (papildu) alfabēts. Pārslēdzot paÅ”reizējo alfabētu, vecā alfabēta daļa kļūst par palÄ«gierÄ«ci. Piemēram, mēs pārgājām no ASCII uz kirilicu ā€” tagad atlicinātā ir 64 rakstzÄ«mes, kas satur Latīņu alfabēts, cipari, atstarpe un komats (visbiežākie iestarpinājumi tekstos, kas nav ASCII). Pārslēdzieties atpakaļ uz ASCII ā€” un galvenā kirilicas alfabēta daļa kļūs par palÄ«galfabētu.

Pateicoties piekļuvei diviem alfabētiem, mēs varam apstrādāt lielu skaitu tekstu ar minimālām izmaksām par alfabētu pārslēgÅ”anu (pieturzÄ«mes visbiežāk novedÄ«s pie atgrieÅ”anās pie ASCII, bet pēc tam mēs iegÅ«sim daudzas rakstzÄ«mes, kas nav ASCII rakstzÄ«mes no papildu alfabēta, bez pārslēgÅ”anās vēlreiz).

Bonuss: apakÅ”alfabēta prefikss 11xxxxxx un izvēloties tā sākotnējo nobÄ«di 0xC0, mēs iegÅ«stam daļēju saderÄ«bu ar CP1252. Citiem vārdiem sakot, daudzi (bet ne visi) Rietumeiropas teksti, kas kodēti CP1252, UTF-C izskatÄ«sies vienādi.

Tomēr Å”eit rodas grÅ«tÄ«bas: kā iegÅ«t palÄ«gu no galvenā alfabēta? JÅ«s varat atstāt to paÅ”u nobÄ«di, bet - diemžēl - Å”eit Unicode struktÅ«ra jau spēlē pret mums. Ä»oti bieži alfabēta galvenā daļa neatrodas bloka sākumā (piemēram, krievu lielajam ā€œAā€ ir kods 0x0410, lai gan kirilicas bloks sākas ar 0x0400). Tādējādi, paņemot krātuvē pirmās 64 rakstzÄ«mes, mēs varam zaudēt piekļuvi alfabēta astes daļai.

Lai novērstu Å”o problēmu, es manuāli izgāju cauri dažiem blokiem, kas atbilst dažādām valodām, un norādÄ«ju tiem papildu alfabēta nobÄ«di galvenajā. Latīņu alfabēts kā izņēmums parasti tika pārkārtots kā base64.

Vēl viens velosipēds: mēs glabājam Unicode virknes par 30ā€“60% kompaktākas nekā UTF-8

Pēdējie pieskārieni

Beidzot padomāsim, kur vēl kaut ko varētu uzlabot.

Ņemiet vērā, ka formāts 101xxxxx xxxxxxxx xxxxxxxx ļauj kodēt ciparus lÄ«dz 0x1FFFFF, un Unicode beidzas agrāk, plkst 0x10FFFF. Citiem vārdiem sakot, pēdējais koda punkts tiks attēlots kā 10110000 11111111 11111111. Tāpēc mēs varam teikt, ka, ja pirmais baits ir formas 1011xxxx (kur xxxx lielāks par 0), tad tas nozÄ«mē kaut ko citu. Piemēram, tur var pievienot vēl 15 rakstzÄ«mes, kas pastāvÄ«gi ir pieejamas kodÄ“Å”anai vienā baitā, bet es nolēmu to darÄ«t savādāk.

ApskatÄ«sim tos Unikoda blokus, kuriem ir nepiecieÅ”ami trÄ«s baiti. BÅ«tÄ«bā, kā jau minēts, tās ir Ä·Ä«nieÅ”u rakstzÄ«mes, taču ar tām ir grÅ«ti kaut ko darÄ«t, to ir 21 tÅ«kstotis. Bet tur lidoja arÄ« hiragana un katakana - un viņu vairs nav tik daudz, nepilni divi simti. Un, tā kā mēs atcerējāmies japāņus, ir arÄ« emocijzÄ«mes (patiesÄ«bā tās ir izkaisÄ«tas daudzās vietās Unicode, bet galvenie bloki ir diapazonā 0x1F300 - 0x1FBFF). Ja domājat par to, ka tagad ir emocijzÄ«mes, kas ir saliktas no vairākiem koda punktiem vienlaikus (piemēram, emocijzÄ«mes ā€ā€ā€Vēl viens velosipēds: mēs glabājam Unicode virknes par 30ā€“60% kompaktākas nekā UTF-8 sastāv no pat 7 kodiem!), tad kļūst pilnÄ«gs kauns tērēt trÄ«s baitus katram (7Ɨ3 = 21 baits vienas ikonas dēļ, murgs).

Tāpēc mēs atlasām dažus atlasītos diapazonus, kas atbilst emocijzīmēm, hiraganai un katakanai, pārnumurējam tos vienā nepārtrauktā sarakstā un kodējam kā divus baitus, nevis trīs:

1011xxxx xxxxxxxx

Lieliski: iepriekÅ” minētā emocijzÄ«meVēl viens velosipēds: mēs glabājam Unicode virknes par 30ā€“60% kompaktākas nekā UTF-8, kas sastāv no 7 koda punktiem, UTF-8 aizņem 25 baitus, un mēs to iekļaujam 14 (tieÅ”i divi baiti katram koda punktam). Starp citu, Habrs atteicās to sagremot (gan vecajā, gan jaunajā redaktorā), tāpēc man vajadzēja ievietot to ar attēlu.

Mēģināsim novērst vēl vienu problēmu. Kā mēs atceramies, pamata alfabēts bÅ«tÄ«bā ir augsts 6 biti, ko paturam prātā un pielÄ«mējam pie katra nākamā atkodētā simbola koda. AttiecÄ«bā uz Ä·Ä«nieÅ”u rakstzÄ«mēm, kas ir blokā 0x4E00 - 0x9FFF, tas ir bits 0 vai 1. Tas nav Ä«paÅ”i ērti: mums bÅ«s nepārtraukti jāpārslēdz alfabēts starp Ŕīm divām vērtÄ«bām (t.i., jāiztērē trÄ«s baiti). Bet ņemiet vērā, ka garajā režīmā no paÅ”a koda mēs varam atņemt rakstzÄ«mju skaitu, kuras mēs kodējam, izmantojot Ä«so režīmu (pēc visiem iepriekÅ” aprakstÄ«tajiem trikiem tas ir 10240) - tad hieroglifu diapazons tiks novirzÄ«ts uz 0x2600 - 0x77FF, un Å”ajā gadÄ«jumā visā Å”ajā diapazonā nozÄ«mÄ«gākie 6 biti (no 21) bÅ«s vienādi ar 0. Tādējādi hieroglifu secÄ«bas izmantos divus baitus uz vienu hieroglifu (kas ir optimāli tik lielam diapazonam), bez izraisot alfabēta slēdžus.

Alternatīvi risinājumi: SCSU, BOCU-1

Unikoda eksperti, tikko izlasÄ«juÅ”i raksta nosaukumu, visticamāk, steigs atgādināt, ka tieÅ”i starp Unicode standartiem ir Unikoda standarta saspieÅ”anas shēma (SCSU), kas apraksta kodÄ“Å”anas metodi, kas ir ļoti lÄ«dzÄ«ga rakstā aprakstÄ«tajai.

AtzÄ«Å”os godÄ«gi: par tā esamÄ«bu uzzināju tikai pēc tam, kad biju dziļi iegrimis sava lēmuma rakstÄ«Å”anā. Ja es par to bÅ«tu zinājis no paÅ”a sākuma, es droÅ”i vien bÅ«tu mēģinājis uzrakstÄ«t ievieÅ”anu, nevis nākt klajā ar savu pieeju.

Interesanti ir tas, ka SCSU izmanto idejas, kas ir ļoti lÄ«dzÄ«gas tām, kuras es izdomāju pats (jēdziena ā€œalfabētsā€ vietā viņi izmanto ā€œlogusā€, un to ir vairāk nekā man). Tajā paŔā laikā Å”im formātam ir arÄ« trÅ«kumi: tas ir nedaudz tuvāk kompresijas algoritmiem nekā kodÄ“Å”anas algoritmiem. Jo Ä«paÅ”i standarts sniedz daudzas attēloÅ”anas metodes, bet nepasaka, kā izvēlēties optimālo - Å”im nolÅ«kam kodētājam ir jāizmanto sava veida heiristika. Tādējādi SCSU kodētājs, kas ražo labu iepakojumu, bÅ«s sarežģītāks un apgrÅ«tinoŔāks nekā mans algoritms.

SalÄ«dzinājumam uz JavaScript pārnesu salÄ«dzinoÅ”i vienkārÅ”u SCSU ievieÅ”anu - koda apjoma ziņā tas izrādÄ«jās salÄ«dzināms ar manu UTF-C, bet dažos gadÄ«jumos rezultāts bija par desmitiem procentu sliktāks (dažkārt var arÄ« pārsniegt, bet ne daudz). Piemēram, teksti ebreju un grieÄ·u valodā tika kodēti ar UTF-C par 60% labāk nekā SCSU (iespējams, to kompakto alfabētu dēļ).

AtseviŔķi piebildÄ«Å”u, ka bez SCSU ir arÄ« vēl viens veids, kā kompakti attēlot Unicode - BOCU-1, taču tā mērÄ·is ir MIME saderÄ«ba (kas man nebija vajadzÄ«ga), un kodÄ“Å”anai ir izmantota nedaudz atŔķirÄ«ga pieeja. Es neesmu novērtējis tā efektivitāti, bet man Ŕķiet, ka tā diez vai bÅ«s augstāka par SCSU.

Iespējamie uzlabojumi

Manis piedāvātais algoritms pēc konstrukcijas nav universāls (iespējams, tieÅ”i Å”eit mani mērÄ·i visvairāk atŔķiras no Unicode konsorcija mērÄ·iem). Es jau minēju, ka tas tika izstrādāts galvenokārt vienam uzdevumam (daudzvalodu vārdnÄ«cas glabāŔanai prefiksu kokā), un dažas tās funkcijas var nebÅ«t piemērotas citiem uzdevumiem. Bet tas, ka tas nav standarts, var bÅ«t pluss - varat to viegli pārveidot atbilstoÅ”i savām vajadzÄ«bām.

Piemēram, acÄ«mredzamā veidā jÅ«s varat atbrÄ«voties no stāvokļa klātbÅ«tnes, veikt bezvalstnieku kodÄ“Å”anu - vienkārÅ”i neatjauniniet mainÄ«gos offs, auxOffs Šø is21Bit kodētājā un dekodētājā. Å ajā gadÄ«jumā nebÅ«s iespējams efektÄ«vi iepakot viena un tā paÅ”a alfabēta rakstzÄ«mju secÄ«bas, taču bÅ«s garantija, ka viena un tā pati rakstzÄ«me vienmēr tiek kodēta ar vieniem un tiem paÅ”iem baitiem neatkarÄ«gi no konteksta.

Turklāt jÅ«s varat pielāgot kodētāju noteiktai valodai, mainot noklusējuma stāvokli - piemēram, koncentrējoties uz tekstiem krievu valodā, sākumā iestatiet kodētāju un dekodētāju. offs = 0x0400 Šø auxOffs = 0. Tas ir Ä«paÅ”i loÄ£iski bezvalsts režīma gadÄ«jumā. Kopumā tas bÅ«s lÄ«dzÄ«gs vecā astoņu bitu kodējuma izmantoÅ”anai, taču netiek noņemta iespēja pēc vajadzÄ«bas ievietot rakstzÄ«mes no visa unikoda.

Vēl viens iepriekÅ” minētais trÅ«kums ir tāds, ka lielajā UTF-C kodētā tekstā nav iespējams ātri atrast rakstzÄ«mju robežu, kas ir vistuvāk patvaļīgam baitam. Ja nogriežat pēdējos, teiksim, 100 baitus no kodētā bufera, jÅ«s riskējat iegÅ«t atkritumus, ar kuriem neko nevarat izdarÄ«t. Kodējums nav paredzēts vairāku gigabaitu žurnālu glabāŔanai, taču kopumā to var labot. baits 0xBF nekad nedrÄ«kst parādÄ«ties kā pirmais baits (bet var bÅ«t otrais vai treÅ”ais). Tāpēc, kodējot, varat ievietot secÄ«bu 0xBF 0xBF 0xBF ik, teiksim, 10 KB - tad, ja jāatrod robeža, pietiks ar izvēlēto gabalu skenēt, lÄ«dz tiks atrasts lÄ«dzÄ«gs marÄ·ieris. Sekojot pēdējam 0xBF tiek garantēts, ka tas ir varoņa sākums. (Dekodējot, Ŕī trÄ«s baitu secÄ«ba, protams, bÅ«s jāignorē.)

Summējot

Ja esat izlasījis tik tālu, apsveicam! Es ceru, ka jūs, tāpat kā es, uzzinājāt kaut ko jaunu (vai atsvaidzinājāt atmiņu) par Unicode struktūru.

Vēl viens velosipēds: mēs glabājam Unicode virknes par 30ā€“60% kompaktākas nekā UTF-8
Demo lapa. Ebreju valodas piemērs parāda priekÅ”rocÄ«bas gan salÄ«dzinājumā ar UTF-8, gan SCSU.

IepriekÅ” aprakstÄ«to pētÄ«jumu nevajadzētu uzskatÄ«t par standartu iejaukÅ”anos. Tomēr kopumā esmu apmierināts ar sava darba rezultātiem, tāpēc esmu apmierināts ar tiem dalÄ«ties: piemēram, samazināta JS bibliotēka sver tikai 1710 baitus (un, protams, tai nav atkarÄ«bu). Kā jau minēju iepriekÅ”, viņas darbu var atrast vietnē demonstrācijas lapa (ir arÄ« tekstu kopums, uz kura to var salÄ«dzināt ar UTF-8 un SCSU).

Visbeidzot, es vēlreiz pievērsÄ«Å”u uzmanÄ«bu gadÄ«jumiem, kad tiek izmantots UTF-C nav tā vērts:

  • Ja jÅ«su rindas ir pietiekami garas (no 100 lÄ«dz 200 rakstzÄ«mēm). Å ajā gadÄ«jumā jums vajadzētu padomāt par tādu saspieÅ”anas algoritmu izmantoÅ”anu kā deflācija.
  • Ja tev vajag ASCII caurspÄ«dÄ«gums, tas ir, jums ir svarÄ«gi, lai kodētās sekvences nesatur ASCII kodus, kas nebija sākotnējā virknē. To var novērst, ja, mijiedarbojoties ar treŔās puses API (piemēram, strādājot ar datu bāzi), kodÄ“Å”anas rezultātu nododat kā abstraktu baitu kopu, nevis kā virknes. Pretējā gadÄ«jumā jÅ«s riskējat iegÅ«t negaidÄ«tas ievainojamÄ«bas.
  • Ja vēlaties ātri atrast rakstzÄ«mju robežas ar patvaļīgu nobÄ«di (piemēram, ja ir bojāta lÄ«nijas daļa). To var izdarÄ«t, bet tikai skenējot rindiņu no sākuma (vai piemērojot iepriekŔējā sadaļā aprakstÄ«to modifikāciju).
  • Ja nepiecieÅ”ams ātri veikt darbÄ«bas ar virkņu saturu (Ŕķirot tās, meklēt tajās apakÅ”virknes, sasaistÄ«t). Tam vispirms ir jāatÅ”ifrē virknes, tāpēc UTF-C Å”ajos gadÄ«jumos bÅ«s lēnāks nekā UTF-8 (bet ātrāk nekā saspieÅ”anas algoritmi). Tā kā viena un tā pati virkne vienmēr tiek kodēta vienādi, precÄ«zs dekodÄ“Å”anas salÄ«dzinājums nav nepiecieÅ”ams, un to var veikt pa baitam.

Update: lietotājs Tjomičs zemāk esoÅ”ajos komentāros ievietoja grafiku, kurā uzsvērti UTF-C pielietojamÄ«bas ierobežojumi. Tas parāda, ka UTF-C ir efektÄ«vāks par vispārējas nozÄ«mes saspieÅ”anas algoritmu (LZW variants), ja vien komplektētā virkne ir Ä«sāka. ~140 rakstzÄ«mes (tomēr es atzÄ«mēju, ka salÄ«dzinājums tika veikts ar vienu tekstu; citās valodās rezultāts var atŔķirties).
Vēl viens velosipēds: mēs glabājam Unicode virknes par 30ā€“60% kompaktākas nekā UTF-8

Avots: www.habr.com

Pievieno komentāru