{"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\/nl\/blog\/administrirovanie\/eshhyo-odin-velosiped-hranim-yunikodnye-stroki-na-30-60-kompaktnee-chem-utf-8","title":{"rendered":"Nog een fiets: we slaan Unicode-strings 30-60% compacter op dan UTF-8","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"Nog een fiets: we slaan Unicode-strings 30-60% compacter op dan UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/56c10bad377127b711bdb204ee300772.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAls je een ontwikkelaar bent en de taak hebt om een codering te kiezen, is Unicode bijna altijd de juiste keuze. De specifieke manier van weergave hangt af van de context, maar meestal is er ook hier een universeel antwoord - UTF-8. Dit is goed omdat het alle Unicode-symbolen toestaat zonder te veel te verspillen. <em>te veel<\/em> bytes in de meeste gevallen. Voor talen die niet alleen het Latijnse alfabet gebruiken, betekent \"niet te veel\" echter minimaal <strong>twee bytes per teken<\/strong>. Is er een betere optie zonder terug te keren naar de prehistorische coderingen die ons beperken tot slechts 256 beschikbare symbolen?<\/p>\n<p>Hieronder bied ik mijn poging aan om deze vraag te beantwoorden en een relatief eenvoudig algoritme te implementeren dat het mogelijk maakt om tekst in de meeste talen ter wereld op te slaan, zonder de overbodigheid die aanwezig is in UTF-8.<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<p><i>Disclaimer.<\/i> Ik wil een paar belangrijke opmerkingen maken: <strong>de beschreven oplossing wordt niet gepresenteerd als een universele vervanging voor UTF-8<\/strong>, het is alleen geschikt voor een beperkte reeks gevallen (daarover hieronder) en het mag absoluut niet worden gebruikt voor interactie met externe API's (die er helemaal niet van op de hoogte zijn). Vaak zijn generieke compressie-algoritmes (bijvoorbeeld deflate) geschikter voor het compact opslaan van grote hoeveelheden tekstgegevens. Bovendien ontdekte ik tijdens het cre\u00ebren van mijn oplossing een bestaande standaard binnen Unicode die hetzelfde probleem oplost - het is iets ingewikkelder (en vaak slechter), maar het is in ieder geval een geaccepteerde standaard en geen zelfgemaakte oplossing. Daarover zal ik ook vertellen.<\/p>\n<h2>Over Unicode en UTF-8<\/h2>\n<p>\nLaten we beginnen met een paar woorden over wat <strong>Unicode<\/strong> en <strong>UTF-8<\/strong>.<\/p>\n<p>Zoals bekend, waren 8-bits coderingen vroeger populair. Het was eenvoudig: 256 symbolen kunnen genummerd worden van 0 tot 255, en de getallen van 0 tot 255 zijn uiteraard te representeren in \u00e9\u00e9n byte. Terugkerend naar de oorsprong, beperkt de ASCII-codering zich zelfs tot 7 bits, waardoor de meest significante bit in de byte-representatie nul is, en de meeste 8-bits coderingen compatibel zijn (ze verschillen alleen in het 'bovenste' gedeelte, waar de meest significante bit \u00e9\u00e9n is).<\/p>\n<p>Wat zijn de verschillen tussen Unicode en die coderingen en waarom zijn er zoveel specifieke representaties - UTF-8, UTF-16 (BE en LE), UTF-32? Laten we het systematisch bekijken.<\/p>\n<p>De belangrijkste Unicode-standaard beschrijft alleen de overeenstemming tussen tekens (en in sommige gevallen - afzonderlijke componenten van tekens) en hun nummers. En er zijn veel mogelijke nummers in deze standaard - van <code><b>0x00<\/b><\/code> tot <code><b>0x10FFFF<\/b><\/code> (1 114 112 stuks). Als we een nummer in een dergelijk bereik in een variabele wilden plaatsen, zouden we met 1 of 2 bytes niet rondkomen. En omdat onze processors niet goed zijn ingericht voor de behandeling van driefnummers, zouden we 4 bytes per teken moeten gebruiken! Dit is wat UTF-32 is, maar juist vanwege deze 'verspilling' is dit formaat niet populair.<\/p>\n<p>Gelukkig zijn de tekens binnen Unicode niet willekeurig geordend. Al deze elementen zijn verdeeld in 17 '<em>vlakken<\/em>', elk met 65536 (<code><b>0x10000<\/b><\/code>) \u00ab<em>codepunten<\/em>). Het begrip 'codepunt' hier is eenvoudigweg <em>het nummer van een teken<\/em>, dat door Unicode is toegewezen. Maar, zoals hierboven vermeld, zijn niet alleen afzonderlijke tekens in Unicode genummerd, maar ook hun componenten en uitvoeringsnotities (en soms komt er helemaal geen nummer aan te pas - misschien tijdelijk, maar dat is voor ons minder belangrijk). Daarom is het nauwkeuriger om altijd over het aantal nummers te praten, en niet over tekens. Echter, voor de kortheid zal ik hierna vaak het woord 'teken' gebruiken, waarmee ik de term 'codepunt' bedoel.<\/p>\n<p><img decoding=\"async\" alt=\"Nog een fiets: we slaan Unicode-strings 30-60% compacter op dan UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/7ad84b571a8025582fdbc3db956cb3b0.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Unicode-vlakken. Zoals te zien is, is het merendeel (de vlakken van 4 tot 13) nog steeds niet in gebruik.<\/i><\/p>\n<p>Het meest opvallende is dat de hele 'kern' zich in de nul-dimensionale ruimte bevindt, deze wordt genoemd &quot;<em>Basic Multilingual Plane<\/em>&quot;. Als de regel tekst in een van de moderne talen staat (inclusief het Chinees), kom je daarbuiten niet. Maar je kunt ook de andere delen van Unicode niet uitsluiten - bijvoorbeeld, emoji's bevinden zich voornamelijk aan het einde van de volgende dimensie, &quot;<em>Supplementary Multilingual Plane<\/em>&quot; (die zich uitstrekt van <code><b>0x10000<\/b><\/code> tot <code><b>0x1FFFF<\/b><\/code>). Daarom werkt UTF-16 als volgt: alle tekens die in <em>Basic Multilingual Plane<\/em>, worden gecodeerd 'zoals ze zijn', met het bijbehorende tweebitsgetal. Echter, sommige nummers in dit bereik vertegenwoordigen helemaal geen specifieke tekens, maar geven aan dat we na dit paar bytes nog een paar moeten overwegen - door de waarden van deze vier bytes samen te voegen, krijgen we een nummer dat het volledige toegestane bereik van Unicode omvat. Deze weergave wordt 'surrogaatparen' genoemd - mogelijk heeft u daar al van gehoord.<\/p>\n<p>Zo vereist UTF-16 twee of (in zeer zeldzame gevallen) vier bytes voor \u00e9\u00e9n 'codepunt'. Dit is beter dan continu vier bytes te gebruiken, maar het Latijnse alfabet (en andere ASCII-tekens) verbruikt bij deze codering de helft van de ruimte aan nullen. UTF-8 is bedoeld om dit te verbeteren: ASCII neemt daarin, zoals voorheen, slechts \u00e9\u00e9n byte in beslag; codes van <code><b>0x80<\/b><\/code> tot <code><b>0x7FF<\/b><\/code> \u2014 twee bytes; van <code><b>0x800<\/b><\/code> tot <code><b>0xFFFF<\/b><\/code> \u2014 drie, en van <code><b>0x10000<\/b><\/code> tot <code><b>0x10FFFF<\/b><\/code> \u2014 vier. Enerzijds gaat het goed met het Latijnse alfabet: de compatibiliteit met ASCII is teruggebracht, en de verdeling is meer gelijkmatig 'verspreid' van 1 tot 4 bytes. Maar alfabetten die anders zijn dan het Latijnse, profiteren helaas niet in vergelijking met UTF-16, en vele vereisen nu drie bytes in plaats van twee \u2014 het bereik dat door de tweebyte codering wordt gedekt, is 32 keer kleiner, van <code><b>0xFFFF<\/b><\/code> tot <code><b>0x7FF<\/b><\/code>, en daarin komt al geen Chinees meer voor, noch bijvoorbeeld Georgisch. Cyrillisch en nog vijf alfabetten - hoera - hebben geluk, 2 bytes per teken.<\/p>\n<p>Waarom is dat zo? Laten we eens kijken hoe UTF-8 de codes van de tekens voorstelt:<br \/>\n<img decoding=\"async\" alt=\"Nog een fiets: we slaan Unicode-strings 30-60% compacter op dan UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/4ef4fe9e949cd75295c45c053dbca6d3.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nDirect voor de representatie van de getallen worden hier bits gebruikt, gemarkeerd met het teken <code><b>x<\/b><\/code>. Het is duidelijk dat er in de tweebyte codering slechts 11 van dergelijke bits (uit 16) zijn. De leidende bits vervullen hier alleen een functie voor beheer. In het geval van de vierbyte codering is er zelfs 21 bit gereserveerd voor het codepunt uit 32 \u2014 het lijkt erop dat drie bytes (die in totaal 24 bits geven) voldoende zouden zijn, maar de beheermarkers slokken te veel op.<\/p>\n<p>Is dat slecht? Eigenlijk niet echt. Enerzijds \u2014 als we ons veel zorgen maken over de ruimte die beslagen wordt, hebben we compressie-algoritmes die gemakkelijk alle overbodige entropie en redundantie kunnen uitschakelen. Aan de andere kant was het doel van Unicode om de meest universele codering te geven. Bijvoorbeeld, een in UTF-8 gecodeerde string kunnen we toevertrouwen aan een code die voorheen alleen met ASCII werkte, en we hoeven niet bang te zijn dat het daar een teken uit het ASCII-bereik tegenkomt dat er eigenlijk niet is (want in UTF-8 zijn alle bytes die beginnen met de nulbit, inderdaad ASCII). En als we plotseling een klein staartje van een grote string willen afsnijden, zonder deze vanaf het begin te decoderen (of een deel van de informatie willen herstellen na een beschadigd stuk) \u2014 is het niet moeilijk om die offset te vinden waar een teken begint (het is genoeg om bytes over te slaan met een bitprefix <code><b>10<\/b><\/code>).<\/p>\n<h2>Waarom dan iets nieuws uitvinden?<\/h2>\n<p>\nTegelijkertijd zijn er af en toe situaties waarin compressie-algoritmen zoals deflate slecht toepasbaar zijn, maar je toch een compacte opslag van strings wilt bereiken. Persoonlijk heb ik met deze taak te maken gehad terwijl ik nadacht over de constructie <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Radix_tree\">van een gecomprimeerde prefixboom<\/a><\/noindex> voor een groot woordenboek dat woorden in willekeurige talen bevat. Enerzijds zijn de woorden heel kort, dus compressie is niet effici\u00ebnt. Anderzijds was de implementatie van de boom die ik overwoog, gericht op het feit dat elke byte van de opgeslagen string een aparte knoop in de boom genereerde, dus het minimaliseren van hun aantal was zeer nuttig. In mijn bibliotheek <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/deNULL\/Az.js\">Az.js<\/a><\/noindex> (net als in <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/kmike\/pymorphy2\">pymorphy2<\/a><\/noindex>, waarop deze is gebaseerd) wordt een dergelijk probleem eenvoudig opgelost \u2014 strings verpakt in <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Deterministic_acyclic_finite_state_automaton\">een DAWG<\/a><\/noindex>-woordenboek worden daar opgeslagen in <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/deNULL\/Az.js\/blob\/master\/src\/az.dawg.js\">het oude vertrouwde CP1251<\/a><\/noindex>. Maar zoals je gemakkelijk kunt begrijpen, werkt dit alleen goed voor een beperkte alfabet \u2014 een string in het Chinees kan je al niet meer in zo'n woordenboek plaatsen.<\/p>\n<p>Daarnaast wil ik nog een vervelend nuancepunt opmerken dat zich voordoet bij het gebruik van UTF-8 in dergelijke datastructuren. Op de afbeelding hierboven is te zien dat bij het opslaan van een teken in de vorm van twee bytes, de bits die betrekking hebben op zijn nummer, niet aaneengeschakeld zijn, maar onderbroken door een paar bits <code><b>10<\/b><\/code> in het midden: <code><b>110xxxxx 10xxxxxx<\/b><\/code>. Hierdoor, wanneer de lagere 6 bits van de tweede byte van de code van het teken overlopen (dat wil zeggen, er is een overgang <code><b>10111111<\/b><\/code> \u2192 <code><b>10000000<\/b><\/code>), verandert ook de eerste byte. Het resultaat is dat de letter \u201e\u043f\u201d wordt weergegeven door de bytes <code><b>0xD0 0xBF<\/b><\/code>, en de volgende \u201e\u0440\u201d \u2014 al door <code><b>0xD1 0x80<\/b><\/code>. In de prefixboom leidt dit tot het splitsen van de ouderknop in twee \u2014 \u00e9\u00e9n voor het prefix <code><b>0xD0<\/b><\/code>, en de andere voor <code><b>0xD1<\/b><\/code> (hoewel de hele Cyrillische tekst alleen met de tweede byte gecodeerd zou kunnen worden).<\/p>\n<h2>Wat ik heb bereikt<\/h2>\n<p>\nToen ik met deze taak geconfronteerd werd, besloot ik om wat te experimenteren met bits, en tegelijkertijd mezelf iets beter te leren kennen met de Unicode-structuur in het algemeen. Het resultaat was het UTF-C coderingsformaat (de \u201eC\u201d staat voor <em>compact<\/em>), dat niet meer dan 3 bytes per codepunt verbruikt, en vaak slechts <strong>\u00e9\u00e9n extra byte vereist voor de hele gecodeerde string<\/strong>. Dit zorgt ervoor dat voor veel niet-ASCII alfabetten deze codering <strong>30-60% compacter is dan UTF-8<\/strong>.<\/p>\n<p>Ik heb voorbeelden van de implementatie van coderings- en decodering algoritmen gepresenteerd in de vorm van <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/deNULL\/utf-c\">bibliotheken in JavaScript en Go<\/a><\/noindex>, u kunt ze vrij gebruiken in uw code. Maar ik wil toch benadrukken dat dit formaat in zekere zin een 'fiets' blijft, en ik raad aan het niet te gebruiken <strong>zonder te beseffen waarom u het nodig heeft<\/strong>. Het is tenslotte meer een experiment dan een serieuze 'verbetering van UTF-8'. Desondanks is de code daar netjes en beknopt geschreven, met veel commentaar en testdekking.<\/p>\n<p><img decoding=\"async\" alt=\"Nog een fiets: we slaan Unicode-strings 30-60% compacter op dan UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/e31978174449cd7de5d8371aaeb9ab2e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>De resultaten van de tests en de vergelijking met UTF-8<\/i><\/p>\n<p>Daarnaast heb ik <noindex><a rel=\"nofollow\" href=\"https:\/\/denull.github.io\/utf-c\/\">een demo-pagina<\/a><\/noindex>, waar u de werking van het algoritme kunt beoordelen, en verder zal ik meer vertellen over de principes en het ontwikkelingsproces.<\/p>\n<h2>Overbodige bits verwijderen<\/h2>\n<p>\nIk heb, natuurlijk, UTF-8 als basis genomen. Het eerste en meest voor de hand liggende dat je kunt veranderen, is het aantal controlebits in elke byte verminderen. Bijvoorbeeld, de eerste byte in UTF-8 begint altijd met <code><b>0<\/b><\/code>, of met <code><b>11<\/b><\/code> \u2014 en het prefix <code><b>10<\/b><\/code> is alleen voor de volgende bytes. Laten we het prefix vervangen, <code><b>11<\/b><\/code> en een werkende opdracht krijgen. <code><b>1<\/b><\/code>en bij de volgende bytes verwijderen we de prefixes helemaal. Wat krijgen we dan?<\/p>\n<p><code><b>0xxxxxxx<\/b><\/code> \u2014 1 byte <br \/>\n<code><b>10xxxxxx xxxxxxxx<\/b><\/code> \u2014 2 bytes <br \/>\n<code><b>110xxxxx xxxxxxxx xxxxxxxx<\/b><\/code> \u2014 3 bytes<\/p>\n<p>Stop, waar is de vierbyte notatie? Die is niet meer nodig \u2014 met de opschrijving over drie bytes hebben we nu 21 bits beschikbaar, en dat is ruim voldoende voor alle getallen tot <code><b>0x10FFFF<\/b><\/code>.<\/p>\n<p>Wat hebben we hier opgeofferd? Het belangrijkste is het ontdekken van de grenzen van symbolen vanuit een willekeurige plek in de buffer. We kunnen niet zomaar naar een willekeurige byte wijzen en vanaf daar het begin van het volgende symbool vinden. Dit is een beperking van ons formaat, maar in de praktijk komt de behoefte eraan niet vaak voor. Gewoonlijk zijn we in staat om de buffer vanaf het begin te doorlopen (vooral als het gaat om korte strings).<\/p>\n<p>De situatie met de dekking van talen met 2 bytes is ook verbeterd: nu biedt het tweebyteformaat een bereik van 14 bits, wat codes tot <code><b>0x3FFF<\/b><\/code>. De Chinezen hebben pech (hun hieroglyphen liggen voornamelijk in het bereik van <code><b>0x4E00<\/b><\/code> tot <code><b>0x9FFF<\/b><\/code>), maar voor de Georgi\u00ebrs en veel andere volkeren is het nu gemakkelijker \u2014 hun talen passen ook in 2 bytes per symbool.<\/p>\n<h2>We voeren de toestand van de encoder in<\/h2>\n<p>\nLaten we nu eens nadenken over de eigenschappen van de rijen zelf. In het woordenboek staan meestal woorden die met de symbolen van \u00e9\u00e9n alfabet zijn geschreven, en dat geldt ook voor veel andere teksten. Het zou goed zijn om dit alfabet een keer op te geven, en vervolgens alleen het nummer van de letter binnenin. Laten we bekijken of de indeling van de symbolen in de Unicode-tabel ons helpt.<\/p>\n<p>Zoals hierboven vermeld, is Unicode verdeeld in <em>vlakken<\/em> per 65536 codes each. However, this division is not very useful (as mentioned, we are mostly in the zero plane). A more interesting division is into <em>blocks.<\/em> These ranges no longer have a fixed length and carry more meaning \u2014 as a rule, each combines characters from one alphabet.<\/p>\n<p><img decoding=\"async\" alt=\"Nog een fiets: we slaan Unicode-strings 30-60% compacter op dan UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/61ea5e859d7e6d9c75e977a3579ff28f.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>A block containing characters from the Bengali alphabet. Unfortunately, due to historical reasons, this is an example of not very dense packing \u2014 96 characters are scattered chaotically across 128 code points of the block.<\/i><\/p>\n<p>The beginnings of blocks and their sizes are always multiples of 16 \u2014 this is done simply for convenience. Additionally, many blocks start and end at values that are multiples of 128 or even 256 \u2014 for instance, the primary Cyrillic occupies 256 bytes from <code><b>0x0400<\/b><\/code> tot <code><b>0x04FF<\/b><\/code>. This is quite convenient: if we save the prefix once <code><b>0x04<\/b><\/code>, then any Cyrillic character can be recorded in a single byte. However, this means we lose the ability to revert to ASCII (and any other characters at all). Therefore, we do it like this:<\/p>\n<ol>\n<li>Two bytes <code><b>10yyyyyy yxxxxxxx<\/b><\/code> not only denote a character with number <code><b>yyyyyy yxxxxxxx<\/b><\/code>, but also alter <em>the current alphabet<\/em> en een werkende opdracht krijgen. <code><b>yyyyyy y0000000<\/b><\/code> (i.e., we remember all bits except for the least significant ones, <strong>7 bits.<\/strong>);<\/li>\n<li>One byte <code><b>0xxxxxxx<\/b><\/code> is a symbol from the current alphabet. It simply needs to be added to the offset we remembered in step 1. As long as we haven't changed the alphabet, the offset is zero, so we preserve compatibility with ASCII.<\/li>\n<\/ol>\n<p>\nSimilarly for codes that require 3 bytes:<\/p>\n<ol>\n<li>Three bytes <code><b>110yyyyy yxxxxxxx xxxxxxxx<\/b><\/code> denote a character with number <code><b>yyyyyy yxxxxxxx xxxxxxxx<\/b><\/code>, change <em>the current alphabet<\/em> en een werkende opdracht krijgen. <code><b>yyyyyy y0000000 00000000<\/b><\/code> (we remembered everything except for the least significant ones, <strong>15 bits<\/strong>), and set a flag that we are now in <em>long<\/em> mode (when switching the alphabet back to the two-byte one, we will reset this flag);<\/li>\n<li>Two bytes <code><b>0xxxxxxx xxxxxxxx<\/b><\/code> in long mode, this is a symbol from the current alphabet. Similarly, we add it to the offset from step 1. The only difference is that now we read two bytes (because we switched to that mode).<\/li>\n<\/ol>\n<p>\nSounds good: as long as we need to encode characters from the same 7-bit range of Unicode, we spend 1 extra byte at the start and only one byte per character.<\/p>\n<p><img decoding=\"async\" alt=\"Nog een fiets: we slaan Unicode-strings 30-60% compacter op dan UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/084d636a25dccdaa9f5a6eb5233c8e99.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>The work of one of the early versions. It already often surpasses UTF-8, but there's still room for improvement.<\/i><\/p>\n<p>What has worsened? Firstly, we have acquired a state, namely <em>the offset of the current alphabet<\/em> and a flag <em>for long mode.<\/em>. Dit beperkt ons verder: nu kunnen dezelfde symbolen op verschillende manieren worden gecodeerd in verschillende contexten. Het zoeken naar substrings zal bijvoorbeeld rekening moeten houden met dit aspect, in plaats van alleen bytes te vergelijken. Ten tweede, zodra we het alfabet veranderden, ontstonden er problemen met de codering van ASCII-tekens (wat niet alleen het Latijnse alfabet is, maar ook basisinterpunctie, inclusief spaties) \u2014 ze vereisen een herhaalde verandering van het alfabet bij 0, dat wil zeggen opnieuw een extra byte (en dan nog een om terug te keren naar ons hoofdalfabet).<\/p>\n<h2>\u00c9\u00e9n alfabet is goed, twee is beter<\/h2>\n<p>\nLaten we onze bitprefixen iets aanpassen door er nog \u00e9\u00e9n aan de drie hierboven beschreven toe te voegen:<\/p>\n<p><code><b>0xxxxxxx<\/b><\/code> \u2014 1 byte in normale modus, 2 in lange modus <br \/>\n<code><b>11xxxxxx<\/b><\/code> \u2014 1 byte <br \/>\n<code><b>100xxxxx xxxxxxxx<\/b><\/code> \u2014 2 bytes <br \/>\n<code><b>101xxxxx xxxxxxxx xxxxxxxx<\/b><\/code> \u2014 3 bytes<\/p>\n<p><img decoding=\"async\" alt=\"Nog een fiets: we slaan Unicode-strings 30-60% compacter op dan UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/fa1eca1adb80b15a4cf4f60f57a09c6c.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nNu is er in de tweebyte notatie \u00e9\u00e9n beschikbare bit minder \u2014 coderingstekens tot <code><b>0x1FFF<\/b><\/code>, en niet <code><b>0x3FFF<\/b><\/code>. Desondanks is dit nog steeds aanzienlijk meer dan bij de tweebyte codes van UTF-8, de meeste gangbare talen passen er nog steeds in, het meest opvallende verlies is de <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> en <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>, de Japanners zijn bedroefd.<\/p>\n<p>Wat voor een nieuwe code <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>) alfabet. Wanneer we het huidige alfabet wisselen, wordt een deel van het oude alfabet een hulpalfabet. Bijvoorbeeld, als we van ASCII op Cyrillisch schakelen \u2014 in de \"schuilplaats\" staan nu 64 symbolen, waaronder <strong>het Latijnse alfabet, cijfers, spatie en komma<\/strong> (de meest voorkomende invoegen in niet-ASCII teksten). Wanneer we terugschakelen naar ASCII \u2014 wordt een groot deel van het Cyrillische alfabet het hulpalfabet.<\/p>\n<p>Dankzij de toegang tot twee alfabets, kunnen we omgaan met een grotere hoeveelheid teksten, met minimale kosten voor het schakelen van alfabetten (interpunctie zal meestal leiden tot terugkeer naar ASCII, maar daarna kunnen we veel niet-ASCII symbolen al uit het aanvullende alfabet halen, zonder opnieuw te schakelen).<\/p>\n<p>Bonus: door het aanvullende alfabet een prefix te geven <code><b>11xxxxxx<\/b><\/code> en de initi\u00eble verschuiving in te stellen op <code><b>0xC0<\/b><\/code>, krijgen we gedeeltelijke compatibiliteit met CP1252. Met andere woorden, veel (maar niet allemaal) West-Europese teksten gecodeerd in CP1252 zullen er ook zo uitzien in UTF-C.<\/p>\n<p>Hier ontstaat echter een moeilijkheid: hoe krijgen we het hulpalfabet uit het hoofdalfabet? We kunnen dezelfde verschuiving behouden, maar \u2014 helaas \u2014 hier speelt de Unicode-structuur tegen ons. Vaak bevindt het hoofddeel van het alfabet zich niet aan het begin van het blok (bijvoorbeeld, de Russische hoofdletter \"\u0410\" heeft code <code>0x04<b>10<\/b><\/code>, hoewel het Cyrillische blok begint met <code>0x04<b>00<\/b><\/code>). Dus door de eerste 64 tekens in de \u2018reserve\u2019 te nemen, verliezen we mogelijk toegang tot het eindgedeelte van het alfabet.<\/p>\n<p>Om dit probleem op te lossen, heb ik handmatig enkele blokken doorgenomen die overeenkomen met verschillende talen en heb ik de verschuiving van het hulpalfabet binnen het hoofdalfabet aangegeven. De Latijnse letters heb ik, als uitzondering, helemaal opnieuw gerangschikt, alsof het base64 is.<\/p>\n<p><img decoding=\"async\" alt=\"Nog een fiets: we slaan Unicode-strings 30-60% compacter op dan UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/d191a3bb17403e99d506dbd008b63159.jpeg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<h2>Laatste handtekeningen<\/h2>\n<p>\nLaten we tenslotte nadenken waar we nog iets kunnen verbeteren.<\/p>\n<p>Laten we opmerken dat het formaat <code><b>101xxxxx xxxxxxxx xxxxxxxx<\/b><\/code> in staat is om cijfers tot <code><b>0x1FFFFF<\/b><\/code>, en Unicode eindigt eerder, op <code><b>0x10FFFF<\/b><\/code>. Met andere woorden, het laatste codepunt zal worden weergegeven als <code><b>10110000 11111111 11111111<\/b><\/code>. Dus kunnen we zeggen dat als de eerste byte eruitziet als <code><b>1011xxxx<\/b><\/code> (waar <code><b>xxxx<\/b><\/code> groter is dan 0), dan betekent dit iets anders. Bijvoorbeeld, we kunnen daar nog eens 15 tekens aan toevoegen, die altijd beschikbaar zijn voor codering met \u00e9\u00e9n byte, maar ik heb besloten het anders te doen.<\/p>\n<p>Laten we kijken naar de Unicode-blokken die nu drie bytes vereisen. Over het algemeen zijn dit, zoals al gezegd, Chinese karakters \u2014 maar daar is moeilijk iets mee te doen, ze zijn er 21.000. Daarnaast zijn er ook de hiragana en katakana \u2014 en die zijn er al veel minder, minder dan tweehonderd. En aangezien we de Japanners hebben genoemd \u2014 daar liggen ook emoji's (in werkelijkheid zijn ze overal verspreid in Unicode, maar de belangrijkste blokken zijn in het bereik <code><b>0x1F300<\/b><\/code> \u2013 <code><b>0x1FBFF<\/b><\/code>). Als we denken aan het feit dat er nu emoji's bestaan die uit meerdere codepunten zijn samengesteld (bijvoorbeeld emoji \u200d\u200d\u200d<noindex><a rel=\"nofollow\" href=\"https:\/\/emojipedia.org\/family-woman-woman-girl-boy\/\"><img decoding=\"async\" alt=\"Nog een fiets: we slaan Unicode-strings 30-60% compacter op dan UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/76cd12423b241cc316af80f03be4348f.jpeg\" style=\"display:block;margin: 0 auto;\" \/><\/a><\/noindex> bestaat uit maar liefst 7 codes!), dan is het droevig om voor elk daarvan drie bytes te besteden (7\u00d73 = 21 bytes voor \u00e9\u00e9n teken, dat is echt problematisch).<\/p>\n<p>Dus kiezen we enkele geselecteerde bereiken waar de emoji's, hiragana en katakana overeenkomen, hernummeren we deze in \u00e9\u00e9n continue lijst en coderen we deze in twee bytes in plaats van drie:<\/p>\n<p><code><b>1011xxxx xxxxxxxx<\/b><\/code> <\/p>\n<p>Uitstekend: de eerder genoemde emoji \u200d\u200d\u200d<noindex><a rel=\"nofollow\" href=\"https:\/\/emojipedia.org\/family-woman-woman-girl-boy\/\"><img decoding=\"async\" alt=\"Nog een fiets: we slaan Unicode-strings 30-60% compacter op dan UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/b9900c78dda5d8e596e3751021b4d35d.jpeg\" style=\"display:block;margin: 0 auto;\" \/><\/a><\/noindex>, bestaande uit 7 codepunten, neemt in UTF-8 25 bytes in beslag, maar we hebben deze in gepast <strong>14<\/strong> laten we zeggen twee bytes per codepunt). Trouwens, Habr weigerde het te verwerken (zowel in de oude als in de nieuwe editor), dus ik moest het als afbeelding invoegen.<\/p>\n<p>Laten we nog een probleem proberen op te lossen. Zoals we ons herinneren, is het hoofdalfabet in wezen <strong>de hogere 6 bits<\/strong>, die we in gedachten houden, en plakken aan de code van elk te decoderen symbool. In het geval van Chinese karakters, die zich in het blok bevinden <code><b>0x4E00<\/b><\/code> \u2013 <code><b>0x9FFF<\/b><\/code>, is dit ofwel bit 0 of 1. Dit is niet erg handig: we moeten voortdurend het alfabet tussen deze twee waarden wisselen (dat wil zeggen, drie bytes verbruiken). Maar laten we opmerken dat we in de lange modus het aantal symbolen dat we coderen met behulp van de korte modus van de code kunnen aftrekken (na alle eerder genoemde trucs is dat 10240) \u2014 dan verschuift het bereik van de karakters naar <code><b>0x2600<\/b><\/code> \u2013 <code><b>0x77FF<\/b><\/code>, en in dat geval zijn de hogere 6 bits (uit 21) in dit hele bereik gelijk aan 0. Op deze manier zullen de reeksen karakters twee bytes per karakter gebruiken (wat optimaal is voor zo'n groot bereik), zonder dat er alfabetswitches optreden. <\/p>\n<h2>Alternatieve oplossingen: SCSU, BOCU-1<\/h2>\n<p>\nKenners van Unicode, die alleen de titel van het artikel hebben gelezen, zullen waarschijnlijk snel herinneren dat er binnen de Unicode-standaarden <noindex><a rel=\"nofollow\" href=\"https:\/\/www.unicode.org\/reports\/tr6\/tr6-4.html\">Standard Compression Scheme for Unicode<\/a><\/noindex> (SCSU) is, dat een methode voor codering beschrijft die veel lijkt op de in dit artikel beschreven.<\/p>\n<p>Ik geef eerlijk toe: ik kwam pas diep in het schrijven van mijn oplossing achter het bestaan ervan. Als ik er vanaf het begin van had geweten, zou ik waarschijnlijk geprobeerd hebben een implementatie ervan te schrijven in plaats van mijn eigen benadering te verzinnen.<\/p>\n<p>Het is interessant dat SCSU idee\u00ebn gebruikt die zeer vergelijkbaar zijn met de idee\u00ebn die ik zelf heb ontwikkeld (in plaats van het concept van \u2018alfabetten\u2019 worden \u2018vensters\u2019 gebruikt, en er zijn er meer beschikbaar dan bij mij). Tegelijkertijd heeft dit formaat ook nadelen: het ligt iets dichter bij compressie-algoritmes dan bij codering. In het bijzonder biedt de standaard veel representatiemethoden, maar zegt niet hoe je de optimale moet kiezen \u2014 daarvoor moet de encoder enige heuristieken toepassen. Daarom zal een SCSU-encoder die een goede compressie biedt, complexer en omvangrijker zijn dan mijn algoritme.<\/p>\n<p>Ter vergelijking heb ik een relatief eenvoudige implementatie van SCSU naar JavaScript overgezet \u2014 qua codevolume blijkt het vergelijkbaar te zijn met mijn UTF-C, maar in een aantal gevallen toonde het resultaten die tientallen procenten slechter waren (soms kan het ook beter presteren, maar niet veel). Bijvoorbeeld, teksten in het Hebreeuws en Grieks codeerde UTF-C met maar liefst <strong>60% beter dan SCSU<\/strong> (waarschijnlijk vanwege hun compacte alfabetten).<\/p>\n<p>Daarnaast wil ik toevoegen dat er naast SCSU ook een andere manier van compacte weergave van Unicode bestaat \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Binary_Ordered_Compression_for_Unicode\">BOCU-1<\/a><\/noindex>, maar deze richt zich op compatibiliteit met MIME (wat ik niet nodig had), en gebruikt een iets andere benadering van codering. Ik heb de effici\u00ebntie ervan niet beoordeeld, maar ik denk niet dat deze hoger zal zijn dan die van SCSU.<\/p>\n<h2>Mogelijke aanpassingen<\/h2>\n<p>\nHet algoritme dat ik heb gepresenteerd is niet universeel by design (hierin verschillen mijn doelen waarschijnlijk het meest van die van de Unicode-consortium). Ik heb al vermeld dat het voornamelijk is ontwikkeld voor \u00e9\u00e9n taak (het opslaan van een meertalige woordenlijst in een prefixboom), en sommige van zijn kenmerken zijn misschien niet geschikt voor andere taken. Maar het feit dat het geen standaard is, kan ook een voordeel zijn \u2014 <strong>je kunt het gemakkelijk aanpassen aan je behoeften<\/strong>.<\/p>\n<p>Bijvoorbeeld, je kunt eenvoudigweg het bestaan van status schrappen, de codering stateless maken \u2014 gewoon de variabelen niet bijwerken <code><b>uitschakelen<\/b><\/code>, <code><b>auxOffs<\/b><\/code> en <code><b>is21Bit<\/b><\/code> in de encoder en decoder. In dat geval is het niet mogelijk om reeksen van symbolen uit \u00e9\u00e9n alfabet effici\u00ebnt in te pakken, maar dat garandeert wel dat hetzelfde symbool altijd op dezelfde manier wordt gecodeerd, ongeacht de context.<\/p>\n<p>Daarnaast kan de encoder worden afgestemd op een specifieke taal door de standaardstatus aan te passen \u2014 bijvoorbeeld, gericht op Russische teksten, de instellingen in het begin van de encoder en decoder vaststellen <code><b>offs = 0x0400<\/b><\/code> en <code><b>auxOffs = 0<\/b><\/code>. Dit is vooral zinvol in de stateless modus. In het algemeen zal dit lijken op het gebruik van de oude 8-bits codering, maar het biedt nog steeds de mogelijkheid om symbolen uit de hele Unicode indien nodig in te voegen.<\/p>\n<p>Een ander tekortkoming, eerder genoemd \u2014 in volumineuze tekst gecodeerd in UTF-C is er geen snelle manier om de grens van een symbool te vinden, dichtbij een willekeurig byte. Door de laatste, laten we zeggen, 100 bytes van de gecodeerde buffer af te snijden, loop je het risico onbruikbare data te krijgen waar je niets mee kunt. Voor het opslaan van meergigabyte logs is de codering niet geschikt, maar in het algemeen kan dit worden opgelost. Een byte <code><b>0xBF<\/b><\/code> mag nooit als de eerste byte voorkomen (maar kan als de tweede of derde verschijnen). Daarom kan bij codering een reeks worden ingevoegd <code><b>0xBF 0xBF 0xBF<\/b><\/code> Elke, laten we zeggen, 10 KB \u2014 dan is het bij behoefte om de grens te vinden voldoende om het geselecteerde stuk te scannen totdat een vergelijkbare marker wordt gevonden. Direct achter de laatste <code><b>0xBF<\/b><\/code> zal het begin van het teken zijn. (Bij decodering moet deze reeks van drie bytes natuurlijk worden genegeerd.)<\/p>\n<h2>Samenvattend<\/h2>\n<p>\nAls je tot hier hebt gelezen \u2014 gefeliciteerd! Hopelijk heb je, net als ik, iets nieuws geleerd (of oude kennis opgefrist) over hoe Unicode werkt.<\/p>\n<p><img decoding=\"async\" alt=\"Nog een fiets: we slaan Unicode-strings 30-60% compacter op dan UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/f14b2d815b06a7fda7b77c0a32e7eb75.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Demonstratiepagina. Aan de hand van het Hebreeuws zijn de voordelen ten opzichte van zowel UTF-8 als SCSU zichtbaar.<\/i><\/p>\n<p>Je zou de bovengenoemde overpeinzingen niet moeten beschouwen als een inbreuk op de standaarden. Echter, over het algemeen ben ik tevreden met de resultaten van mijn werkzaamheden, dus ben ik blij om ze <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/deNULL\/utf-c\">te delen<\/a><\/noindex>: bijvoorbeeld, de JS-bibliotheek in geminimaliseerde vorm weegt slechts 1710 bytes (en heeft uiteraard geen afhankelijkheden). Zoals ik eerder noemde, kun je het werken met deze bibliotheek bekijken op <noindex><a rel=\"nofollow\" href=\"https:\/\/denull.github.io\/utf-c\/\">de demo-pagina<\/a><\/noindex> (daar is ook een set teksten waarmee je het kunt vergelijken met UTF-8 en SCSU).<\/p>\n<p>Tot slot wil ik nog eens de aandacht vestigen op de gevallen waarin het gebruik van UTF-C <b>niet aan te raden is<\/b>:<\/p>\n<ul>\n<li>Als je strings lang genoeg zijn (meer dan 100-200 tekens). In dat geval moet je overwegen compressie-algoritmen zoals deflate toe te passen.<\/li>\n<li>Als je nodig hebt <em>ASCII-transparantie<\/em>, dat wil zeggen dat het belangrijk voor je is dat er in de gecodeerde reeksen geen ASCII-codes voorkomen die niet in de originele string zaten. Deze behoefte kan worden vermeden als je bij interactie met externe API's (bijvoorbeeld bij het werken met databases) de resultaat van de codering doorgeeft als een abstracte set bytes, en niet als strings. Anders loop je het risico onverwachte kwetsbaarheden te krijgen.<\/li>\n<li>Als je snel de grenzen van de tekens wilt vinden op een willekeurige offset (bijvoorbeeld, bij beschadiging van een deel van de string). Dit is mogelijk, maar alleen door de string vanaf het begin te scannen (of door de aanpassing toe te passen die in het vorige deel is beschreven).<\/li>\n<li>Als je snel bewerkingen op tekenreeksen wilt uitvoeren (deze sorteren, substring-zoeken, samenvoegen). Hiervoor moeten de tekenreeksen eerst worden gedecodeerd, waardoor UTF-C trager is dan UTF-8 in deze gevallen (maar sneller dan compressie-algoritmen). Aangezien dezelfde tekenreeks altijd op dezelfde manier wordt gecodeerd, vereist een exacte vergelijking van decodering geen, en kan het byte voor byte worden uitgevoerd.<\/li>\n<\/ul>\n<p><b>Update:<\/b> gebruiker <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\">in de reacties hieronder<\/a><\/noindex> heb ik een grafiek gepost die de toepassingsgrens van UTF-C benadrukt. Hieruit blijkt dat UTF-C effectiever is dan algemeen compressie-algoritme (de varianten van LZW) zolang de te comprimeren tekenreeks korter is dan <b>~140 tekens<\/b> (ik wil er wel op wijzen dat de vergelijking op \u00e9\u00e9n tekst is uitgevoerd; voor andere talen kan het resultaat verschillen).<br \/>\n<img decoding=\"async\" alt=\"Nog een fiets: we slaan Unicode-strings 30-60% compacter op dan UTF-8\" src=\"\/wp-content\/uploads\/2020\/10\/0bc9f35ad2757d656801c6466dda09a8.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>Bron: <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.3 - 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\/nl\/blog\/administrirovanie\/eshhyo-odin-velosiped-hranim-yunikodnye-stroki-na-30-60-kompaktnee-chem-utf-8\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.3\" \/>\n\t\t<meta property=\"og:locale\" content=\"nl_NL\" \/>\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\/nl\/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\udd47Nog een fiets: we slaan unicode-strings 30-60% compacter op dan UTF-8 | ProHoster","description":"Als je.","canonical_url":"https:\/\/prohoster.info\/nl\/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":"nl_NL","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\/nl\/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\/nl\/wp-json\/wp\/v2\/posts\/95911","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/comments?post=95911"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/posts\/95911\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/media\/95912"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/media?parent=95911"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/categories?post=95911"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/tags?post=95911"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}