Дагы бир велосипед: биз Юникод саптарын UTF-30ге караганда 60-8% кыскараак сактайбыз

Дагы бир велосипед: биз Юникод саптарын UTF-30ге караганда 60-8% кыскараак сактайбыз

Эгерде сиз иштеп чыгуучу болсоңуз жана сиз коддоону тандоо милдети менен бетме-бет келген болсоңуз, анда Юникод дээрлик дайыма туура чечим болуп калат. Атайын көрсөтүү ыкмасы контекстке жараша болот, бирок көбүнчө бул жерде универсалдуу жооп бар - UTF-8. Анын жакшы жери - бул Юникоддун бардык символдорун коротпостон колдонууга мүмкүндүк берет дагы көпчүлүк учурларда көп байт. Ырас, латын алфавитин гана колдонгон тилдер үчүн "өтө көп эмес" дегенде ар бир белгиге эки байт. Болгону 256 символ менен чектеген тарыхка чейинки коддоолорго кайтпай, жакшыраак кыла алабызбы?

Төмөндө мен бул суроого жооп берүү аракети менен таанышууну жана UTF-8деги ашыкча кошумчаларды кошпостон дүйнөнүн көпчүлүк тилдеринде сызыктарды сактоого мүмкүндүк берген салыштырмалуу жөнөкөй алгоритмди ишке ашырууну сунуштайм.

Жоопкерчиликтен баш тартуу. Мен дароо бир нече маанилүү эскертүүлөрдү жасайм: сүрөттөлгөн чечим UTF-8 үчүн универсалдуу алмаштыруу катары сунушталбайт, ал иштердин тар тизмесинде гана ылайыктуу (төмөндө алар жөнүндө) жана эч кандай учурда үчүнчү тараптын API'лери менен иштешүү үчүн колдонулбашы керек (алар бул жөнүндө билишпейт). Көбүнчө жалпы максаттагы кысуу алгоритмдери (мисалы, дефлат) тексттик маалыматтардын чоң көлөмүн компакт сактоо үчүн ылайыктуу. Мындан тышкары, өзүмдүн чечимимди түзүү процессинде мен Юникоддун өзүндө иштеп жаткан стандартты таптым, ал ошол эле маселени чечет - бул бир аз татаалыраак (жана көбүнчө андан да жаман), бирок дагы эле бул кабыл алынган стандарт жана жөн эле коюу эмес тизе менен бирге. Мен ал жөнүндө да айтып берейин.

Юникод жана 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 да, 2 да байт жетишсиз болмок. Жана биздин процессорлор үч байттык сандар менен иштөөгө анча ылайыктуу эмес болгондуктан, биз ар бир белгиге 4 байт колдонууга аргасыз болобуз! Бул UTF-32, бирок дал ушул "ысырапкорчулуктун" айынан бул формат популярдуу эмес.

Бактыга жараша, Юникоддогу символдордун тартиби кокустук эмес. Алардын бүт топтому 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).

Анда эмне үчүн жаңы нерсени ойлоп табасың?

Ошол эле учурда, кээде дефлат сыяктуу кысуу алгоритмдери начар колдонула турган жагдайлар болот, бирок сиз саптарды компакт сактоону каалайсыз. Жеке мен куруу жөнүндө ойлонуп жатканда бул көйгөйгө туш болдум кысылган префикс дарагы ыктыярдуу тилдердеги сөздөрдү камтыган чоң сөздүк үчүн. Бир жагынан алганда, ар бир сөз өтө кыска, ошондуктан аны кысуу натыйжасыз болуп калат. Башка жагынан алганда, мен караган дарактын ишке ашырылышы сакталган саптын ар бир байт өзүнчө дарак чокусун жаратышы үчүн иштелип чыккан, андыктан алардын санын азайтуу абдан пайдалуу болду. Менин китепканамда Az.js (Кандай пиморфия2, ага негизделген) окшош көйгөйдү жөн эле чечсе болот - саптар салынган Dawg-сөздүк, ошол жерде сакталган жакшы эски CP1251. Бирок, түшүнүктүү болгондой, бул чектелген алфавит үчүн гана жакшы иштейт - кытай тилиндеги сапты мындай сөздүккө кошууга болбойт.

Өзүнчө, мен мындай маалымат структурасында UTF-8ди колдонууда пайда болгон дагы бир жагымсыз нюансты белгилегим келет. Жогорудагы сүрөттө символ эки байт катары жазылганда, анын санына тиешелүү биттер катарда келбей, бир жуп бит менен бөлүнгөнүн көрсөтүп турат. 10 ортосунда: 110xxxxx 10xxxxxx. Ушундан улам, символдун кодунда экинчи байттын төмөнкү 6 бити ашып кеткенде (б.а. өтүү пайда болот) 1011111110000000), анда биринчи байт да өзгөрөт. Көрсө, “п” тамгасы байт менен белгиленет экен 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 КБ - анда чекти табышыңыз керек болсо, окшош маркер табылганга чейин тандалган бөлүгүн сканерлөө жетиштүү болот. Акыркы артынан 0xBF каармандын башталышы болуп калууга кепилдик берилет. (Декоддоштурууда, бул үч байт ырааттуулугу, албетте, этибарга алынышы керек.)

жыйынтыктап жатып

Эгер сиз буга чейин окуган болсоңуз, куттуктайбыз! Мен сыяктуу, сиз да Юникоддун түзүмү жөнүндө жаңы нерсени билдиңиз (же эс тутумуңузду жаңырттыңыз) деп үмүттөнөм.

Дагы бир велосипед: биз Юникод саптарын UTF-30ге караганда 60-8% кыскараак сактайбыз
Демо бет. Hebrew мисалы UTF-8 жана SCSU экөө тең артыкчылыктарды көрсөтүп турат.

Жогоруда сүрөттөлгөн изилдөөлөр стандарттарга кол салуу катары каралбашы керек. Бирок, мен жалпысынан ишимдин жыйынтыгына ыраазымын, ошондуктан аларга ыраазымын үлүшү: мисалы, кичирейтилген JS китепканасынын салмагы болгону 1710 байт (жана, албетте, көз карандылыгы жок). Мен жогоруда айткандай, анын ишин тапса болот демо бет (UTF-8 жана SCSU менен салыштырууга мүмкүн болгон тексттердин жыйындысы да бар).

Акыр-аягы, мен дагы бир жолу UTF-C колдонулган учурларга көңүл бурам татыктуу эмес:

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

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

Source: www.habr.com