Өөр нэг дугуй: бид Юникод мөрүүдийг UTF-30-аас 60-8% илүү авсаархан хадгалдаг

Өөр нэг дугуй: бид Юникод мөрүүдийг UTF-30-аас 60-8% илүү авсаархан хадгалдаг

Хэрэв та хөгжүүлэгч бол кодчилол сонгох ажилтай тулгарвал Юникод нь бараг үргэлж зөв шийдэл байх болно. Тодорхой дүрслэх арга нь контекстээс хамаардаг боловч ихэнхдээ энд бүх нийтийн хариулт байдаг - UTF-8. Үүний сайн тал нь бүх Юникод тэмдэгтүүдийг зарцуулахгүйгээр ашиглах боломжийг олгодог бас ихэнх тохиолдолд маш олон байт байдаг. Латин цагаан толгойноос илүү ашигладаг хэлнүүдийн хувьд "хэт их биш" гэдэг нь үнэн юм. тэмдэгт бүрт хоёр байт. Биднийг ердөө 256 тэмдэгтээр хязгаарласан балар эртний кодчилол руу буцахгүйгээр илүү сайн хийж чадах уу?

Доор би энэ асуултад хариулах оролдлоготой танилцаж, UTF-8-д байгаа илүүдлийг нэмэхгүйгээр дэлхийн ихэнх хэл дээр мөрүүдийг хадгалах боломжийг олгодог харьцангуй энгийн алгоритмыг хэрэгжүүлэхийг санал болгож байна.

Хариуцлага. Би нэн даруй хэд хэдэн чухал захиалга хийх болно: тайлбарласан шийдлийг UTF-8-ийн бүх нийтийн орлуулах хэлбэрээр санал болгодоггүй, энэ нь зөвхөн тохиолдлын нарийхан жагсаалтад тохиромжтой (доорх талаар дэлгэрэнгүй) бөгөөд ямар ч тохиолдолд гуравдагч талын API-уудтай (энэ талаар мэдэхгүй хүмүүс) харилцахад ашиглах ёсгүй. Ихэнх тохиолдолд ерөнхий зориулалтын шахалтын алгоритмууд (жишээ нь, deflate) нь их хэмжээний текст өгөгдлийг авсаархан хадгалахад тохиромжтой байдаг. Нэмж дурдахад, шийдлээ бүтээх явцад би Юникод дотроос одоо байгаа стандартыг олсон бөгөөд энэ нь ижил асуудлыг шийддэг - энэ нь арай илүү төвөгтэй (мөн ихэнхдээ улам дорддог) боловч энэ нь хүлээн зөвшөөрөгдсөн стандарт бөгөөд зүгээр л тавихгүй. өвдөг дээрээ хамтдаа. Би ч бас түүний тухай танд хэлье.

Юникод болон UTF-8-ийн тухай

Эхлэхийн тулд энэ нь юу болох талаар хэдэн үг хэлье Юникод и UTF-8.

Та бүхний мэдэж байгаагаар 8 битийн кодчилол түгээмэл байсан. Тэдгээрийн тусламжтайгаар бүх зүйл энгийн байсан: 256 тэмдэгтийг 0-ээс 255 хүртэлх тоогоор дугаарлаж, 0-ээс 255 хүртэлх тоог нэг байтаар илэрхийлэх боломжтой. Хэрэв бид хамгийн эхэнд буцаж очвол ASCII кодчилол нь 7 битээр бүрэн хязгаарлагддаг тул түүний байт дүрслэл дэх хамгийн чухал бит нь тэг бөгөөд ихэнх 8 битийн кодчилолууд нь түүнтэй нийцдэг (тэдгээр нь зөвхөн "дээд" хэсэгт ялгаатай байдаг. хэсэг, хамгийн чухал бит нь нэг ).

Юникод нь эдгээр кодчилолуудаас юугаараа ялгаатай вэ, яагаад үүнтэй холбоотой маш олон тусгай дүрслэл байдаг - UTF-8, UTF-16 (BE ба LE), UTF-32? Үүнийг дарааллаар нь цэгцэлье.

Юникод үндсэн стандарт нь зөвхөн тэмдэгтүүд (зарим тохиолдолд тэмдэгтүүдийн бие даасан бүрэлдэхүүн хэсэг) болон тэдгээрийн тоо хоорондын захидал харилцааг тодорхойлдог. Мөн энэ стандартад маш олон боломжит тоо байдаг - эхлэн 0x00 нь 0x10FFFF (1 ширхэг). Хэрэв бид ийм муж дахь тоог хувьсагч болгон оруулахыг хүсвэл 114 ба 112 байт ч хангалттай биш байх болно. Манай процессорууд гурван байт тоонуудтай ажиллахад тийм ч их зориулагдаагүй тул бид тэмдэгт бүрт 1 байт ашиглахаас өөр аргагүй болно! Энэ бол UTF-2, гэхдээ яг ийм "үрэлгэн байдлаас" болж энэ формат түгээмэл биш юм.

Аз болоход Юникод доторх тэмдэгтүүдийн дараалал санамсаргүй биш юм. Тэдний бүх багц нь 17 "д хуваагддаг.онгоцууд", тус бүр нь 65536 (0x10000) «.кодын цэгүүд" Энд байгаа "кодын цэг" гэсэн ойлголт нь энгийн зүйл юм тэмдэгтийн дугаар, түүнд Юникодоор хуваарилагдсан. Гэхдээ дээр дурьдсанчлан Юникод дээр зөвхөн бие даасан тэмдэгтүүдийг дугаарлаад зогсохгүй тэдгээрийн бүрэлдэхүүн хэсэг, үйлчилгээний тэмдэг (мөн заримдаа энэ тоонд юу ч тохирохгүй байна - магадгүй одоохондоо, гэхдээ бидний хувьд энэ нь тийм ч чухал биш юм), тиймээс Тэмдэгтийн бус харин тоонуудын өөрсдийнхөө талаар тусгайлан ярих нь илүү зөв юм. Гэсэн хэдий ч, товчхон байх үүднээс би "код цэг" гэсэн нэр томъёог агуулсан "тэмдэг" гэдэг үгийг ихэвчлэн ашиглах болно.

Өөр нэг дугуй: бид Юникод мөрүүдийг UTF-30-аас 60-8% илүү авсаархан хадгалдаг
Юникод онгоцууд. Таны харж байгаагаар ихэнх нь (4-ээс 13-р онгоц) ашиглагдаагүй хэвээр байна.

Хамгийн гайхалтай нь бүх гол "целлюлоз" нь тэг хавтгайд оршдог бөгөөд үүнийг " гэж нэрлэдэг.Олон хэлний үндсэн хавтгай". Хэрэв мөрөнд орчин үеийн аль нэг хэлээр (хятад хэлээр) текст байгаа бол та энэ хавтгайгаас цааш явахгүй. Гэхдээ та Юникод үлдсэн хэсгийг нь таслах боломжгүй - жишээлбэл, эможи нь ихэвчлэн төгсгөлд байрладаг. дараагийн онгоц"Нэмэлт олон хэлний хавтгай"(энэ нь 0x10000 нь 0x1FFFF). Тиймээс UTF-16 үүнийг хийдэг: дотор нь орсон бүх тэмдэгтүүд Олон хэлний үндсэн хавтгай, харгалзах хоёр байт тоогоор "байгаагаар" кодлогдсон байна. Гэсэн хэдий ч, энэ муж дахь зарим тоонууд нь тодорхой тэмдэгтүүдийг огт заагаагүй боловч энэ хос байтны дараа өөр нэгийг авч үзэх шаардлагатай байгааг харуулж байна - эдгээр дөрвөн байтын утгыг нэгтгэснээр бид байтыг хамарсан тоог авах болно. бүх хүчинтэй Юникод муж. Энэ санааг "орлогч хосууд" гэж нэрлэдэг - та тэдний талаар сонссон байх.

Тиймээс UTF-16 нь "кодын цэг" тутамд хоёр буюу (маш ховор тохиолдолд) дөрвөн байт шаарддаг. Энэ нь дөрвөн байтыг байнга ашиглахаас илүү сайн боловч латин хэл (болон бусад ASCII тэмдэгтүүд) ингэж кодлогдсон үед зайны тал хувийг тэг дээр үрдэг. UTF-8 нь үүнийг засах зорилготой: ASCII нь өмнөх шигээ зөвхөн нэг байт эзэлдэг; кодууд 0x80 нь 0x7FF - хоёр байт; -аас 0x800 нь 0xFFFF - гурав, түүнээс 0x10000 нь 0x10FFFF - дөрөв. Нэг талаас, латин цагаан толгой сайн болсон: ASCII-тэй нийцтэй байдал буцаж, тархалт 1-ээс 4 байт хүртэл жигд "тархаж" байна. Гэвч харамсалтай нь латин үсгээс бусад цагаан толгой нь UTF-16-тай харьцуулахад ямар ч ашиггүй бөгөөд одоо олонх нь хоёр биш гурван байт шаарддаг - хоёр байт бичлэгийн хамрах хүрээ 32 дахин багассан байна. 0xFFFF нь 0x7FF, үүнд Хятад ч, жишээлбэл, Гүрж хэл ч ороогүй болно. Кирилл болон бусад таван цагаан толгой - hurray - азтай, тэмдэгт бүрт 2 байт.

Яагаад ийм зүйл болдог вэ? UTF-8 тэмдэгтийн кодыг хэрхэн илэрхийлж байгааг харцгаая:
Өөр нэг дугуй: бид Юникод мөрүүдийг UTF-30-аас 60-8% илүү авсаархан хадгалдаг
Тоонуудыг шууд илэрхийлэхийн тулд энд тэмдэглэгдсэн битүүдийг ашигладаг x. Хоёр байт бичлэгт ердөө 11 ийм бит (16-аас) байгааг харж болно. Энд байгаа тэргүүлэх битүүд нь зөвхөн туслах функцтэй. Дөрвөн байт бичлэгийн хувьд 21 битийн 32-ийг кодын цэгийн дугаарт хуваарилдаг - энэ нь гурван байт (нийт 24 бит өгдөг) хангалттай мэт санагдаж болох ч үйлчилгээний тэмдэглэгээ хэт их хэмжээгээр иддэг.

Энэ муу юу? Үнэхээр биш. Нэг талаас, хэрэв бид орон зайд маш их анхаарал хандуулдаг бол бүх нэмэлт энтропи, илүүдлийг хялбархан арилгах шахах алгоритмуудтай. Нөгөөтэйгүүр, Юникодын зорилго нь хамгийн түгээмэл кодчиллыг хангах явдал байв. Жишээлбэл, бид UTF-8-д кодлогдсон мөрийг өмнө нь зөвхөн ASCII дээр ажиллаж байсан код руу даатгаж болох бөгөөд энэ нь үнэндээ байхгүй ASCII мужаас тэмдэгтийг харах болно гэж айх хэрэггүй (эцсийн эцэст UTF-8-д бүгдээрээ). тэг битээс эхэлдэг байт - энэ нь яг ASCII юм). Хэрэв бид гэнэт том утаснаас жижиг сүүлийг эхнээс нь тайлахгүйгээр таслахыг хүсч байвал (эсвэл гэмтсэн хэсгийн дараа мэдээллийн зарим хэсгийг сэргээх) тэмдэгтийн эхлэлийг олоход хялбар байдаг (энэ нь хангалттай. битийн угтвартай байтыг алгасах 10).

Тэгвэл яагаад шинэ зүйл зохион бүтээх вэ?

Үүний зэрэгцээ, deflate гэх мэт шахалтын алгоритмуудыг ашиглах боломжгүй тохиолдол байдаг, гэхдээ та мөрүүдийг авсаархан хадгалахыг хүсдэг. Би хувьдаа барилга барих талаар бодож байхдаа ийм асуудалтай тулгарсан шахсан угтвар мод дурын хэл дээрх үгсийг багтаасан том толь бичгийн хувьд. Нэг талаараа үг бүр нь маш богино байдаг тул шахах нь үр дүнгүй болно. Нөгөөтэйгүүр, миний авч үзсэн модны хэрэгжилт нь хадгалагдсан мөрийн байт бүр нь тусдаа модны орой үүсгэдэг тул тэдгээрийн тоог багасгах нь маш хэрэгтэй байсан. Миний номын санд Az.js (Шиг пиморфи 2, үүн дээр үндэслэсэн) ижил төстэй асуудлыг энгийн байдлаар шийдэж болно - strings into packed DAWG-толь бичиг, тэнд хадгалагдсан хуучин сайн CP1251. Гэхдээ ойлгоход хялбар тул энэ нь зөвхөн хязгаарлагдмал цагаан толгойн хувьд сайн ажилладаг - Хятад хэл дээрх мөрийг ийм толь бичигт нэмж оруулах боломжгүй.

Ийм өгөгдлийн бүтцэд UTF-8-ийг ашиглах үед гарч ирдэг өөр нэг таагүй мэдрэмжийг би тусад нь тэмдэглэхийг хүсч байна. Дээрх зурган дээр тэмдэгтийг хоёр байт хэлбэрээр бичихэд түүний дугаартай холбоотой битүүд дараалан ирдэггүй, харин хос битээр тусгаарлагддаг болохыг харуулж байна. 10 дунд нь: 110xxxxx 10xxxxxx. Үүнээс болж хоёр дахь байтын доод 6 бит тэмдэгтийн кодонд халихад (өөрөөр хэлбэл шилжилт үүсдэг) 1011111110000000), дараа нь эхний байт бас өөрчлөгдөнө. "p" үсэг нь байтаар тэмдэглэгдсэн байдаг 0xD0 0xBF, дараагийн "r" аль хэдийн байна 0xD1 0x80. Угтвар модны хувьд энэ нь эх зангилааг хоёр болгон хуваахад хүргэдэг - нэг нь угтварын хувьд 0xD0, өөр нэг нь 0xD1 (хэдийгээр кирилл үсгийг бүхэлд нь зөвхөн хоёр дахь байтаар кодлох боломжтой).

Би юу авсан юм

Энэ асуудалтай тулгараад би битүүдтэй тоглоом тоглох дадлага хийхээр шийдсэн бөгөөд үүний зэрэгцээ Юникод-ийн бүтцийг бүхэлд нь бага зэрэг сайн мэддэг болсон. Үүний үр дүнд UTF-C кодчилолын формат ("C" нь компакт), кодын цэг бүрт 3-аас илүүгүй байт зарцуулдаг бөгөөд ихэвчлэн зөвхөн зарцуулах боломжийг олгодог бүхэл кодлогдсон мөрөнд нэг нэмэлт байт. Энэ нь ASCII бус олон цагаан толгойн үсгүүдэд ийм кодчилол гарч ирэхэд хүргэдэг UTF-30-аас 60-8% илүү авсаархан.

Би маягт дээр кодчилол, декодчилох алгоритмыг хэрэгжүүлэх жишээг үзүүлэв JavaScript болон Go номын сангууд, та тэдгээрийг кодоо чөлөөтэй ашиглаж болно. Гэхдээ би энэ формат нь тодорхой утгаараа "унадаг дугуй" хэвээр байгааг онцлон тэмдэглэх болно, би үүнийг ашиглахыг зөвлөдөггүй. чамд яагаад хэрэгтэй байгааг ойлгохгүйгээр. Энэ нь "UTF-8-ийн сайжруулалт" гэхээсээ илүү туршилт хэвээр байна. Гэсэн хэдий ч тэнд байгаа кодыг маш олон тооны тайлбар, тестийн хамрах хүрээг хамарсан, цэвэрхэн, товчхон бичсэн болно.

Өөр нэг дугуй: бид Юникод мөрүүдийг UTF-30-аас 60-8% илүү авсаархан хадгалдаг
Туршилтын үр дүн ба UTF-8-тай харьцуулалт

Би ч бас тэгсэн Демо хуудас, та алгоритмын гүйцэтгэлийг үнэлэх боломжтой бөгөөд дараа нь би түүний зарчим, хөгжлийн үйл явцын талаар илүү ихийг хэлэх болно.

Илүүдэл битүүдийг арилгах

Мэдээжийн хэрэг би UTF-8-ийг үндэс болгон авсан. Үүнийг өөрчилж болох хамгийн эхний бөгөөд хамгийн тодорхой зүйл бол байт бүрийн үйлчилгээний битийн тоог багасгах явдал юм. Жишээлбэл, UTF-8 дахь эхний байт нь үргэлж аль нэгээр нь эхэлдэг 0, эсвэл хамт 11 - угтвар 10 Зөвхөн дараах байтуудад л байна. Угтварыг орлъё 11 тухай 1, мөн дараагийн байтуудын хувьд бид угтваруудыг бүрэн устгах болно. Юу тохиолдох вэ?

0xxxxxxx - 1 байт
10xxxxxx xxxxxxxx - 2 байт
110xxxxx xxxxxxxx xxxxxxxx - 3 байт

Хүлээгээрэй, дөрвөн байт бичлэг хаана байна? Гэхдээ энэ нь шаардлагагүй болсон - гурван байтаар бичихэд бид одоо 21 бит байгаа бөгөөд энэ нь бүх тоонд хангалттай. 0x10FFFF.

Бид энд юуг золиосолсон бэ? Хамгийн чухал зүйл бол буфер дахь дурын байршлаас тэмдэгтийн хил хязгаарыг илрүүлэх явдал юм. Бид дурын байтыг зааж, түүнээс дараагийн тэмдэгтийн эхлэлийг олох боломжгүй. Энэ нь бидний форматын хязгаарлалт боловч бодит байдал дээр энэ нь ховор тохиолддог. Бид ихэвчлэн буферийг эхнээс нь давж чаддаг (ялангуяа богино шугамын хувьд).

Хэлүүдийг 2 байтаар хамрах нөхцөл байдал сайжирсан: одоо хоёр байт формат нь 14 битийн хүрээг өгдөг бөгөөд эдгээр нь кодууд юм. 0x3FFF. Хятадууд азгүй хүмүүс (тэдний дүрүүд нь ихэвчлэн өөр өөр байдаг 0x4E00 нь 0x9FFF), гэхдээ Гүржүүд болон бусад олон ард түмэн илүү хөгжилтэй байдаг - тэдний хэл нь тэмдэгт бүрт 2 байт багтдаг.

Кодлогчийн төлөвийг оруулна уу

Одоо шугамын шинж чанаруудын талаар бодож үзье. Толь бичиг нь ихэвчлэн ижил цагаан толгойн үсгээр бичигдсэн үгсийг агуулдаг бөгөөд энэ нь бусад олон бичвэрүүдэд ч хамаатай. Энэ цагаан толгойн үсгийг нэг удаа зааж өгөөд зөвхөн доторх үсгийн тоог л зааж өгөх нь зүйтэй болов уу. Юникод хүснэгтийн тэмдэгтүүдийн зохион байгуулалт нь бидэнд туслах эсэхийг харцгаая.

Дээр дурдсанчлан Юникод нь хуваагддаг онгоц Тус бүр нь 65536 кодтой. Гэхдээ энэ нь тийм ч ашигтай хэлтэс биш юм (аль хэдийн хэлсэнчлэн бид ихэнхдээ тэг хавтгайд байдаг). Илүү сонирхолтой зүйл бол хуваах явдал юм блокууд. Эдгээр мужууд нь тогтмол урттай байхаа больсон бөгөөд илүү утга учиртай байдаг - дүрмээр бол тус бүр нь ижил цагаан толгойн тэмдэгтүүдийг нэгтгэдэг.

Өөр нэг дугуй: бид Юникод мөрүүдийг UTF-30-аас 60-8% илүү авсаархан хадгалдаг
Бенгал цагаан толгойн тэмдэгтүүдийг агуулсан блок. Харамсалтай нь түүхэн шалтгааны улмаас энэ нь тийм ч нягт биш савлагааны жишээ юм - 96 тэмдэгт нь 128 блок кодын цэгүүдэд эмх замбараагүй тархсан байна.

Блокуудын эхлэл ба тэдгээрийн хэмжээ нь үргэлж 16-ын үржвэртэй байдаг - үүнийг хялбар болгохын тулд хийдэг. Нэмж дурдахад, олон блокууд нь 128 эсвэл бүр 256-ын үржвэрийн утгаар эхэлж, төгсдөг - жишээлбэл, үндсэн кирилл үсэг нь 256 байт эзэлдэг. 0x0400 нь 0x04FF. Энэ нь маш тохиромжтой: хэрэв бид угтварыг нэг удаа хадгалвал 0x04, дараа нь ямар ч кирилл үсгийг нэг байт дотор бичиж болно. Үнэн бол бид ASCII (болон бусад дүрүүд) руу буцах боломжоо алдах болно. Тиймээс бид үүнийг хийдэг:

  1. Хоёр байт 10yyyyyy yxxxxxxx зөвхөн тоогоор тэмдэг тэмдэглээд зогсохгүй yyyyyy yxxxxxxx, гэхдээ бас өөрчлөгддөг одоогийн цагаан толгой тухай yyyyyy y0000000 (өөрөөр хэлбэл бид хамгийн бага ач холбогдолтой зүйлээс бусад бүх битийг санаж байна 7 хуудас);
  2. Нэг байт 0xxxxxxx Энэ бол одоогийн цагаан толгойн дүр юм. Үүнийг 1-р алхам дээр бидний санаж байсан офсет дээр нэмэх хэрэгтэй. Бид цагаан толгойн үсгийг өөрчлөөгүй ч офсет нь тэг тул ASCII-тэй нийцтэй хэвээр байна.

Мөн 3 байт шаардлагатай кодуудын хувьд:

  1. Гурван байт 110yyyyy yxxxxxxx xxxxxxxx тоо бүхий тэмдгийг заана yyyyyy yxxxxxxx xxxxxxxx, өөрчлөх одоогийн цагаан толгой тухай yyyyyy y0000000 00000000 (Залуугаасаа бусад бүх зүйлийг санав 15 хуудас), бид одоо байгаа нүдийг шалгана уу урт горим (цагаан толгойг хоёр байт болгож өөрчлөх үед бид энэ тугийг дахин тохируулах болно);
  2. Хоёр байт 0xxxxxxx xxxxxxxx урт горимд энэ нь одоогийн цагаан толгойн тэмдэгт юм. Үүний нэгэн адил бид үүнийг 1-р алхамаас офсетээр нэмнэ. Цорын ганц ялгаа нь одоо бид хоёр байт уншдаг (учир нь бид энэ горимд шилжсэн).

Сайхан сонсогдож байна: одоо бид ижил 7 битийн Юникод муж дахь тэмдэгтүүдийг кодлох шаардлагатай бол эхэнд 1 нэмэлт байт, тэмдэгт бүрт нийт нэг байт зарцуулдаг.

Өөр нэг дугуй: бид Юникод мөрүүдийг UTF-30-аас 60-8% илүү авсаархан хадгалдаг
Өмнөх хувилбаруудын аль нэгээр нь ажиллаж байна. Энэ нь UTF-8-ийг ихэвчлэн давдаг боловч сайжруулах зүйл байсаар байна.

Юу нь илүү муу вэ? Нэгдүгээрт, бидэнд нэг болзол бий одоогийн цагаан толгойн офсет болон шалгах хайрцаг урт горим. Энэ нь биднийг улам бүр хязгаарлаж байна: одоо ижил тэмдэгтүүдийг өөр өөр нөхцөл байдалд өөр өөрөөр кодлох боломжтой. Жишээлбэл, дэд мөрүүдийг хайхдаа зөвхөн байтыг харьцуулах замаар бус үүнийг харгалзан үзэх шаардлагатай болно. Хоёрдугаарт, цагаан толгойгоо өөрчилсөн даруйдаа ASCII тэмдэгтүүдийн кодчилол муу болсон (энэ нь зөвхөн латин цагаан толгой төдийгүй үндсэн цэг таслал, түүний дотор хоосон зай) - тэд цагаан толгойг дахин 0 болгон өөрчлөхийг шаарддаг, өөрөөр хэлбэл, дахин нэмэлт байт (дараа нь бидний гол зүйл рүү буцах өөр нэг).

Нэг цагаан толгой сайн, хоёр нь илүү

Дээр дурдсан гурваас нэгийг нь шахаж бит угтваруудаа бага зэрэг өөрчлөхийг хичээцгээе.

0xxxxxxx — Энгийн горимд 1 байт, урт горимд 2 байт
11xxxxxx - 1 байт
100xxxxx xxxxxxxx - 2 байт
101xxxxx xxxxxxxx xxxxxxxx - 3 байт

Өөр нэг дугуй: бид Юникод мөрүүдийг UTF-30-аас 60-8% илүү авсаархан хадгалдаг

Одоо хоёр байт бичлэгт нэг бага бит байна - код хүртэл оноо 0x1FFF, үгүй ​​ээ 0x3FFF. Гэсэн хэдий ч, энэ нь хоёр байт UTF-8 кодуудаас мэдэгдэхүйц том хэвээр байгаа бөгөөд ихэнх нийтлэг хэлүүд нийцсэн хэвээр байгаа бөгөөд хамгийн мэдэгдэхүйц алдагдал нь буурсан байна. хирагана и катакана, Япончууд гунигтай байна.

Энэ шинэ код юу вэ? 11xxxxxx? Энэ бол 64 тэмдэгтээс бүрдсэн жижиг "сташ" бөгөөд энэ нь бидний үндсэн цагаан толгойн үсгийг нөхдөг тул би үүнийг туслах гэж нэрлэсэн (туслах) цагаан толгой. Бид одоогийн цагаан толгойг солиход хуучин цагаан толгойн нэг хэсэг нь туслах болно. Жишээлбэл, бид ASCII-ээс кирилл үсэг рүү шилжсэн - одоо хадгалалт нь 64 тэмдэгт агуулсан байна. Латин цагаан толгой, тоо, зай, таслал (ASCII бус бичвэрүүдэд хамгийн олон удаа оруулдаг). ASCII руу буцаж шилжвэл кирилл цагаан толгойн гол хэсэг нь туслах цагаан толгой болно.

Хоёр цагаан толгойн үсгийн ачаар бид цагаан толгойн үсгийг солиход хамгийн бага зардалтай олон тооны текстийг боловсруулах боломжтой (цэг таслал нь ихэвчлэн ASCII руу буцахад хүргэдэг, гэхдээ үүний дараа бид нэмэлт цагаан толгойноос ASCII бус олон тэмдэгтүүдийг авах болно. дахин солих).

Шагнал: дэд цагаан толгойн угтвар 11xxxxxx болон түүний анхны офсетийг сонгох 0xC0, бид CP1252-тэй хэсэгчлэн нийцдэг. Өөрөөр хэлбэл, CP1252-д кодлогдсон Баруун Европын олон (гэхдээ бүгд биш) текстүүд UTF-C дээр адилхан харагдах болно.

Гэсэн хэдий ч энд нэг бэрхшээл гарч ирдэг: үндсэн цагаан толгойноос туслах үсгийг хэрхэн авах вэ? Та ижил офсетийг үлдээж болно, гэхдээ харамсалтай нь энд Юникод бүтэц аль хэдийн бидний эсрэг тоглож байна. Ихэнхдээ цагаан толгойн гол хэсэг нь блокийн эхэнд байдаггүй (жишээлбэл, Оросын нийслэл "А" кодтой байдаг. 0x0410, хэдийгээр кирилл блок нь эхэлдэг 0x0400). Тиймээс эхний 64 тэмдэгтийг хадгалсан газарт оруулснаар бид цагаан толгойн сүүл хэсэгт нэвтрэх эрхээ алдаж магадгүй юм.

Энэ асуудлыг засахын тулд би өөр өөр хэлтэй харгалзах зарим блокуудыг гараар дамжуулж, үндсэн цагаан толгойн доторх туслах цагаан толгойн офсетийг зааж өгсөн. Латин цагаан толгойн үсгийг үл хамаарах зүйл болгон ерөнхийдөө base64 шиг шинэчилсэн.

Өөр нэг дугуй: бид Юникод мөрүүдийг UTF-30-аас 60-8% илүү авсаархан хадгалдаг

Эцсийн мэдрэгчүүд

Эцэст нь өөр хаана ямар нэг зүйлийг сайжруулах талаар бодож үзье.

Формат гэдгийг анхаарна уу 101xxxxx xxxxxxxx xxxxxxxx хүртэлх тоог кодлох боломжийг танд олгоно 0x1FFFFF, мөн Юникод өмнө нь дуусна, цагт 0x10FFFF. Өөрөөр хэлбэл, сүүлийн кодын цэгийг дүрслэх болно 10110000 11111111 11111111. Тиймээс, хэрэв эхний байт нь хэлбэртэй байвал бид хэлж болно 1011xxxx (Хаана xxxx 0-ээс их) бол энэ нь өөр зүйл гэсэн үг юм. Жишээлбэл, та тэнд нэг байтаар кодлох боломжтой өөр 15 тэмдэгт нэмж болно, гэхдээ би үүнийг өөрөөр хийхээр шийдсэн.

Одоо гурван байт шаарддаг юникод блокуудыг харцгаая. Үндсэндээ аль хэдийн дурьдсанчлан эдгээр нь хятад тэмдэгтүүд боловч тэдэнтэй юу ч хийхэд хэцүү байдаг, тэдгээрийн 21 мянга нь байдаг. Гэхдээ хирагана, катакана нар бас тэнд ниссэн - одоо тийм ч олон биш, хоёр зуу хүрэхгүй. Япончуудыг санаж байгаа болохоор эможи бас байдаг (үнэндээ тэд Юникод дээр олон газар тараагдсан байдаг, гэхдээ гол блокууд нь хүрээн дотор байдаг. 0x1F300 - 0x1FBFF). Хэрэв та одоо хэд хэдэн кодын цэгээс нэгэн зэрэг цуглуулсан эможи байдаг гэж бодож байгаа бол (жишээлбэл, эможи ‍‍‍)Өөр нэг дугуй: бид Юникод мөрүүдийг UTF-30-аас 60-8% илүү авсаархан хадгалдаг 7 кодоос бүрддэг!), Дараа нь тус бүр дээр гурван байт зарцуулах нь үнэхээр ичмээр юм (нэг дүрсний төлөө 7×3 = 21 байт, хар дарсан зүүд).

Тиймээс бид эможи, хирагана, катакана-д тохирох хэд хэдэн сонгосон мужийг сонгон, тэдгээрийг нэг тасралтгүй жагсаалтад дахин дугаарлаж, гурвын оронд хоёр байт болгон кодлодог.

1011xxxx xxxxxxxx

Гайхалтай: дээр дурдсан эможиӨөр нэг дугуй: бид Юникод мөрүүдийг UTF-30-аас 60-8% илүү авсаархан хадгалдаг, 7 кодын цэгээс бүрдэх, UTF-8-д 25 байт багтаамжтай бөгөөд бид үүнийг тохируулна 14 (кодын цэг бүрт яг хоёр байт). Дашрамд хэлэхэд, Хабр үүнийг (хуучин болон шинэ редакторын аль алинд нь) шингээхээс татгалзсан тул би үүнийг зурагтай хамт оруулах шаардлагатай болсон.

Дахиад нэг асуудлыг засах гэж оролдъё. Бидний санаж байгаагаар үндсэн цагаан толгой нь үндсэндээ байдаг өндөр 6 бит, бид үүнийг санаж, дараагийн кодыг тайлсан тэмдэг бүрийн кодыг наа. Блок дотор байгаа хятад тэмдэгтүүдийн хувьд 0x4E00 - 0x9FFF, энэ нь бит 0 эсвэл 1 байна. Энэ нь тийм ч тохиромжтой биш юм: бид эдгээр хоёр утгын хооронд цагаан толгойн үсгийг байнга сольж байх шаардлагатай (өөрөөр хэлбэл гурван байт зарцуулдаг). Гэхдээ урт горимд кодноос бид богино горимыг ашиглан кодчилдог тэмдэгтүүдийн тоог хасаж болно гэдгийг анхаарна уу (дээр тайлбарласан бүх заль мэхийн дараа энэ нь 10240 байна) - дараа нь иероглифийн хүрээ шилжих болно. 0x2600 - 0x77FF, ба энэ тохиолдолд, энэ бүх мужид хамгийн чухал 6 бит (21-ээс) 0-тэй тэнцүү байх болно. Тиймээс иероглифийн дараалалд нэг иероглифт хоёр байт (энэ нь ийм том мужид хамгийн тохиромжтой) ашиглах болно. цагаан толгойн шилжилтийг үүсгэдэг.

Альтернатив шийдлүүд: SCSU, BOCU-1

Юникод мэргэжилтнүүд нийтлэлийн гарчгийг дөнгөж уншаад Юникод стандартуудын дунд шууд байдаг гэдгийг сануулах гэж яарах байх. Юникод зориулсан стандарт шахалтын схем (SCSU), энэ нь нийтлэлд тайлбарласантай маш төстэй кодчилолын аргыг тайлбарладаг.

Би чин сэтгэлээсээ хүлээн зөвшөөрч байна: Би шийдвэрээ бичихдээ гүн гүнзгий автсаны дараа л түүний оршин тогтнохыг мэдсэн. Хэрэв би энэ талаар анхнаасаа мэдсэн бол өөрийн гэсэн арга барилыг гаргахын оронд хэрэгжүүлэлт бичихийг оролдох байсан байх.

Сонирхолтой нь SCSU миний бие даан гаргаж ирсэн санаатай тун төстэй санааг ашигладаг ("цагаан толгой" гэсэн ойлголтын оронд "цонх" ашигладаг бөгөөд надаас илүү олон санаанууд байдаг). Үүний зэрэгцээ энэ формат нь сул талуудтай: энэ нь кодлохоос илүү шахалтын алгоритмд арай ойр байдаг. Ялангуяа стандарт нь дүрслэх олон аргыг өгдөг боловч оновчтойг нь хэрхэн сонгохыг заагаагүй байдаг - үүний тулд кодлогч нь зарим төрлийн эвристикийг ашиглах ёстой. Тиймээс, сайн савлагаа үйлдвэрлэдэг SCSU кодлогч нь миний алгоритмаас илүү төвөгтэй, илүү төвөгтэй байх болно.

Харьцуулахын тулд би SCSU-ийн харьцангуй энгийн хэрэгжилтийг JavaScript руу шилжүүлсэн - кодын эзлэхүүний хувьд энэ нь миний UTF-C-тэй харьцуулж болохуйц байсан боловч зарим тохиолдолд үр дүн нь хэдэн арван хувиар муу байсан (заримдаа энэ нь үүнээс давж магадгүй, гэхдээ тийм ч их биш). Жишээлбэл, еврей, грек хэл дээрх бичвэрүүдийг UTF-C кодчилсон SCSU-аас 60% илүү (Магадгүй тэдний авсаархан цагаан толгойн үсгийн улмаас).

Тус тусад нь би SCSU-аас гадна Юникодыг авсаархан илэрхийлэх өөр нэг арга бий гэдгийг нэмж хэлье. BOCU-1, гэхдээ энэ нь MIME нийцтэй байх зорилготой (энэ нь надад хэрэггүй байсан) бөгөөд кодчилолд арай өөр хандлагыг ашигладаг. Би түүний үр нөлөөг үнэлээгүй ч энэ нь SCSU-ээс өндөр байх магадлал багатай юм шиг санагдаж байна.

Боломжит сайжруулалтууд

Миний танилцуулсан алгоритм нь дизайны хувьд түгээмэл биш (энэ нь миний зорилго Юникод консорциумын зорилгоос хамгийн их ялгаатай байх магадлалтай). Энэ нь үндсэндээ нэг даалгаварт зориулагдсан (олон хэл дээрх толь бичгийг угтвар модонд хадгалах) гэж би аль хэдийн дурдсан бөгөөд түүний зарим онцлог нь бусад даалгаварт тохиромжгүй байж магадгүй юм. Гэхдээ энэ нь стандарт биш байгаа нь давуу тал болно - та өөрийн хэрэгцээнд нийцүүлэн хялбархан өөрчилж болно.

Жишээлбэл, тодорхой байдлаар та төлөв байдлаас ангижрах, харьяалалгүй кодчилол хийх боломжтой - хувьсагчдыг бүү шинэчил. offs, auxOffs и is21Bit кодлогч болон декодер дотор. Энэ тохиолдолд ижил цагаан толгойн тэмдэгтүүдийн дарааллыг үр дүнтэй багцлах боломжгүй боловч контекстээс үл хамааран ижил тэмдэгтийг үргэлж ижил байтаар кодлох баталгаа байх болно.

Нэмж дурдахад, та анхдагч төлөвийг өөрчлөх замаар кодлогчийг тодорхой хэлээр тохируулах боломжтой - жишээлбэл, орос текст дээр анхаарлаа төвлөрүүлж, кодлогч болон декодлогчийг эхэнд нь тохируулах боломжтой. offs = 0x0400 и auxOffs = 0. Энэ нь ялангуяа харьяалалгүй горимын хувьд утга учиртай. Ерөнхийдөө энэ нь хуучин найман битийн кодчилолыг ашиглахтай төстэй боловч шаардлагатай бол бүх Юникод тэмдэгт оруулах боломжийг хасахгүйгээр хийх болно.

Өмнө дурьдсан өөр нэг сул тал бол UTF-C-д кодлогдсон том бичвэрт дурын байттай хамгийн ойр байгаа тэмдэгтийн заагийг хурдан олох арга байхгүй. Хэрэв та кодлогдсон буферээс сүүлийн 100 байтыг таслах юм бол юу ч хийж чадахгүй хог хаягдал авах эрсдэлтэй. Кодчилол нь олон гигабайт бүртгэлийг хадгалахад зориулагдаагүй боловч ерөнхийдөө үүнийг засах боломжтой. Байт 0xBF хэзээ ч эхний байт байдлаар гарч ирэх ёсгүй (гэхдээ хоёр, гурав дахь байж болно). Тиймээс, кодлохдоо дарааллыг оруулж болно 0xBF 0xBF 0xBF бүр 10 KB - тэгвэл хэрэв та хил хязгаарыг олох шаардлагатай бол ижил төстэй тэмдэглэгээ олдох хүртэл сонгосон хэсгийг скан хийхэд хангалттай. Сүүлчийн араас 0xBF дүрийн эхлэл байх нь баталгаатай. (Мэдээж тайлахдаа гурван байт дарааллыг үл тоомсорлох хэрэгтэй.)

Дүгнэж хэлэх

Хэрэв та энэ хүртэл уншсан бол баяр хүргэе! Та надтай адил Юникодын бүтцийн талаар шинэ зүйл сурсан (эсвэл ой санамжаа сэргээсэн) гэж найдаж байна.

Өөр нэг дугуй: бид Юникод мөрүүдийг UTF-30-аас 60-8% илүү авсаархан хадгалдаг
Демо хуудас. Еврей хэлний жишээ нь UTF-8 болон SCSU хоёрын давуу талыг харуулж байна.

Дээр дурдсан судалгааг стандартад халдсан гэж үзэж болохгүй. Гэхдээ ажлынхаа үр дүнд ерөнхийдөө сэтгэл хангалуун байдаг болохоор сэтгэл хангалуун байдаг хуваалцах: жишээ нь, жижигрүүлсэн JS номын сан нь ердөө 1710 байт жинтэй (мэдээж ямар ч хамааралгүй). Дээр дурдсанчлан түүний бүтээлийг эндээс олж болно Демо хуудас (үүнийг UTF-8 ба SCSU-тай харьцуулж болох олон тооны бичвэрүүд бас байдаг).

Эцэст нь би UTF-C ашигласан тохиолдлуудад дахин анхаарлаа хандуулах болно Үнэ цэнэтэй биш:

  • Хэрэв таны мөр хангалттай урт байвал (100-200 тэмдэгтээс). Энэ тохиолдолд та deflate гэх мэт шахалтын алгоритмуудыг ашиглах талаар бодох хэрэгтэй.
  • Хэрэв чамд хэрэгтэй бол ASCII ил тод байдал, өөрөөр хэлбэл, кодлогдсон дараалал нь анхны мөрөнд байгаагүй ASCII кодыг агуулаагүй байх нь танд чухал юм. Хэрэв та гуравдагч этгээдийн API-тай (жишээлбэл, мэдээллийн сантай ажиллах) харьцахдаа кодчилолын үр дүнг мөр биш, хийсвэр байт багц хэлбэрээр дамжуулвал ийм хэрэгцээ гарахаас зайлсхийх боломжтой. Үгүй бол та гэнэтийн эмзэг байдалд орох эрсдэлтэй.
  • Хэрэв та дүрийн хил хязгаарыг дурын аргаар хурдан олохыг хүсвэл (жишээлбэл, шугамын хэсэг гэмтсэн үед). Үүнийг хийж болно, гэхдээ зөвхөн мөрийг эхнээс нь сканнердах замаар (эсвэл өмнөх хэсэгт тайлбарласан өөрчлөлтийг ашиглан).
  • Хэрэв та мөрийн агуулга дээр үйлдлүүдийг хурдан гүйцэтгэх шаардлагатай бол (тэдгээрийг эрэмбэлэх, тэдгээрийн доторх дэд мөрүүдийг хайх, нэгтгэх). Энэ нь эхлээд мөрүүдийг тайлахыг шаарддаг тул UTF-C нь эдгээр тохиолдолд UTF-8-аас удаан байх болно (гэхдээ шахалтын алгоритмаас хурдан). Ижил мөрийг үргэлж ижил аргаар кодлодог тул код тайлалтыг нарийн харьцуулах шаардлагагүй бөгөөд үүнийг байт байтаар хийж болно.

мэдээ: хэрэглэгч Тиомич доорх сэтгэгдэлд UTF-C-ийн хэрэглээний хязгаарыг онцолсон график байрлуулсан. Энэ нь багцалсан мөр нь богино байх үед UTF-C нь ерөнхий зориулалтын шахалтын алгоритмаас (LZW-ийн хувилбар) илүү үр дүнтэй болохыг харуулж байна. ~140 тэмдэгт (гэхдээ би харьцуулалтыг нэг текст дээр хийсэн болохыг анхаарна уу; бусад хэл дээрх үр дүн өөр байж болно).
Өөр нэг дугуй: бид Юникод мөрүүдийг UTF-30-аас 60-8% илүү авсаархан хадгалдаг

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх