{"id":95911,"date":"2020-10-05T01:42:09","date_gmt":"2020-10-04T23:42:09","guid":{"rendered":"https:\/\/prohoster.info\/blog\/administrirovanie\/eshhyo-odin-velosiped-hranim-yunikodnye-stroki-na-30-60-kompaktnee-chem-utf-8"},"modified":"2020-10-05T01:42:09","modified_gmt":"2020-10-04T23:42:09","slug":"eshhyo-odin-velosiped-hranim-yunikodnye-stroki-na-30-60-kompaktnee-chem-utf-8","status":"publish","type":"post","link":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/eshhyo-odin-velosiped-hranim-yunikodnye-stroki-na-30-60-kompaktnee-chem-utf-8","title":{"rendered":"Jeszcze jeden rower: przechowujemy ci\u0105gi znak\u00f3w Unicode o 30-60% bardziej kompaktowo ni\u017c UTF-8","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"Jeszcze jeden rower: przechowujemy ci\u0105gi znak\u00f3w Unicode o 30-60% bardziej kompaktowo ni\u017c UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/56c10bad377127b711bdb204ee300772.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nJe\u015bli jeste\u015b programist\u0105 i stoisz przed zadaniem wyboru kodowania, zazwyczaj odpowiednim rozwi\u0105zaniem b\u0119dzie Unicode. Konkretna metoda reprezentacji zale\u017cy od kontekstu, ale najcz\u0119\u015bciej jest tu r\u00f3wnie\u017c uniwersalna odpowied\u017a \u2014 UTF-8. Jest ono dobre, poniewa\u017c pozwala na u\u017cycie wszystkich symboli Unicode, nie marnuj\u0105c <em>zbyt<\/em> wielu bajt\u00f3w w wi\u0119kszo\u015bci przypadk\u00f3w. Prawda jest taka, \u017ce dla j\u0119zyk\u00f3w, kt\u00f3re nie u\u017cywaj\u0105 tylko alfabetu \u0142aci\u0144skiego, \u201eniezbyt wiele\u201d \u2014 to co najmniej <strong>dwa bajty na znak<\/strong>. Czy mo\u017cna lepiej, nie wracaj\u0105c do prehistorycznych kodowa\u0144, kt\u00f3re ograniczaj\u0105 nas do zaledwie 256 dost\u0119pnych symboli?<\/p>\n<p>Poni\u017cej przedstawiam swoj\u0105 pr\u00f3b\u0119 odpowiedzi na to pytanie oraz implementacj\u0119 stosunkowo prostego algorytmu, kt\u00f3ry pozwala na przechowywanie ci\u0105g\u00f3w w wi\u0119kszo\u015bci j\u0119zyk\u00f3w \u015bwiata, nie dodaj\u0105c tej nadmiarowo\u015bci, kt\u00f3ra wyst\u0119puje w UTF-8.<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<p><i>Zastrze\u017cenie.<\/i> Od razu dokonam kilku wa\u017cnych zastrze\u017ce\u0144: <strong>opisana tu metoda nie jest proponowana jako uniwersalne zast\u0119pstwo dla UTF-8<\/strong>, nadaje si\u0119 tylko w w\u0105skim zakresie przypadk\u00f3w (o kt\u00f3rych poni\u017cej), i w \u017cadnym wypadku nie nale\u017cy jej stosowa\u0107 do interakcji z zewn\u0119trznymi API (kt\u00f3re o tym nie maj\u0105 poj\u0119cia). Najcz\u0119\u015bciej do kompaktowego przechowywania du\u017cych ilo\u015bci danych tekstowych lepiej nadaj\u0105 si\u0119 algorytmy kompresji og\u00f3lnego przeznaczenia (na przyk\u0142ad deflate). Ponadto, ju\u017c podczas tworzenia mojego rozwi\u0105zania odkry\u0142em istniej\u0105cy standard w samym Unicode, kt\u00f3ry rozwi\u0105zuje t\u0119 sam\u0105 kwesti\u0119 \u2014 jest on nieco bardziej skomplikowany (i cz\u0119sto gorszy), ale wci\u0105\u017c jest to uznawany standard, a nie co\u015b stworzonego na kolanie. O tym r\u00f3wnie\u017c opowiem.<\/p>\n<h2>O Unicode i UTF-8<\/h2>\n<p>\nNa pocz\u0105tek \u2014 kilka s\u0142\u00f3w o tym, czym w og\u00f3le jest <strong>Unicode<\/strong> i <strong>UTF-8<\/strong>.<\/p>\n<p>Jak wiadomo, wcze\u015bniej popularne by\u0142y 8-bitowe kodowania. Z nimi wszystko by\u0142o proste: 256 znak\u00f3w mo\u017cna by\u0142o ponumerowa\u0107 liczbami od 0 do 255, a liczby od 0 do 255 w oczywisty spos\u00f3b przedstawia si\u0119 w postaci jednego bajta. Je\u015bli wr\u00f3ci\u0107 do samych \u017ar\u00f3de\u0142, to kodowanie ASCII ogranicza si\u0119 w og\u00f3le do 7 bit\u00f3w, dlatego najwy\u017cszy bit w jego przedstawieniu bajtowym wynosi zero, a wi\u0119kszo\u015b\u0107 8-bitowych kodowa\u0144 jest z nim zgodna (r\u00f3\u017cni\u0105 si\u0119 tylko w \u201eg\u00f3rnej\u201d cz\u0119\u015bci, gdzie najwy\u017cszy bit wynosi jeden).<\/p>\n<p>Czym wi\u0119c r\u00f3\u017cni si\u0119 Unicode od tych kodowa\u0144 i dlaczego zwi\u0105zanych z nim jest wiele konkretnych reprezentacji \u2014 UTF-8, UTF-16 (BE i LE), UTF-32? Rozwa\u017cmy to po kolei.<\/p>\n<p>G\u0142\u00f3wny standard Unicode opisuje jedynie zgodno\u015b\u0107 mi\u0119dzy symbolami (a w niekt\u00f3rych przypadkach \u2014 pojedynczymi komponentami symboli) a ich numerami. A mo\u017cliwych numer\u00f3w w tym standardzie jest bardzo wiele \u2014 od <code><b>0x00<\/b><\/code> do <code><b>0x10FFFF<\/b><\/code> (1 114 112 sztuk). Gdyby\u015bmy chcieli umie\u015bci\u0107 liczb\u0119 w takim zakresie w zmiennej, ani 1, ani 2 bajty by nam nie wystarczy\u0142y. A poniewa\u017c nasze procesory nie s\u0105 dostosowane do pracy z liczbami trzybajtowymi, musieliby\u015bmy u\u017cywa\u0107 ca\u0142ych 4 bajt\u00f3w na jeden symbol! To w\u0142a\u015bnie jest UTF-32, ale ze wzgl\u0119du na t\u0119 \"marnotrawno\u015b\u0107\" ten format nie cieszy si\u0119 popularno\u015bci\u0105.<\/p>\n<p>Na szcz\u0119\u015bcie symbole w Unicode'ie nie s\u0105 uporz\u0105dkowane przypadkowo. Ca\u0142a ich masa podzielona jest na 17 \u201e<em>poziom\u00f3w<\/em>\u00bb, z kt\u00f3rych ka\u017cdy zawiera 65536 (<code><b>0x10000<\/b><\/code>) \u00ab<em>punkt\u00f3w kodowych<\/em>\u00bb. Poj\u0119cie \u201epunktu kodowego\u201d tutaj \u2014 to po prostu <em>numer symbolu<\/em>, nadany mu przez Unicode. Ale, jak wspomniano wcze\u015bniej, w Unicode'ie s\u0105 numerowane nie tylko pojedyncze symbole, ale tak\u017ce ich komponenty i znaczniki kontrolne (a czasami w og\u00f3le nic nie odpowiada numerowi \u2014 by\u0107 mo\u017ce do czasu, ale dla nas to nie ma znaczenia), dlatego poprawniej jest zawsze m\u00f3wi\u0107 o liczbie samych numer\u00f3w, a nie symboli. Jednak dalej dla skr\u00f3tu cz\u0119sto b\u0119d\u0119 u\u017cywa\u0142 s\u0142owa \u201esymbol\u201d, maj\u0105c na my\u015bli termin \u201epunkt kodowy\u201d.<\/p>\n<p><img decoding=\"async\" alt=\"Jeszcze jeden rower: przechowujemy ci\u0105gi znak\u00f3w Unicode o 30-60% bardziej kompaktowo ni\u017c UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/7ad84b571a8025582fdbc3db956cb3b0.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Poziomy Unicode'a. Jak wida\u0107, wi\u0119kszo\u015b\u0107 (poziomy od 4 do 13) wci\u0105\u017c nie jest wykorzystywana.<\/i><\/p>\n<p>Co jest najbardziej niezwyk\u0142e \u2014 ca\u0142a ta \"mi\u0119kka cz\u0119\u015b\u0107\" le\u017cy w zerowej p\u0142aszczy\u017anie, nazywa si\u0119 \"<em>Basic Multilingual Plane<\/em>\". Je\u015bli linia zawiera tekst w jednym z nowoczesnych j\u0119zyk\u00f3w (w tym chi\u0144skim), nie wyjdziesz poza t\u0119 p\u0142aszczyzn\u0119. Ale r\u00f3wnie\u017c nie mo\u017cna odci\u0105\u0107 reszty Unicode \u2014 na przyk\u0142ad emotikony g\u0142\u00f3wnie znajduj\u0105 si\u0119 na ko\u0144cu kolejnej w kolejno\u015bci p\u0142aszczyzny, \"<em>Supplementary Multilingual Plane<\/em>\" (rozci\u0105ga si\u0119 od <code><b>0x10000<\/b><\/code> do <code><b>0x1FFFF<\/b><\/code>). Dlatego UTF-16 post\u0119puje w ten spos\u00f3b: wszystkie symbole, kt\u00f3re znajduj\u0105 si\u0119 w <em>Basic Multilingual Plane<\/em>, s\u0105 kodowane \u201etak jak s\u0105\u201d, odpowiadaj\u0105c im dwu-bajtowym numerem. Jednak cz\u0119\u015b\u0107 liczb w tym zakresie w og\u00f3le nie oznacza konkretnych symboli, a wskazuje, \u017ce po tej parze bajt\u00f3w nale\u017cy rozwa\u017cy\u0107 jeszcze jedn\u0105 \u2014 \u0142\u0105cz\u0105c warto\u015bci tych czterech bajt\u00f3w razem, uzyskamy liczb\u0119 obejmuj\u0105c\u0105 ca\u0142y dozwolony zakres Unicode'a. To przedstawienie nazywa si\u0119 \u201eparami zast\u0119pczymi\u201d \u2014 by\u0107 mo\u017ce s\u0142yszeli\u015bcie o nich.<\/p>\n<p>W ten spos\u00f3b UTF-16 wymaga dw\u00f3ch lub (w bardzo rzadkich przypadkach) czterech bajt\u00f3w na jeden \u201epunkt kodowy\u201d. To lepiej ni\u017c ci\u0105g\u0142e korzystanie z czterech bajt\u00f3w, ale \u0142acina (i inne znaki ASCII) w takim kodowaniu zajmuj\u0105 po\u0142ow\u0119 miejsca wype\u0142nionego zerami. UTF-8 ma to naprawi\u0107: w nim ASCII zajmuje, jak dawniej, tylko jeden bajt; kody od <code><b>0x80<\/b><\/code> do <code><b>0x7FF<\/b><\/code> \u2014 dw\u00f3ch bajt\u00f3w; od <code><b>0x800<\/b><\/code> do <code><b>0xFFFF<\/b><\/code> \u2014 trzech, a od <code><b>0x10000<\/b><\/code> do <code><b>0x10FFFF<\/b><\/code> \u2014 czterech. Z jednej strony \u0142acina zyska\u0142a: przywr\u00f3cono zgodno\u015b\u0107 z ASCII, a tak\u017ce rozk\u0142ad jest bardziej r\u00f3wnomiernie \u201erozmazany\u201d od 1 do 4 bajt\u00f3w. Jednak alfabety inne ni\u017c \u0142aci\u0144ski niestety nie zyskuj\u0105 na tym w por\u00f3wnaniu do UTF-16, a wiele z nich teraz wymaga trzech bajt\u00f3w zamiast dw\u00f3ch \u2014 zakres pokrywany dwubajtowym zapisem skurczy\u0142 si\u0119 32 razy, z <code><b>0xFFFF<\/b><\/code> do <code><b>0x7FF<\/b><\/code>, i nie obejmuje ju\u017c ani chi\u0144skiego, ani, na przyk\u0142ad, gruzi\u0144skiego. Cyrylica i jeszcze pi\u0119\u0107 alfabet\u00f3w \u2014 hura \u2014 maj\u0105 szcz\u0119\u015bcie, 2 bajty na znak.<\/p>\n<p>Dlaczego tak si\u0119 dzieje? Przyjrzyjmy si\u0119, jak UTF-8 reprezentuje kody symboli:<br \/>\n<img decoding=\"async\" alt=\"Jeszcze jeden rower: przechowujemy ci\u0105gi znak\u00f3w Unicode o 30-60% bardziej kompaktowo ni\u017c UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/4ef4fe9e949cd75295c45c053dbca6d3.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nBezpo\u015brednio do reprezentacji liczb u\u017cyto bit\u00f3w oznaczonych symbolem <code><b>x<\/b><\/code>. Wida\u0107, \u017ce w dwubajtowym zapisie takich bit\u00f3w jest tylko 11 (z 16). Wiod\u0105ce bity pe\u0142ni\u0105 tutaj tylko funkcj\u0119 pomocnicz\u0105. W przypadku czterobajtowego zapisu na numer kodu punktowego przewidziano 21 bit\u00f3w z 32 \u2014 wydawa\u0142oby si\u0119, \u017ce wystarczy\u0142oby i trzy bajty (kt\u00f3re daj\u0105 \u0142\u0105cznie 24 bity), ale znaki pomocnicze zajmuj\u0105 zbyt wiele miejsca.<\/p>\n<p>Czy to jest \u017ale? W rzeczywisto\u015bci nie bardzo. Z jednej strony \u2014 je\u015bli bardzo dbamy o zajmowan\u0105 przestrze\u0144, mamy algorytmy kompresji, kt\u00f3re \u0142atwo eliminuj\u0105 wszelk\u0105 nadmiern\u0105 entropi\u0119 i nadmiarowo\u015b\u0107. Z drugiej strony \u2014 celem Unicode'a by\u0142o zapewnienie maksymalnie uniwersalnego kodowania. Na przyk\u0142ad, napisana w UTF-8 linia mo\u017ce by\u0107 zaufa\u0107 kodowi, kt\u00f3ry wcze\u015bniej dzia\u0142a\u0142 tylko z ASCII, i nie obawia\u0107 si\u0119, \u017ce zobaczy tam symbol z zakresu ASCII, kt\u00f3rego tam w rzeczywisto\u015bci nie ma (bo w UTF-8 wszystkie bajty, kt\u00f3re zaczynaj\u0105 si\u0119 od zera, to w\u0142a\u015bnie ASCII). A je\u015bli nagle chcemy odci\u0105\u0107 ma\u0142y ogon od du\u017cej linii, nie dekoduj\u0105c jej od samego pocz\u0105tku (lub przywracaj\u0105c cz\u0119\u015b\u0107 informacji po uszkodzonym fragmencie) \u2014 nie jest trudno znale\u017a\u0107 przesuni\u0119cie, w kt\u00f3rym zaczyna si\u0119 jaki\u015b symbol (wystarczy pomin\u0105\u0107 bajty, kt\u00f3re maj\u0105 prefiks bitowy <code><b>10<\/b><\/code>).<\/p>\n<h2>Dlaczego zatem wymy\u015bla\u0107 co\u015b nowego?<\/h2>\n<p>\nJednocze\u015bnie zdarzaj\u0105 si\u0119 sytuacje, w kt\u00f3rych algorytmy kompresji, takie jak deflate, s\u0105 s\u0142abo zastosowane, a ch\u0119\u0107 uzyskania kompaktowego przechowywania ci\u0105g\u00f3w jest du\u017ca. Osobi\u015bcie spotka\u0142em si\u0119 z takim zadaniem, my\u015bl\u0105c o budowie <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Radix_tree\">skompresowanego drzewa prefiksowego<\/a><\/noindex> dla du\u017cego s\u0142ownika, kt\u00f3ry obejmuje s\u0142owa w dowolnych j\u0119zykach. Z jednej strony ka\u017cde s\u0142owo jest bardzo kr\u00f3tkie, wi\u0119c kompresja by\u0142aby nieefektywna. Z drugiej - implementacja drzewa, kt\u00f3r\u0105 rozwa\u017ca\u0142em, by\u0142a zaprojektowana tak, aby ka\u017cdy bajt przechowywanego ci\u0105gu generowa\u0142 oddzielny wierzcho\u0142ek drzewa, co sprawia\u0142o, \u017ce minimalizowanie ich liczby by\u0142o bardzo korzystne. W mojej bibliotece <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/deNULL\/Az.js\">Az.js<\/a><\/noindex> (podobnie jak w <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/kmike\/pymorphy2\">pymorphy2<\/a><\/noindex>, na kt\u00f3rej si\u0119 opiera) podobny problem rozwi\u0105zuje si\u0119 prosto \u2014 ci\u0105gi, spakowane w <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Deterministic_acyclic_finite_state_automaton\">s\u0142ownik DAWG<\/a><\/noindex>, s\u0105 tam przechowywane w <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/deNULL\/Az.js\/blob\/master\/src\/az.dawg.js\">starej dobrej CP1251<\/a><\/noindex>. Ale, jak \u0142atwo zrozumie\u0107, dzia\u0142a to dobrze tylko dla ograniczonego alfabetu \u2014 ci\u0105g w j\u0119zyku chi\u0144skim nie zmie\u015bci\u0142by si\u0119 ju\u017c w takim s\u0142owniku.<\/p>\n<p>Osobno zauwa\u017c\u0119 jeszcze jeden nieprzyjemny niuans, kt\u00f3ry pojawia si\u0119 podczas u\u017cywania UTF-8 w takiej strukturze danych. Na powy\u017cszym obrazku wida\u0107, \u017ce podczas zapisu znaku w postaci dw\u00f3ch bajt\u00f3w bity odnosz\u0105ce si\u0119 do jego numeru nie s\u0105 s\u0105siaduj\u0105ce, a s\u0105 rozdzielone par\u0105 bit\u00f3w <code><b>10<\/b><\/code> po\u015brodku: <code><b>110xxxxx 10xxxxxx<\/b><\/code>. Z tego powodu, gdy w kodzie znaku przepe\u0142niaj\u0105 si\u0119 najmniejsze 6 bit\u00f3w drugiego bajtu (tzn. nast\u0119puje przej\u015bcie <code><b>10111111<\/b><\/code> \u2192 <code><b>10000000<\/b><\/code>), to zmienia si\u0119 r\u00f3wnie\u017c pierwszy bajt. Okazuje si\u0119, \u017ce litera \u201e\u043f\u201d jest oznaczana przez bajty <code><b>0xD0 0xBF<\/b><\/code>, a nast\u0119pna po niej \u201e\u0440\u201d \u2014 ju\u017c przez <code><b>0xD1 0x80<\/b><\/code>. W drzewie prefiksowym prowadzi to do podzia\u0142u wierzcho\u0142ka rodzica na dwa \u2014 jeden dla prefiksu <code><b>0xD0<\/b><\/code>, a drugi dla <code><b>0xD1<\/b><\/code> (chocia\u017c ca\u0142a cyrylica mog\u0142aby by\u0107 kodowana tylko drugim bajtem).<\/p>\n<h2>Co mi wysz\u0142o<\/h2>\n<p>\nStykaj\u0105c si\u0119 z tym zadaniem postanowi\u0142em po\u0107wiczy\u0107 zabawy z bitami, a przy okazji nieco lepiej pozna\u0107 struktur\u0119 Unicode w og\u00f3le. Efektem by\u0142 format kodowania UTF-C (\u201eC\u201d od <em>compact<\/em>), kt\u00f3ry nie zu\u017cywa wi\u0119cej ni\u017c 3 bajt\u00f3w na jeden punkt kodowy, a bardzo cz\u0119sto pozwala na zu\u017cycie tylko <strong>jednego zb\u0119dnego bajta na ca\u0142y kodowany ci\u0105g<\/strong>. Prowadzi to do tego, \u017ce w wielu nie-ASCII alfabetach takie kodowanie okazuje si\u0119 <strong>o 30-60% bardziej kompaktowe ni\u017c UTF-8<\/strong>.<\/p>\n<p>Zrealizowa\u0142em przyk\u0142ady implementacji algorytm\u00f3w kodowania i dekodowania w formie <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/deNULL\/utf-c\">bibliotek w JavaScript i Go<\/a><\/noindex>, mo\u017cesz swobodnie u\u017cywa\u0107 ich w swoim kodzie. Niemniej jednak podkre\u015blam, \u017ce w pewnym sensie ten format pozostaje \u201erowerem\u201d i nie rekomenduj\u0119 jego u\u017cycia <strong>bez \u015bwiadomo\u015bci, po co ci on jest potrzebny<\/strong>. To wci\u0105\u017c bardziej eksperyment ni\u017c powa\u017cne \u201eulepszenie UTF-8\u201d. Mimo to, kod jest napisany starannie, zwi\u0119\u017ale, z du\u017c\u0105 liczb\u0105 komentarzy i pokryciem testami.<\/p>\n<p><img decoding=\"async\" alt=\"Jeszcze jeden rower: przechowujemy ci\u0105gi znak\u00f3w Unicode o 30-60% bardziej kompaktowo ni\u017c UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/e31978174449cd7de5d8371aaeb9ab2e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Wynik dzia\u0142ania test\u00f3w i por\u00f3wnanie z UTF-8<\/i><\/p>\n<p>Dodatkowo stworzy\u0142em <noindex><a rel=\"nofollow\" href=\"https:\/\/denull.github.io\/utf-c\/\">stron\u0119 demo<\/a><\/noindex>, gdzie mo\u017cna oceni\u0107 dzia\u0142anie algorytmu, a dalej opowiem szczeg\u00f3\u0142owo o jego zasadach i procesie rozwoju.<\/p>\n<h2>Eliminujemy zb\u0119dne bity<\/h2>\n<p>\nZa podstaw\u0119 wzi\u0105\u0142em oczywi\u015bcie UTF-8. Pierwsze i najbardziej oczywiste, co mo\u017cna zmieni\u0107 \u2014 to zmniejszy\u0107 liczb\u0119 bit\u00f3w steruj\u0105cych w ka\u017cdym bajcie. Na przyk\u0142ad, pierwszy bajt w UTF-8 zawsze zaczyna si\u0119 albo od <code><b>0<\/b><\/code>, albo od <code><b>11<\/b><\/code> \u2014 a prefix <code><b>10<\/b><\/code> ma tylko nast\u0119pne bajty. Zast\u0105pimy prefix <code><b>11<\/b><\/code> na <code><b>1<\/b><\/code>, a w kolejnych bajtach ca\u0142kowicie usuniemy prefiksy. Co z tego wyjdzie?<\/p>\n<p><code><b>0xxxxxxx<\/b><\/code> \u2014 1 bajt <br \/>\n<code><b>10xxxxxx xxxxxxxx<\/b><\/code> \u2014 2 bajty <br \/>\n<code><b>110xxxxx xxxxxxxx xxxxxxxx<\/b><\/code> \u2014 3 bajty<\/p>\n<p>Stop, a gdzie zapis czterobajtowy? A sta\u0142 si\u0119 niepotrzebny \u2014 przy zapisie trzema bajtami mamy teraz dost\u0119pny 21 bit i wystarcza to z zapasem na wszystkie liczby do <code><b>0x10FFFF<\/b><\/code>.<\/p>\n<p>Czego\u015b si\u0119 tu zrzekli\u015bmy? Najwa\u017cniejsze \u2014 wykrywanie granic symboli z dowolnego miejsca w buforze. Nie mo\u017cemy wskaza\u0107 na dowolny bajt i od niego znale\u017a\u0107 pocz\u0105tek nast\u0119pnego symbolu. To ograniczenie naszego formatu, ale w praktyce potrzeba takiej funkcjonalno\u015bci rzadko si\u0119 pojawia. Zazwyczaj potrafimy przebiec bufor od samego pocz\u0105tku (zw\u0142aszcza gdy mowa o kr\u00f3tkich ci\u0105gach).<\/p>\n<p>Sytuacja z pokryciem j\u0119zyk\u00f3w 2 bajtami r\u00f3wnie\u017c poprawi\u0142a si\u0119: teraz dwubajtowy format daje zakres 14 bit\u00f3w, co oznacza kody do <code><b>0x3FFF<\/b><\/code>. Chi\u0144czykom si\u0119 nie wiedzie (ich znaki g\u0142\u00f3wnie mieszcz\u0105 si\u0119 w zakresie od <code><b>0x4E00<\/b><\/code> do <code><b>0x9FFF<\/b><\/code>), ale Gruzinom i wielu innym narodowo\u015bciom jest teraz weselej \u2014 ich j\u0119zyki r\u00f3wnie\u017c mieszcz\u0105 si\u0119 w 2 bajtach na symbol.<\/p>\n<h2>Wprowadzamy stan enkodera<\/h2>\n<p>\nZastan\u00f3wmy si\u0119 teraz nad w\u0142a\u015bciwo\u015bciami samych ci\u0105g\u00f3w. W s\u0142owniku najcz\u0119\u015bciej znajduj\u0105 si\u0119 s\u0142owa napisane znakami jednego alfabetu, a dla wielu innych tekst\u00f3w r\u00f3wnie\u017c jest to prawda. Dobrze by by\u0142o raz wskaza\u0107 ten alfabet, a dalej podawa\u0107 tylko numer litery w nim. Zobaczmy, czy pomo\u017ce nam rozmieszczenie znak\u00f3w w tabeli Unicode.<\/p>\n<p>Jak wspomniano wcze\u015bniej, Unicode jest podzielony na <em>p\u0142aszczyzny<\/em> po 65536 kod\u00f3w ka\u017cdy. Ale to nie jest zbyt przydatny podzia\u0142 (jak ju\u017c wspomniano, najcz\u0119\u015bciej jeste\u015bmy w zero-osi). Bardziej interesuj\u0105cym podzia\u0142em jest <em>bloki.<\/em> Te zakresy ju\u017c nie maj\u0105 sta\u0142ej d\u0142ugo\u015bci i maj\u0105 wi\u0119cej sensu - zazwyczaj ka\u017cdy \u0142\u0105czy znaki jednego alfabetu.<\/p>\n<p><img decoding=\"async\" alt=\"Jeszcze jeden rower: przechowujemy ci\u0105gi znak\u00f3w Unicode o 30-60% bardziej kompaktowo ni\u017c UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/61ea5e859d7e6d9c75e977a3579ff28f.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Blok, zawieraj\u0105cy znaki alfabetu bengalskiego. Niestety, z powod\u00f3w historycznych, jest to przyk\u0142ad niezbyt g\u0119stego pakowania - 96 znak\u00f3w jest chaotycznie rozrzuconych po 128 punktach kodowych bloku.<\/i><\/p>\n<p>Pocz\u0105tki blok\u00f3w i ich rozmiary s\u0105 zawsze wielokrotno\u015bci\u0105 16 - zrobiono to po prostu dla wygody. Ponadto wiele blok\u00f3w zaczyna si\u0119 i ko\u0144czy na warto\u015bciach wielokrotnych 128 lub nawet 256 - na przyk\u0142ad, podstawowa cyrylica zajmuje 256 bajt\u00f3w od <code><b>0x0400<\/b><\/code> do <code><b>0x04FF<\/b><\/code>. To do\u015b\u0107 wygodne: je\u015bli raz zapiszemy prefiks <code><b>0x04<\/b><\/code>, to ka\u017cdy znak cyrylicy mo\u017cna zapisa\u0107 jednym bajtem. Niestety, tracimy jednak mo\u017cliwo\u015b\u0107 powrotu do ASCII (i do jakichkolwiek innych znak\u00f3w w og\u00f3le). Dlatego robimy tak:<\/p>\n<ol>\n<li>Dwa bajty <code><b>10yyyyyy yxxxxxxx<\/b><\/code> nie tylko oznaczaj\u0105 znak o numerze <code><b>yyyyyy yxxxxxxx<\/b><\/code>, ale tak\u017ce zmieniaj\u0105 <em>aktualny alfabet<\/em> na <code><b>yyyyyy y0000000<\/b><\/code> (tzn. zapami\u0119tujemy wszystkie bity, z wyj\u0105tkiem najni\u017cszych <strong>7 bit\u00f3w<\/strong>);<\/li>\n<li>Jeden bajt <code><b>0xxxxxxx<\/b><\/code> to znak aktualnego alfabetu. Nale\u017cy go po prostu doda\u0107 do przesuni\u0119cia, kt\u00f3re zapami\u0119tali\u015bmy w kroku 1. Dop\u00f3ki nie zmieniamy alfabetu, przesuni\u0119cie wynosi zero, wi\u0119c zachowali\u015bmy zgodno\u015b\u0107 z ASCII.<\/li>\n<\/ol>\n<p>\nPodobnie dla kod\u00f3w, kt\u00f3re wymagaj\u0105 3 bajt\u00f3w:<\/p>\n<ol>\n<li>Trzy bajty <code><b>110yyyyy yxxxxxxx xxxxxxxx<\/b><\/code> oznaczaj\u0105 znak o numerze <code><b>yyyyyy yxxxxxxx xxxxxxxx<\/b><\/code>, zmieniaj\u0105 <em>aktualny alfabet<\/em> na <code><b>yyyyyy y0000000 00000000<\/b><\/code> (zapami\u0119tali\u015bmy wszystko, z wyj\u0105tkiem najni\u017cszych <strong>15 bit\u00f3w<\/strong>), i ustawiaj\u0105 znacznik, \u017ce teraz jeste\u015bmy w <em>d\u0142ugim<\/em> trybie (przy zmianie alfabetu z powrotem na dwubajtowy, ten znacznik zostanie zresetowany);<\/li>\n<li>Dwa bajty <code><b>0xxxxxxx xxxxxxxx<\/b><\/code> w d\u0142ugim trybie to znak aktualnego alfabetu. Podobnie, dodajemy go do przesuni\u0119cia z kroku 1. Ca\u0142a r\u00f3\u017cnica polega na tym, \u017ce teraz czytamy dwa bajty (poniewa\u017c przeszli\u015bmy do takiego trybu).<\/li>\n<\/ol>\n<p>\nBrzmi nie\u017ale: teraz, dop\u00f3ki musimy kodowa\u0107 znaki z tego samego 7-bitowego zakresu Unicode, wydajemy 1 dodatkowy bajt na pocz\u0105tku i po jednym bajcie na ka\u017cdy znak.<\/p>\n<p><img decoding=\"async\" alt=\"Jeszcze jeden rower: przechowujemy ci\u0105gi znak\u00f3w Unicode o 30-60% bardziej kompaktowo ni\u017c UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/084d636a25dccdaa9f5a6eb5233c8e99.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Dzia\u0142a jedna z wczesnych wersji. Ju\u017c cz\u0119sto omija UTF-8, ale wci\u0105\u017c jest co poprawia\u0107.<\/i><\/p>\n<p>Co sta\u0142o si\u0119 gorsze? Po pierwsze, mamy stan, a mianowicie <em>przesuni\u0119cie aktualnego alfabetu<\/em> i znacznik <em>d\u0142ugiego trybu<\/em>Dodatkowo ogranicza nas to: teraz te same znaki mog\u0105 by\u0107 kodowane w r\u00f3\u017cny spos\u00f3b w r\u00f3\u017cnych kontekstach. Szukanie podci\u0105g\u00f3w, na przyk\u0142ad, b\u0119dzie musia\u0142o by\u0107 ju\u017c uwzgl\u0119dnione w tym kontek\u015bcie, a nie tylko por\u00f3wnuj\u0105c bajty. Po drugie, zaraz po zmianie alfabetu, kodowanie znak\u00f3w ASCII zacz\u0119\u0142o sprawia\u0107 problemy (a to nie tylko \u0142aci\u0144ska, ale i podstawowa interpunkcja, w tym spacje) \u2014 wymagaj\u0105 one ponownej zmiany alfabetu na 0, co oznacza znowu dodatkowy bajt (a nast\u0119pnie jeszcze jeden, by wr\u00f3ci\u0107 do naszego g\u0142\u00f3wnego).<\/p>\n<h2>Jeden alfabet jest dobry, dwa \u2014 lepsze<\/h2>\n<p>\nSpr\u00f3bujmy nieco zmieni\u0107 nasze prefiksy bitowe, dodaj\u0105c do tych trzech opisanych jeszcze jeden:<\/p>\n<p><code><b>0xxxxxxx<\/b><\/code> \u2014 1 bajt w normalnym trybie, 2 w d\u0142ugim <br \/>\n<code><b>11xxxxxx<\/b><\/code> \u2014 1 bajt <br \/>\n<code><b>100xxxxx xxxxxxxx<\/b><\/code> \u2014 2 bajty <br \/>\n<code><b>101xxxxx xxxxxxxx xxxxxxxx<\/b><\/code> \u2014 3 bajty<\/p>\n<p><img decoding=\"async\" alt=\"Jeszcze jeden rower: przechowujemy ci\u0105gi znak\u00f3w Unicode o 30-60% bardziej kompaktowo ni\u017c UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/fa1eca1adb80b15a4cf4f60f57a09c6c.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nTeraz w zapisie dwubajtowym jeden dost\u0119pny bit sta\u0142 si\u0119 mniej \u2014 mog\u0105 si\u0119 zmie\u015bci\u0107 punkty kodowe a\u017c do <code><b>0x1FFF<\/b><\/code>, a nie <code><b>0x3FFF<\/b><\/code>. Tyle \u017ce wci\u0105\u017c jest to wyra\u017anie wi\u0119cej ni\u017c w dwubajtowych kodach UTF-8, wi\u0119kszo\u015b\u0107 powszechnie u\u017cywanych j\u0119zyk\u00f3w nadal si\u0119 mie\u015bci, najwi\u0119ksz\u0105 strat\u0105 jest <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%A5%D0%B8%D1%80%D0%B0%D0%B3%D0%B0%D0%BD%D0%B0\">hiragana<\/a><\/noindex> i <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%9A%D0%B0%D1%82%D0%B0%D0%BA%D0%B0%D0%BD%D0%B0\">katakana<\/a><\/noindex>, Japo\u0144czycy s\u0105 smutni.<\/p>\n<p>C\u00f3\u017c to za nowy kod <code><b>11xxxxxx<\/b><\/code>? \u042d\u0442\u043e \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u00ab\u0437\u0430\u0433\u0430\u0448\u043d\u0438\u043a\u00bb \u0440\u0430\u0437\u043c\u0435\u0440\u043e\u043c \u0432 64 \u0441\u0438\u043c\u0432\u043e\u043b\u0430, \u043e\u043d \u0434\u043e\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u043d\u0430\u0448 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0430\u043b\u0444\u0430\u0432\u0438\u0442, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u043d\u0430\u0437\u0432\u0430\u043b \u0435\u0433\u043e \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u043c (<em>auxiliary<\/em>) alfabetem. Kiedy prze\u0142\u0105czamy aktualny alfabet, kawa\u0142ek starego alfabetu staje si\u0119 pomocniczy. Na przyk\u0142ad, prze\u0142\u0105czaj\u0105c si\u0119 z ASCII na cyrylic\u0119 \u2014 w \u201ezapleczu\u201d jest teraz 64 znaki, zawieraj\u0105ce <strong>\u0142acin\u0119, cyfry, spacj\u0119 i przecinek<\/strong> (najcz\u0119stsze wstawki w tekstach non-ASCII). Powracaj\u0105c z powrotem do ASCII \u2014 pomocniczym alfabetem stanie si\u0119 g\u0142\u00f3wna cz\u0119\u015b\u0107 cyrylicy.<\/p>\n<p>Dzi\u0119ki dost\u0119powi do dw\u00f3ch alfabet\u00f3w mo\u017cemy poradzi\u0107 sobie z du\u017c\u0105 ilo\u015bci\u0105 tekst\u00f3w, maj\u0105c minimalne koszty na prze\u0142\u0105czanie alfabet\u00f3w (interpunkcja najcz\u0119\u015bciej b\u0119dzie prowadzi\u0107 do powrotu do ASCII, ale potem wiele znak\u00f3w non-ASCII b\u0119dziemy pobiera\u0107 ju\u017c z dodatkowego alfabetu, bez ponownego prze\u0142\u0105czania).<\/p>\n<p>Bonus: oznaczaj\u0105c dodatkowy alfabet prefiksem <code><b>11xxxxxx<\/b><\/code> i wybieraj\u0105c jego wst\u0119pne przesuni\u0119cie r\u00f3wne <code><b>0xC0<\/b><\/code>, uzyskujemy cz\u0119\u015bciow\u0105 zgodno\u015b\u0107 z CP1252. Innymi s\u0142owy, wiele (ale nie wszystkie) tekst\u00f3w zachodnioeuropejskich zakodowanych w CP1252 b\u0119dzie wygl\u0105da\u0107 tak samo w UTF-C.<\/p>\n<p>Tutaj jednak pojawia si\u0119 trudno\u015b\u0107: jak prze\u0142\u0105czy\u0107 si\u0119 z g\u0142\u00f3wnego alfabetu na pomocniczy? Mo\u017cna zachowa\u0107 to samo przesuni\u0119cie, ale \u2014 niestety \u2014 tutaj struktura Unicode gra przeciwko nam. Bardzo cz\u0119sto g\u0142\u00f3wna cz\u0119\u015b\u0107 alfabetu nie znajduje si\u0119 na pocz\u0105tku bloku (na przyk\u0142ad rosyjska wielka litera \u201eA\u201d ma kod <code>0x04<b>10<\/b><\/code>, chocia\u017c blok cyryliczny zaczyna si\u0119 od <code>0x04<b>00<\/b><\/code>). W ten spos\u00f3b, bior\u0105c w \u201ezaplecze\u201d pierwsze 64 znaki, mo\u017cliwe, \u017ce utracimy dost\u0119p do ko\u0144cowej cz\u0119\u015bci alfabetu.<\/p>\n<p>Aby rozwi\u0105za\u0107 ten problem, r\u0119cznie przeszed\u0142em przez niekt\u00f3re bloki odpowiadaj\u0105ce r\u00f3\u017cnym j\u0119zykom i wskaza\u0142em dla nich przesuni\u0119cie pomocniczego alfabetu w obr\u0119bie g\u0142\u00f3wnego. \u0141aci\u0144skie litery w zasadzie przemie\u015bci\u0142em przynajmniej tak jak base64.<\/p>\n<p><img decoding=\"async\" alt=\"Jeszcze jeden rower: przechowujemy ci\u0105gi znak\u00f3w Unicode o 30-60% bardziej kompaktowo ni\u017c UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/d191a3bb17403e99d506dbd008b63159.jpeg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<h2>Ostatnie szlify<\/h2>\n<p>\nNa koniec pomy\u015blmy, gdzie jeszcze mo\u017cemy co\u015b dopracowa\u0107.<\/p>\n<p>Zauwa\u017cmy, \u017ce format <code><b>101xxxxx xxxxxxxx xxxxxxxx<\/b><\/code> pozwala zakodowa\u0107 liczby a\u017c do <code><b>0x1FFFFF<\/b><\/code>, a Unicode ko\u0144czy si\u0119 wcze\u015bniej, na <code><b>0x10FFFF<\/b><\/code>. Innymi s\u0142owy, ostatni punkt kodowy b\u0119dzie reprezentowany jako <code><b>10110000 11111111 11111111<\/b><\/code>. Mo\u017cemy zatem powiedzie\u0107, \u017ce je\u015bli pierwszy bajt ma posta\u0107 <code><b>1011xxxx<\/b><\/code> (gdzie <code><b>xxxx<\/b><\/code> wi\u0119cej ni\u017c 0), oznacza to co\u015b innego. Na przyk\u0142ad, mo\u017cna doda\u0107 tam jeszcze 15 znak\u00f3w, kt\u00f3re s\u0105 stale dost\u0119pne do kodowania jednym bajtem, ale zdecydowa\u0142em si\u0119 post\u0105pi\u0107 inaczej.<\/p>\n<p>Przyjrzyjmy si\u0119 blokom Unicode, kt\u00f3re obecnie wymagaj\u0105 trzech bajt\u00f3w. G\u0142\u00f3wnie, jak ju\u017c wspomniano, s\u0105 to chi\u0144skie znaki \u2014 ale trudno co\u015b z nimi zrobi\u0107, jest ich 21 tysi\u0119cy. Ale te\u017c tam wpad\u0142y hiragana i katakana \u2014 a tych ju\u017c nie ma tak du\u017co, mniej ni\u017c dwie\u015bcie. A skoro wspomnieli\u015bmy o Japo\u0144czykach \u2014 tam te\u017c s\u0105 emotikony (naprawd\u0119 s\u0105 rozsiane w Unicode, ale podstawowe bloki w zakresie <code><b>0x1F300<\/b><\/code> \u2013 <code><b>0x1FBFF<\/b><\/code>). Je\u015bli pomy\u015blimy o tym, \u017ce obecnie istniej\u0105 emotikony, kt\u00f3re sk\u0142adaj\u0105 si\u0119 z kilku punkt\u00f3w kodowych (na przyk\u0142ad emotikona \u200d\u200d\u200d<noindex><a rel=\"nofollow\" href=\"https:\/\/emojipedia.org\/family-woman-woman-girl-boy\/\"><img decoding=\"async\" alt=\"Jeszcze jeden rower: przechowujemy ci\u0105gi znak\u00f3w Unicode o 30-60% bardziej kompaktowo ni\u017c UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/76cd12423b241cc316af80f03be4348f.jpeg\" style=\"display:block;margin: 0 auto;\" \/><\/a><\/noindex> sk\u0142ada si\u0119 z a\u017c 7 kod\u00f3w!), to naprawd\u0119 szkoda wydawa\u0107 na ka\u017cd\u0105 po trzy bajty (7\u00d73 = 21 bajt\u00f3w za jeden znak, to koszmar).<\/p>\n<p>Dlatego wybieramy kilka wybranych zakres\u00f3w odpowiadaj\u0105cych emotikonom, hiraganie i katakanie, numerujemy je ponownie w jedn\u0105 ci\u0105g\u0142\u0105 list\u0119 i kodujemy w postaci dw\u00f3ch bajt\u00f3w zamiast trzech:<\/p>\n<p><code><b>1011xxxx xxxxxxxx<\/b><\/code> <\/p>\n<p>\u015awietnie: wcze\u015bniej wspomniana emotikona \u200d\u200d\u200d<noindex><a rel=\"nofollow\" href=\"https:\/\/emojipedia.org\/family-woman-woman-girl-boy\/\"><img decoding=\"async\" alt=\"Jeszcze jeden rower: przechowujemy ci\u0105gi znak\u00f3w Unicode o 30-60% bardziej kompaktowo ni\u017c UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/b9900c78dda5d8e596e3751021b4d35d.jpeg\" style=\"display:block;margin: 0 auto;\" \/><\/a><\/noindex>, sk\u0142adaj\u0105ca si\u0119 z 7 punkt\u00f3w kodowych, w UTF-8 zajmuje 25 bajt\u00f3w, a my zmie\u015bcili\u015bmy j\u0105 w <strong>14<\/strong> dok\u0142adnie po dwa bajty na ka\u017cdy punkt kodowy). Nawiasem m\u00f3wi\u0105c, Habr odm\u00f3wi\u0142 jej przetworzenia (zar\u00f3wno w starym, jak i nowym edytorze), wi\u0119c musia\u0142em wstawi\u0107 j\u0105 jako obrazek.<\/p>\n<p>Spr\u00f3bujmy naprawi\u0107 jeszcze jeden problem. Jak pami\u0119tamy, g\u0142\u00f3wny alfabet to w rzeczywisto\u015bci <strong>najstarsze 6 bit\u00f3w<\/strong>, kt\u00f3re trzymamy w pami\u0119ci i przyklejamy do kodu ka\u017cdego kolejnego dekodowanego znaku. W przypadku chi\u0144skich znak\u00f3w, kt\u00f3re znajduj\u0105 si\u0119 w bloku <code><b>0x4E00<\/b><\/code> \u2013 <code><b>0x9FFF<\/b><\/code>, to jest to albo bit 0, albo 1. To nie jest zbyt wygodne: b\u0119dziemy musieli ca\u0142y czas prze\u0142\u0105cza\u0107 alfabet mi\u0119dzy tymi dwoma warto\u015bciami (tzn. wydawa\u0107 po trzy bajty). Ale zauwa\u017cmy, \u017ce w trybie d\u0142ugim z samego kodu mo\u017cemy odj\u0105\u0107 liczb\u0119 znak\u00f3w, kt\u00f3re kodujemy przy u\u017cyciu trybu kr\u00f3tkiego (po wszystkich wcze\u015bniej opisanych sztuczkach to 10240) \u2014 wtedy zakres znak\u00f3w przesunie si\u0119 do <code><b>0x2600<\/b><\/code> \u2013 <code><b>0x77FF<\/b><\/code>, a w tym przypadku w ca\u0142ym tym zakresie najwy\u017csze 6 bit\u00f3w (z 21) b\u0119dzie r\u00f3wne 0. Tak wi\u0119c, ci\u0105gi znak\u00f3w b\u0119d\u0105 u\u017cywa\u0107 po dwa bajty na znak (co jest optymalne dla tak du\u017cego zakresu), nie powoduj\u0105c prze\u0142\u0105cze\u0144 alfabetu. <\/p>\n<h2>Alternatywne rozwi\u0105zania: SCSU, BOCU-1<\/h2>\n<p>\nZnawcy Unicode, jeszcze tylko przeczytawszy tytu\u0142 artyku\u0142u, prawdopodobnie pospiesz\u0105 si\u0119, aby przypomnie\u0107, \u017ce bezpo\u015brednio w standardach Unicode istnieje <noindex><a rel=\"nofollow\" href=\"https:\/\/www.unicode.org\/reports\/tr6\/tr6-4.html\">Standard Compression Scheme for Unicode<\/a><\/noindex> (SCSU), kt\u00f3ry opisuje spos\u00f3b kodowania, bardzo podobny do tego opisanego w artykule.<\/p>\n<p>Przyznaj\u0119 si\u0119 szczerze: o jego istnieniu dowiedzia\u0142em si\u0119 dopiero g\u0142\u0119boko zanurzywszy si\u0119 w pisanie mojego rozwi\u0105zania. Gdybym wiedzia\u0142 o nim od samego pocz\u0105tku, prawdopodobnie spr\u00f3bowa\u0142bym napisa\u0107 jego implementacj\u0119 zamiast wymy\u015blania w\u0142asnego podej\u015bcia.<\/p>\n<p>Co ciekawe, SCSU wykorzystuje pomys\u0142y bardzo podobne do tych, do kt\u00f3rych doszed\u0142em samodzielnie (zamiast poj\u0119cia 'alfabet\u00f3w' u\u017cywane s\u0105 'okna', a ich dost\u0119pnych jest wi\u0119cej ni\u017c u mnie). Jednocze\u015bnie ten format ma te\u017c minusy: jest nieco bli\u017cej algorytm\u00f3w kompresji ni\u017c kodowania. W szczeg\u00f3lno\u015bci standard oferuje wiele sposob\u00f3w reprezentacji, ale nie m\u00f3wi, jak wybra\u0107 z nich optymalny \u2014 w tym celu enkoder musi stosowa\u0107 jakie\u015b heurystyki. Tak wi\u0119c, enkoder SCSU, oferuj\u0105cy dobr\u0105 kompresj\u0119, b\u0119dzie trudniejszy i bardziej niepor\u0119czny ni\u017c m\u00f3j algorytm.<\/p>\n<p>Dla por\u00f3wnania, przenios\u0142em stosunkowo prost\u0105 implementacj\u0119 SCSU na JavaScript \u2014 pod wzgl\u0119dem obj\u0119to\u015bci kodu okaza\u0142a si\u0119 por\u00f3wnywalna z moim UTF-C, ale w niekt\u00f3rych przypadkach uzyska\u0142a wyniki o dziesi\u0105tki procent gorsze (czasami mo\u017ce j\u0105 przewy\u017csza\u0107, ale nieznacznie). Na przyk\u0142ad teksty w j\u0119zyku hebrajskim i greckim UTF-C skompresowa\u0142em a\u017c <strong>o 60% lepiej ni\u017c SCSU<\/strong> (najprawdopodobniej z powodu ich kompaktowych alfabet\u00f3w).<\/p>\n<p>Osobno dodam da, osim SCSU, postoji i drugi na\u010din kompakt predstaviti Unicode \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Binary_Ordered_Compression_for_Unicode\">BOCU-1<\/a><\/noindex>, ale ma na celu zgodno\u015b\u0107 z MIME (co nie by\u0142o mi potrzebne), i u\u017cywa nieco innego podej\u015bcia do kodowania. Nie ocenia\u0142em jego efektywno\u015bci, ale wydaje mi si\u0119, \u017ce nie b\u0119dzie ona wy\u017csza ni\u017c SCSU.<\/p>\n<h2>Mo\u017cliwe udoskonalenia<\/h2>\n<p>\nZaproponowany przeze mnie algorytm nie jest uniwersalny z za\u0142o\u017cenia (w tym, chyba, moje cele w najwi\u0119kszym stopniu rozchodz\u0105 si\u0119 z celami konsorcjum Unicode). Ju\u017c wspomnia\u0142em, \u017ce by\u0142 on projektowany g\u0142\u00f3wnie do jednej zadania (przechowywania wieloj\u0119zycznego s\u0142ownika w drzewie prefiksowym), a niekt\u00f3re z jego cech mog\u0105 si\u0119 s\u0142abo sprawdza\u0107 w innych zadaniach. Ale fakt, \u017ce nie jest standardem, mo\u017ce by\u0107 r\u00f3wnie\u017c plusem \u2014 <strong>mo\u017cesz \u0142atwo dostosowa\u0107 go do swoich potrzeb<\/strong>.<\/p>\n<p>Na przyk\u0142ad, oczywi\u015bcie, mo\u017cna pozby\u0107 si\u0119 stanu, przeprowadzaj\u0105c kodowanie bezstanowe \u2014 po prostu nie aktualizuj\u0105c zmiennych <code><b>zwolnienia<\/b><\/code>, <code><b>auxOffs<\/b><\/code> i <code><b>is21Bit<\/b><\/code> w enkoderze i dekoderze. W takim przypadku nie b\u0119dzie mo\u017cna efektywnie pakowa\u0107 sekwencji znak\u00f3w z jednego alfabetu, ale b\u0119dzie gwarancja, \u017ce ten sam znak zawsze koduje si\u0119 tymi samymi bajtami, niezale\u017cnie od kontekstu.<\/p>\n<p>Ponadto mo\u017cna dostosowa\u0107 enkoder do konkretnego j\u0119zyka, zmieniaj\u0105c stan domy\u015blny \u2014 na przyk\u0142ad, orientuj\u0105c si\u0119 na tekstach rosyjskich, ustawi\u0107 na pocz\u0105tku enkodera i dekodera <code><b>offs = 0x0400<\/b><\/code> i <code><b>auxOffs = 0<\/b><\/code>. W szczeg\u00f3lno\u015bci ma to sens w przypadku trybu bezstanowego. Og\u00f3lnie rzecz bior\u0105c, b\u0119dzie to przypomina\u0142o u\u017cycie starego osiembitowego kodowania, tylko nie pozbawia mo\u017cliwo\u015bci wstawiania znak\u00f3w z ca\u0142ego Unicode w razie potrzeby.<\/p>\n<p>Kolejna wada, wspomniana wcze\u015bniej \u2014 w obszernej tek\u015bcie zakodowanym w UTF-C nie ma szybkiego sposobu na znalezienie granicy znaku najbli\u017cszego do dowolnego bajtu. Odcinaj\u0105c od zakodowanego bufora ostatnie, powiedzmy, 100 bajt\u00f3w, ryzykujesz uzyskanie \u015bmieci, z kt\u00f3rymi nie mo\u017cna nic zrobi\u0107. Kodowanie nie jest zaprojektowane do przechowywania kilku gigabajtowych log\u00f3w, ale w og\u00f3lnym zarysie mo\u017cna to poprawi\u0107. Bajt <code><b>0xBF<\/b><\/code> nigdy nie powinien wyst\u0119powa\u0107 jako pierwszy bajt (ale mo\u017ce by\u0107 drugim lub trzecim). Dlatego podczas kodowania mo\u017cna wstawi\u0107 sekwencj\u0119 <code><b>0xBF 0xBF 0xBF<\/b><\/code> na przyk\u0142ad co 10 KB \u2014 wtedy, w razie potrzeby, wystarczy zeskanowa\u0107 wybrany fragment, a\u017c znajdzie si\u0119 odpowiedni znacznik. Po tym ostatnim <code><b>0xBF<\/b><\/code> z pewno\u015bci\u0105 b\u0119dzie pocz\u0105tek znaku. (Podczas dekodowania, t\u0119 sekwencj\u0119 trzech bajt\u00f3w nale\u017cy oczywi\u015bcie zignorowa\u0107.)<\/p>\n<h2>Podsumowuj\u0105c<\/h2>\n<p>\nJe\u015bli dotar\u0142e\u015b tutaj \u2014 gratulacje! Mam nadziej\u0119, \u017ce, tak jak ja, nauczy\u0142e\u015b si\u0119 czego\u015b nowego (lub od\u015bwie\u017cy\u0142e\u015b star\u0105 wiedz\u0119) na temat dzia\u0142ania Unicode.<\/p>\n<p><img decoding=\"async\" alt=\"Jeszcze jeden rower: przechowujemy ci\u0105gi znak\u00f3w Unicode o 30-60% bardziej kompaktowo ni\u017c UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/f14b2d815b06a7fda7b77c0a32e7eb75.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Strona demonstracyjna. Na przyk\u0142adzie hebrajskiego wida\u0107 zalety zar\u00f3wno w por\u00f3wnaniu z UTF-8, jak i SCSU.<\/i><\/p>\n<p>Nie nale\u017cy traktowa\u0107 powy\u017cszych rozwa\u017ca\u0144 jako naruszenie standard\u00f3w. Jednak og\u00f3lnie jestem zadowolony z wynik\u00f3w mojej pracy, wi\u0119c ciesz\u0119 si\u0119, \u017ce <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/deNULL\/utf-c\">mog\u0119 si\u0119 nimi<\/a><\/noindex>podzieli\u0107: na przyk\u0142ad, biblioteka JS w zminimalizowanej wersji wa\u017cy zaledwie 1710 bajt\u00f3w (i nie ma oczywi\u015bcie \u017cadnych zale\u017cno\u015bci). Jak wspomnia\u0142em wcze\u015bniej, mo\u017cna zapozna\u0107 si\u0119 z jej dzia\u0142aniem na <noindex><a rel=\"nofollow\" href=\"https:\/\/denull.github.io\/utf-c\/\">stronie demo<\/a><\/noindex> (tam znajduje si\u0119 r\u00f3wnie\u017c zestaw tekst\u00f3w, na kt\u00f3rych mo\u017cna por\u00f3wna\u0107 j\u0105 z UTF-8 i SCSU).<\/p>\n<p>Na koniec jeszcze raz zwr\u00f3c\u0119 uwag\u0119 na przypadki, w kt\u00f3rych u\u017cywanie UTF-C <b>jest niezalecane<\/b>:<\/p>\n<ul>\n<li>Je\u015bli twoje ci\u0105gi s\u0105 wystarczaj\u0105co d\u0142ugie (od 100 do 200 znak\u00f3w). W takim przypadku warto rozwa\u017cy\u0107 zastosowanie algorytm\u00f3w kompresji, takich jak deflate.<\/li>\n<li>Je\u015bli potrzebujesz <em>przezroczysto\u015bci ASCII<\/em>, to znaczy wa\u017cne jest dla ciebie, aby w zakodowanych sekwencjach nie wyst\u0119powa\u0142y kody ASCII, kt\u00f3re nie by\u0142y obecne w oryginalnym ci\u0105gu. Mo\u017cna unikn\u0105\u0107 tych potrzeb, je\u015bli podczas wsp\u00f3\u0142pracy z zewn\u0119trznymi API (np. pracuj\u0105c z baz\u0105 danych) b\u0119dziesz przekazywa\u0107 wyniki kodowania jako abstrakcyjny zbi\u00f3r bajt\u00f3w, a nie jako ci\u0105gi. W przeciwnym razie ryzykujesz wyst\u0105pieniem nieprzewidzianych luk.<\/li>\n<li>Je\u015bli chcesz mie\u0107 mo\u017cliwo\u015b\u0107 szybkiego znajdowania granic znak\u00f3w w dowolnym przesuni\u0119ciu (na przyk\u0142ad w przypadku uszkodzenia cz\u0119\u015bci ci\u0105gu). Mo\u017cna to zrobi\u0107, ale tylko poprzez zeskanowanie ci\u0105gu od pocz\u0105tku (lub zastosowanie modyfikacji opisanej w poprzedniej sekcji).<\/li>\n<li>Je\u015bli potrzebujesz szybko wykonywa\u0107 operacje na zawarto\u015bci ci\u0105g\u00f3w (sortowa\u0107 je, wyszukiwa\u0107 podci\u0105gi, konkatenowa\u0107). W tym celu ci\u0105gi nale\u017cy najpierw dekodowa\u0107, dlatego UTF-C b\u0119dzie wolniejszy ni\u017c UTF-8 w tych przypadkach (ale szybszy ni\u017c algorytmy kompresji). Poniewa\u017c ten sam ci\u0105g zawsze jest kodowany w ten sam spos\u00f3b, dok\u0142adne por\u00f3wnanie dekodowania nie jest wymagane, mo\u017cna je wykona\u0107 bajt po bajcie.<\/li>\n<\/ul>\n<p><b>Aktualizacja:<\/b> u\u017cytkownik <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/users\/tyomitch\/\"><b>tyomitch<\/b><\/a><\/noindex> <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/521110\/#comment_22139258\">w komentarzach poni\u017cej<\/a><\/noindex> opublikowa\u0142 wykres pokazuj\u0105cy granice zastosowania UTF-C. Wida\u0107 na nim, \u017ce UTF-C jest bardziej efektywny ni\u017c algorytmy kompresji og\u00f3lnego przeznaczenia (warianty LZW), dop\u00f3ki kompresowany ci\u0105g jest kr\u00f3tszy <b>~140 znak\u00f3w<\/b> (cho\u0107 zauwa\u017cam, \u017ce por\u00f3wnanie by\u0142o przeprowadzone na jednym tek\u015bcie; dla innych j\u0119zyk\u00f3w wyniki mog\u0105 si\u0119 r\u00f3\u017cni\u0107).<br \/>\n<img decoding=\"async\" alt=\"Jeszcze jeden rower: przechowujemy ci\u0105gi znak\u00f3w Unicode o 30-60% bardziej kompaktowo ni\u017c UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/0bc9f35ad2757d656801c6466dda09a8.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\u0179r\u00f3d\u0142o: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/521110\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0415\u0441\u043b\u0438 \u0432\u044b \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0438 \u043f\u0435\u0440\u0435\u0434 \u0432\u0430\u043c\u0438 \u0441\u0442\u043e\u0438\u0442 \u0437\u0430\u0434\u0430\u0447\u0430 \u0432\u044b\u0431\u043e\u0440\u0430 \u043a\u043e\u0434\u0438\u0440\u043e\u0432\u043a\u0438, \u0442\u043e \u043f\u043e\u0447\u0442\u0438 \u0432\u0441\u0435\u0433\u0434\u0430 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u043c \u0440\u0435\u0448\u0435\u043d\u0438\u0435\u043c \u0431\u0443\u0434\u0435\u0442 \u042e\u043d\u0438\u043a\u043e\u0434. \u041a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430, \u043d\u043e \u0447\u0430\u0449\u0435 \u0432\u0441\u0435\u0433\u043e \u0442\u0443\u0442 \u0442\u043e\u0436\u0435 \u0435\u0441\u0442\u044c \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u044b\u0439 \u043e\u0442\u0432\u0435\u0442 \u2014 UTF-8. \u041e\u043d \u0445\u043e\u0440\u043e\u0448 \u0442\u0435\u043c, \u0447\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u0435 \u0441\u0438\u043c\u0432\u043e\u043b\u044b \u042e\u043d\u0438\u043a\u043e\u0434\u0430, \u043d\u0435 \u0442\u0440\u0430\u0442\u044f \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u043c\u043d\u043e\u0433\u043e \u0431\u0430\u0439\u0442 \u0432 \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u0435 \u0441\u043b\u0443\u0447\u0430\u0435\u0432. \u041f\u0440\u0430\u0432\u0434\u0430, \u0434\u043b\u044f \u044f\u0437\u044b\u043a\u043e\u0432, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0449\u0438\u0445 \u043d\u0435 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":95912,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-95911","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.1.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0415\u0441\u043b\u0438 \u0432\u044b.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/eshhyo-odin-velosiped-hranim-yunikodnye-stroki-na-30-60-kompaktnee-chem-utf-8\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"pl_PL\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u0415\u0449\u0451 \u043e\u0434\u0438\u043d \u0432\u0435\u043b\u043e\u0441\u0438\u043f\u0435\u0434: \u0445\u0440\u0430\u043d\u0438\u043c \u044e\u043d\u0438\u043a\u043e\u0434\u043d\u044b\u0435 \u0441\u0442\u0440\u043e\u043a\u0438 \u043d\u0430 30-60% \u043a\u043e\u043c\u043f\u0430\u043a\u0442\u043d\u0435\u0435, \u0447\u0435\u043c UTF-8 | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u0415\u0441\u043b\u0438 \u0432\u044b.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/eshhyo-odin-velosiped-hranim-yunikodnye-stroki-na-30-60-kompaktnee-chem-utf-8\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2020-10-04T23:42:09+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-10-04T23:42:09+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47 Jeszcze jedno ko\u0142o: przechowujemy ci\u0105gi Unicode o 30-60% bardziej kompaktowo ni\u017c UTF-8 | ProHoster","description":"Je\u015bli ty.","canonical_url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/eshhyo-odin-velosiped-hranim-yunikodnye-stroki-na-30-60-kompaktnee-chem-utf-8","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pl_PL","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u0415\u0449\u0451 \u043e\u0434\u0438\u043d \u0432\u0435\u043b\u043e\u0441\u0438\u043f\u0435\u0434: \u0445\u0440\u0430\u043d\u0438\u043c \u044e\u043d\u0438\u043a\u043e\u0434\u043d\u044b\u0435 \u0441\u0442\u0440\u043e\u043a\u0438 \u043d\u0430 30-60% \u043a\u043e\u043c\u043f\u0430\u043a\u0442\u043d\u0435\u0435, \u0447\u0435\u043c UTF-8 | ProHoster","og:description":"\u0415\u0441\u043b\u0438 \u0432\u044b.","og:url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/eshhyo-odin-velosiped-hranim-yunikodnye-stroki-na-30-60-kompaktnee-chem-utf-8","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2020-10-04T23:42:09+00:00","article:modified_time":"2020-10-04T23:42:09+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"95911","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 10:55:40","updated":"2022-09-29 03:35:04","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/95911","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/comments?post=95911"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/95911\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media\/95912"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media?parent=95911"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/categories?post=95911"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/tags?post=95911"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}