Beic arall: rydym yn storio tannau Unicode 30-60% yn fwy cryno nag UTF-8

Beic arall: rydym yn storio tannau Unicode 30-60% yn fwy cryno nag UTF-8

Os ydych chi'n ddatblygwr a'ch bod chi'n wynebu'r dasg o ddewis amgodio, yna Unicode fydd yr ateb cywir bron bob amser. Mae'r dull cynrychioli penodol yn dibynnu ar y cyd-destun, ond gan amlaf mae ateb cyffredinol yma hefyd - UTF-8. Y peth da amdano yw ei fod yn caniatáu ichi ddefnyddio pob cymeriad Unicode heb wario hefyd llawer o beit yn y rhan fwyaf o achosion. Gwir, ar gyfer ieithoedd sy’n defnyddio mwy na’r wyddor Ladin yn unig, “ddim yn ormod” o leiaf dau beit i bob cymeriad. A allwn ni wneud yn well heb ddychwelyd at amgodiadau cynhanesyddol sy'n ein cyfyngu i ddim ond 256 o nodau sydd ar gael?

Isod rwy'n cynnig ymgyfarwyddo â'm hymgais i ateb y cwestiwn hwn a gweithredu algorithm cymharol syml sy'n eich galluogi i storio llinellau yn y rhan fwyaf o ieithoedd y byd heb ychwanegu'r diswyddiad sydd yn UTF-8.

Ymwadiad. Gwnaf rai amheuon pwysig ar unwaith: ni chynigir yr ateb a ddisgrifir fel ateb cyffredinol yn lle UTF-8, dim ond mewn rhestr gul o achosion y mae'n addas (mwy arnynt isod), ac ni ddylid mewn unrhyw achos ei ddefnyddio i ryngweithio ag APIs trydydd parti (nad ydynt hyd yn oed yn gwybod amdano). Yn fwyaf aml, mae algorithmau cywasgu cyffredinol (er enghraifft, datchwyddiant) yn addas ar gyfer storio llawer iawn o ddata testun yn gryno. Yn ogystal, eisoes yn y broses o greu fy ateb, deuthum o hyd i safon sy'n bodoli eisoes yn Unicode ei hun, sy'n datrys yr un broblem - mae ychydig yn fwy cymhleth (ac yn aml yn waeth), ond yn dal i fod yn safon dderbyniol, ac nid yn unig yn rhoi gyda'i gilydd ar y pen-glin. Fe ddywedaf wrthych amdano hefyd.

Am Unicode ac UTF-8

I ddechrau, ychydig o eiriau am yr hyn ydyw Unicode и UTF-8.

Fel y gwyddoch, arferai amgodiadau 8-did fod yn boblogaidd. Gyda nhw, roedd popeth yn syml: gellir rhifo 256 nod gyda rhifau o 0 i 255, ac yn amlwg gellir cynrychioli rhifau o 0 i 255 fel un beit. Os awn yn ôl i'r cychwyn cyntaf, mae'r amgodio ASCII wedi'i gyfyngu'n llwyr i 7 did, felly'r darn mwyaf arwyddocaol yn ei gynrychiolaeth beit yw sero, ac mae'r rhan fwyaf o amgodiadau 8-did yn gydnaws ag ef (dim ond yn yr "uwch" y maent yn wahanol. rhan, lle mae'r rhan mwyaf arwyddocaol yn un).

Sut mae Unicode yn wahanol i'r amgodiadau hynny a pham mae cymaint o gynrychioliadau penodol yn gysylltiedig ag ef - UTF-8, UTF-16 (BE a LE), UTF-32? Gadewch i ni ei ddatrys mewn trefn.

Mae'r safon Unicode sylfaenol yn disgrifio'r gyfatebiaeth rhwng nodau yn unig (ac mewn rhai achosion, cydrannau unigol nodau) a'u rhifau. Ac mae yna lawer o niferoedd posib yn y safon hon - o 0x00 i 0x10FFFF (1 o ddarnau). Pe baem am roi rhif mewn amrediad o'r fath i mewn i newidyn, ni fyddai 114 na 112 beit yn ddigon i ni. A chan nad yw ein proseswyr wedi'u cynllunio'n fawr ar gyfer gweithio gyda rhifau tri-beit, byddem yn cael ein gorfodi i ddefnyddio cymaint â 1 beit y cymeriad! Dyma UTF-2, ond yn union oherwydd y “gwastraff” hwn nad yw'r fformat hwn yn boblogaidd.

Yn ffodus, nid yw trefn y cymeriadau o fewn Unicode yn hap. Mae eu set gyfan wedi'i rhannu'n 17"awyrennau", pob un yn cynnwys 65536 (0x10000) "pwyntiau cod" Mae'r cysyniad o “bwynt cod” yma yn syml rhif nod, a neilltuwyd iddo gan Unicode. Ond, fel y soniwyd uchod, yn Unicode nid yn unig y mae cymeriadau unigol yn cael eu rhifo, ond hefyd eu cydrannau a'u marciau gwasanaeth (ac weithiau nid oes dim byd o gwbl yn cyfateb i'r rhif - efallai am y tro, ond nid yw hyn mor bwysig i ni), felly mae'n fwy cywir bob amser siarad yn benodol am nifer y rhifau eu hunain, ac nid symbolau. Fodd bynnag, yn y canlynol, er mwyn bod yn gryno, byddaf yn aml yn defnyddio'r gair “symbol”, gan awgrymu'r term “pwynt cod”.

Beic arall: rydym yn storio tannau Unicode 30-60% yn fwy cryno nag UTF-8
Awyrennau Unicode. Fel y gwelwch, mae'r rhan fwyaf ohono (awyrennau 4 i 13) yn dal heb ei ddefnyddio.

Yr hyn sydd fwyaf rhyfeddol yw bod yr holl brif “mwydion” yn gorwedd yn yr awyren sero, fe'i gelwir yn “Awyren Amlieithog Sylfaenol" . Os yw llinell yn cynnwys testun yn un o'r ieithoedd modern (gan gynnwys Tsieinëeg), ni fyddwch yn mynd y tu hwnt i'r awyren hon. Ond ni allwch dorri i ffwrdd gweddill Unicode chwaith - er enghraifft, lleolir emoji yn bennaf ar ddiwedd y yr awyren nesaf,"Plân Amlieithog Atodol"(mae'n ymestyn o 0x10000 i 0x1FFFF). Felly mae UTF-16 yn gwneud hyn: pob nod yn dod o fewn Awyren Amlieithog Sylfaenol, yn cael eu hamgodio “fel y mae” gyda rhif dau-beit cyfatebol. Fodd bynnag, nid yw rhai o'r rhifau yn yr ystod hon yn nodi nodau penodol o gwbl, ond maent yn nodi bod angen i ni ystyried un arall ar ôl y pâr hwn o beit - trwy gyfuno gwerthoedd y pedwar beit hyn gyda'i gilydd, rydym yn cael rhif sy'n cwmpasu yr ystod Unicode ddilys gyfan. Gelwir y syniad hwn yn “gyplau dirprwyol” - efallai eich bod wedi clywed amdanynt.

Felly mae UTF-16 yn gofyn am ddau neu (mewn achosion prin iawn) pedwar beit fesul "pwynt cod". Mae hyn yn well na defnyddio pedwar beit drwy'r amser, ond mae Lladin (a nodau ASCII eraill) o'i hamgodio fel hyn yn gwastraffu hanner y gofod ar sero. Mae UTF-8 wedi'i gynllunio i gywiro hyn: mae ASCII ynddo yn ei feddiannu, fel o'r blaen, dim ond un beit; codau o 0x80 i 0x7FF - dau beit; rhag 0x800 i 0xFFFF — tri, ac o 0x10000 i 0x10FFFF - pedwar. Ar y naill law, mae'r wyddor Ladin wedi dod yn dda: mae cydnawsedd ag ASCII wedi dychwelyd, ac mae'r dosbarthiad yn fwy cyfartal “lledaenu” o 1 i 4 beit. Ond nid yw wyddor heblaw Lladin, gwaetha'r modd, yn elwa mewn unrhyw ffordd o'i gymharu ag UTF-16, ac mae llawer bellach angen tri beit yn lle dau - mae'r ystod a gwmpesir gan gofnod dau-beit wedi culhau 32 gwaith, gyda 0xFFFF i 0x7FF, ac nid yw Tsieinëeg nac, er enghraifft, Sioraidd wedi'u cynnwys ynddo. Cyrilig a phum wyddor arall - hurray - lwcus, 2 beit i bob cymeriad.

Pam mae hyn yn digwydd? Gadewch i ni weld sut mae UTF-8 yn cynrychioli codau cymeriad:
Beic arall: rydym yn storio tannau Unicode 30-60% yn fwy cryno nag UTF-8
Yn uniongyrchol i gynrychioli rhifau, defnyddir darnau sydd wedi'u marcio â'r symbol yma x. Gellir gweld mai dim ond 11 did o'r fath sydd mewn cofnod dau beit (allan o 16). Swyddogaeth ategol yn unig sydd gan y darnau arweiniol yma. Yn achos cofnod pedwar beit, dyrennir 21 allan o 32 did ar gyfer rhif y pwynt cod - mae'n ymddangos y byddai tri beit (sy'n rhoi cyfanswm o 24 did) yn ddigon, ond mae marcwyr gwasanaeth yn bwyta gormod.

Ydy hyn yn ddrwg? Ddim mewn gwirionedd. Ar y naill law, os ydym yn poeni llawer am ofod, mae gennym algorithmau cywasgu a all gael gwared ar yr holl entropi a'r diswyddiad ychwanegol yn hawdd. Ar y llaw arall, nod Unicode oedd darparu'r codio mwyaf cyffredinol posibl. Er enghraifft, gallwn ymddiried llinell wedi'i hamgodio yn UTF-8 i godio a oedd yn gweithio'n flaenorol gydag ASCII yn unig, a pheidio ag ofni y bydd yn gweld cymeriad o'r ystod ASCII nad yw yno mewn gwirionedd (wedi'r cyfan, yn UTF-8 i gyd beit yn dechrau o'r did sero - dyma'n union beth yw ASCII). Ac os ydym yn sydyn am dorri cynffon fach o linyn mawr heb ei ddadgodio o'r cychwyn cyntaf (neu adfer rhan o'r wybodaeth ar ôl adran wedi'i difrodi), mae'n hawdd i ni ddod o hyd i'r gwrthbwyso lle mae cymeriad yn dechrau (mae'n ddigon i hepgor beit sydd â rhagddodiad ychydig 10).

Pam felly dyfeisio rhywbeth newydd?

Ar yr un pryd, mae yna sefyllfaoedd o bryd i'w gilydd pan fydd algorithmau cywasgu fel datchwyddiant yn berthnasol yn wael, ond rydych chi am sicrhau storfa gryno o linynnau. Yn bersonol, deuthum ar draws y broblem hon wrth feddwl am adeiladu coeden rhagddodiad cywasgedig am eiriadur mawr yn cynnwys geiriau mewn ieithoedd mympwyol. Ar y naill law, mae pob gair yn fyr iawn, felly bydd ei gywasgu yn aneffeithiol. Ar y llaw arall, cynlluniwyd y gweithrediad coed a ystyriais fel bod pob beit o'r llinyn storio yn cynhyrchu fertig coeden ar wahân, felly roedd lleihau eu nifer yn ddefnyddiol iawn. Yn fy llyfrgell Az.js (Fel pymorffi2, y mae'n seiliedig arno) gellir datrys problem debyg yn syml - llinynnau wedi'u pacio i mewn DAWG-geiriadur, storio yno yn CP1251 hen dda. Ond, fel sy'n hawdd ei ddeall, dim ond ar gyfer wyddor gyfyngedig y mae hyn yn gweithio'n dda - ni ellir ychwanegu llinell mewn Tsieinëeg at eiriadur o'r fath.

Ar wahân, hoffwn nodi un naws annymunol arall sy'n codi wrth ddefnyddio UTF-8 mewn strwythur data o'r fath. Mae'r llun uchod yn dangos pan fydd cymeriad yn cael ei ysgrifennu fel dau beit, nid yw'r darnau sy'n gysylltiedig â'i rif yn dod mewn rhes, ond yn cael eu gwahanu gan bâr o ddarnau 10 yn y canol: 110xxxxx 10xxxxxx. Oherwydd hyn, pan fydd 6 did isaf yr ail beit yn gorlifo yn y cod nod (h.y., mae trawsnewidiad yn digwydd 1011111110000000), yna mae'r beit cyntaf yn newid hefyd. Mae'n ymddangos bod y llythyren "p" yn cael ei ddynodi gan beit 0xD0 0xBF, ac mae’r “r” nesaf eisoes 0xD1 0x80. Mewn coeden rhagddodiad, mae hyn yn arwain at rannu'r nod rhiant yn ddau - un ar gyfer y rhagddodiad 0xD0, ac un arall ar gyfer 0xD1 (er y gallai'r wyddor Syrilig gyfan gael ei hamgodio gan yr ail beit yn unig).

Beth ges i

Yn wyneb y broblem hon, penderfynais ymarfer chwarae gemau gyda darnau, ac ar yr un pryd dod yn fwy cyfarwydd â strwythur Unicode yn ei gyfanrwydd. Y canlyniad oedd y fformat amgodio UTF-C ("C" ar gyfer compact), sy'n gwario dim mwy na 3 bytes fesul pwynt cod, ac yn aml iawn yn caniatáu ichi wario yn unig un beit ychwanegol ar gyfer y llinell amgodio gyfan. Mae hyn yn arwain at y ffaith bod amgodio o'r fath yn troi allan i fod ar lawer o wyddor nad ydynt yn ASCII 30-60% yn fwy cryno nag UTF-8.

Rwyf wedi cyflwyno enghreifftiau o weithredu algorithmau amgodio a datgodio ar y ffurf llyfrgelloedd JavaScript a Go, gallwch chi eu defnyddio'n rhydd yn eich cod. Ond byddaf yn dal i bwysleisio bod y fformat hwn mewn ffordd yn parhau i fod yn “feic”, ac nid wyf yn argymell ei ddefnyddio heb sylweddoli pam eich bod ei angen. Mae hwn yn dal i fod yn fwy o arbrawf na “gwelliant UTF-8” difrifol. Serch hynny, mae'r cod yno wedi'i ysgrifennu'n daclus, yn gryno, gyda nifer fawr o sylwadau a sylw i'r prawf.

Beic arall: rydym yn storio tannau Unicode 30-60% yn fwy cryno nag UTF-8
Canlyniadau profion a chymhariaeth ag UTF-8

gwnes i hefyd tudalen demo, lle gallwch chi werthuso perfformiad yr algorithm, ac yna byddaf yn dweud mwy wrthych am ei egwyddorion a'i broses ddatblygu.

Cael gwared ar ddarnau diangen

Cymerais UTF-8 fel sail, wrth gwrs. Y peth cyntaf a mwyaf amlwg y gellir ei newid ynddo yw lleihau nifer y darnau gwasanaeth ym mhob beit. Er enghraifft, mae'r beit cyntaf yn UTF-8 bob amser yn dechrau gyda'r naill neu'r llall 0, neu gyda 11 - rhagddodiad 10 Dim ond y bytes canlynol sydd ganddo. Gadewch i ni ddisodli'r rhagddodiad 11 ar 1, ac ar gyfer y bytes nesaf byddwn yn dileu'r rhagddodiaid yn gyfan gwbl. Beth fydd yn digwydd?

0xxxxxxx — 1 beit
10xxxxxx xxxxxxxx - 2 beit
110xxxxx xxxxxxxx xxxxxxxx - 3 beit

Arhoswch, ble mae'r record pedwar beit? Ond nid oes ei angen bellach - wrth ysgrifennu mewn tri beit, mae gennym bellach 21 did ar gael ac mae hyn yn ddigon ar gyfer pob rhif hyd at 0x10FFFF.

Beth ydyn ni wedi'i aberthu yma? Y peth pwysicaf yw canfod ffiniau cymeriad o leoliad mympwyol yn y byffer. Ni allwn bwyntio at beit mympwyol a chanfod dechrau'r cymeriad nesaf ohono. Mae hyn yn gyfyngiad ar ein fformat, ond yn ymarferol anaml y mae hyn yn angenrheidiol. Fel arfer, rydyn ni'n gallu rhedeg trwy'r byffer o'r cychwyn cyntaf (yn enwedig pan ddaw i linellau byr).

Mae'r sefyllfa o ran cwmpasu ieithoedd gyda 2 beit hefyd wedi dod yn well: nawr mae'r fformat dau beit yn rhoi ystod o 14 did, ac mae'r rhain yn godau hyd at 0x3FFF. Mae'r Tsieineaid yn anlwcus (mae eu cymeriadau'n amrywio o 0x4E00 i 0x9FFF), ond mae Georgiaid a llawer o bobloedd eraill yn cael mwy o hwyl - mae eu hieithoedd hefyd yn ffitio i mewn i 2 beit y cymeriad.

Rhowch gyflwr yr amgodiwr

Gadewch i ni nawr feddwl am briodweddau'r llinellau eu hunain. Mae'r geiriadur gan amlaf yn cynnwys geiriau wedi'u hysgrifennu mewn cymeriadau o'r un wyddor, ac mae hyn hefyd yn wir am lawer o destunau eraill. Byddai yn dda nodi y wyddor hon unwaith, ac yna nodi dim ond rhif y llythyren o'i mewn. Gawn ni weld a fydd trefniant y cymeriadau yn y tabl Unicode yn ein helpu ni.

Fel y soniwyd uchod, mae Unicode wedi'i rannu'n awyren 65536 codau yr un. Ond nid yw hon yn rhaniad defnyddiol iawn (fel y dywedwyd eisoes, yn fwyaf aml rydym yn yr awyren sero). Mwy diddorol yw'r rhaniad erbyn blociau. Nid oes gan yr ystodau hyn hyd sefydlog bellach, ac maent yn fwy ystyrlon - fel rheol, mae pob un yn cyfuno nodau o'r un wyddor.

Beic arall: rydym yn storio tannau Unicode 30-60% yn fwy cryno nag UTF-8
Bloc sy'n cynnwys nodau'r wyddor Bengali. Yn anffodus, am resymau hanesyddol, dyma enghraifft o becynnu nad yw'n drwchus iawn - mae 96 nod wedi'u gwasgaru'n anhrefnus ar draws 128 o bwyntiau cod bloc.

Mae dechreuadau blociau a'u meintiau bob amser yn lluosrifau o 16 - gwneir hyn yn syml er hwylustod. Yn ogystal, mae llawer o flociau yn dechrau ac yn gorffen ar werthoedd sy'n lluosrifau o 128 neu hyd yn oed 256 - er enghraifft, mae'r wyddor Syrilig sylfaenol yn cymryd 256 beit o 0x0400 i 0x04FF. Mae hyn yn eithaf cyfleus: os byddwn yn arbed y rhagddodiad unwaith 0x04, yna gellir ysgrifennu unrhyw gymeriad Cyrilig mewn un beit. Yn wir, fel hyn byddwn yn colli'r cyfle i ddychwelyd i ASCII (ac i unrhyw gymeriadau eraill yn gyffredinol). Felly rydym yn gwneud hyn:

  1. Dau beit 10yyyyyy yxxxxxxx nid yn unig yn dynodi symbol gyda rhif yyyyyy yxxxxxxx, ond hefyd yn newid wyddor gyfredol ar yyyyyy y0000000 (h.y. rydym yn cofio’r holl ddarnau ac eithrio’r rhai lleiaf arwyddocaol Bit 7);
  2. Un beit 0xxxxxxx dyma gymeriad yr wyddor gyfredol. Mae angen ei ychwanegu at y gwrthbwyso a gofiwyd gennym yng ngham 1. Er na wnaethom newid yr wyddor, sero yw'r gwrthbwyso, felly gwnaethom gynnal cydnawsedd ag ASCII.

Yn yr un modd ar gyfer codau sy'n gofyn am 3 beit:

  1. Tri beit 110yyyyy yxxxxxxx xxxxxxxx dynodi symbol gyda rhif yyyyyy yxxxxxxx xxxxxxxx, newid wyddor gyfredol ar yyyyyy y0000000 00000000 (yn cofio popeth heblaw'r rhai iau Bit 15), a thiciwch y blwch yr ydym yn awr ynddo hir modd (wrth newid yr wyddor yn ôl i un beit dwbl, byddwn yn ailosod y faner hon);
  2. Dau beit 0xxxxxxx xxxxxxxx yn y modd hir mae'n gymeriad y wyddor gyfredol. Yn yr un modd, rydym yn ei ychwanegu gyda'r gwrthbwyso o gam 1. Yr unig wahaniaeth yw ein bod nawr yn darllen dau beit (oherwydd ein bod wedi newid i'r modd hwn).

Mae'n swnio'n dda: nawr tra bod angen i ni amgodio cymeriadau o'r un ystod Unicode 7-did, rydyn ni'n gwario 1 beit ychwanegol ar y dechrau a chyfanswm o un beit fesul cymeriad.

Beic arall: rydym yn storio tannau Unicode 30-60% yn fwy cryno nag UTF-8
Gweithio o un o'r fersiynau cynharach. Mae eisoes yn aml yn curo UTF-8, ond mae lle i wella o hyd.

Beth sy'n waeth? Yn gyntaf, mae gennym amod, sef gwrthbwyso wyddor gyfredol a blwch ticio modd hir. Mae hyn yn ein cyfyngu ymhellach: nawr gellir amgodio'r un nodau yn wahanol mewn gwahanol gyd-destunau. Bydd yn rhaid chwilio am is-linynnau, er enghraifft, gan gymryd hyn i ystyriaeth, ac nid dim ond drwy gymharu beit. Yn ail, cyn gynted ag y gwnaethom newid yr wyddor, daeth yn ddrwg gydag amgodio nodau ASCII (ac nid yn unig yr wyddor Ladin yw hyn, ond hefyd atalnodi sylfaenol, gan gynnwys bylchau) - mae angen iddynt newid yr wyddor eto i 0, hynny yw, eto beit ychwanegol (ac yna un arall i fynd yn ôl at ein prif bwynt).

Mae un wyddor yn dda, dwy yn well

Gadewch i ni geisio newid ein rhagddodiaid bit ychydig, gan wasgu mewn un arall i'r tri a ddisgrifir uchod:

0xxxxxxx — 1 beit yn y modd arferol, 2 yn y modd hir
11xxxxxx — 1 beit
100xxxxx xxxxxxxx - 2 beit
101xxxxx xxxxxxxx xxxxxxxx - 3 beit

Beic arall: rydym yn storio tannau Unicode 30-60% yn fwy cryno nag UTF-8

Nawr mewn cofnod dau beit mae un did yn llai ar gael - pwyntiau cod hyd at 0x1FFFAc nid 0x3FFF. Fodd bynnag, mae'n dal yn amlwg yn fwy nag mewn codau dwbl-beit UTF-8, mae'r ieithoedd mwyaf cyffredin yn dal i ffitio i mewn, mae'r golled fwyaf amlwg wedi cwympo allan hiragana и katakana, mae'r Japaneaid yn drist.

Beth yw'r cod newydd hwn? 11xxxxxx? Mae hwn yn “stash” bach o 64 nod mewn maint, mae'n ategu ein prif wyddor, felly fe'i gelwais yn ategol (ategol) wyddor. Pan fyddwn yn newid yr wyddor gyfredol, mae darn o'r hen wyddor yn dod yn ategol. Er enghraifft, rydym yn newid o ASCII i Cyrillic - mae'r stash bellach yn cynnwys 64 nod sy'n cynnwys Yr wyddor Ladin, rhifau, gofod a choma (mewnosodiadau amlaf mewn testunau nad ydynt yn ASCII). Newidiwch yn ôl i ASCII - a bydd prif ran yr wyddor Syrilig yn dod yn wyddor ategol.

Diolch i fynediad i ddwy wyddor, gallwn drin nifer fawr o destunau heb fawr o gostau ar gyfer newid yr wyddor (bydd atalnodi yn aml yn arwain at ddychwelyd i ASCII, ond ar ôl hynny byddwn yn cael llawer o nodau nad ydynt yn ASCII o'r wyddor ychwanegol, heb newid eto).

Bonws: rhagddodi'r is-wyddor 11xxxxxx a dewis ei wrthbwyso dechreuol i fod 0xC0, rydym yn cael cydnawsedd rhannol â CP1252. Mewn geiriau eraill, bydd llawer (ond nid pob un) o destunau Gorllewin Ewrop sydd wedi'u hamgodio yn CP1252 yn edrych yr un fath yn UTF-C.

Yma, fodd bynnag, mae anhawster yn codi: sut i gael un ategol o'r brif wyddor? Gallwch chi adael yr un gwrthbwyso, ond - gwaetha'r modd - yma mae strwythur Unicode eisoes yn chwarae yn ein herbyn. Yn aml iawn nid yw prif ran yr wyddor ar ddechrau'r bloc (er enghraifft, mae gan brifddinas Rwsia "A" y cod 0x0410, er bod y bloc Cyrilig yn dechrau gyda 0x0400). Felly, ar ôl cymryd y 64 nod cyntaf i'r stash, efallai y byddwn yn colli mynediad i ran gynffon yr wyddor.

I ddatrys y broblem hon, es i â llaw trwy rai blociau sy'n cyfateb i wahanol ieithoedd, a nodi gwrthbwyso'r wyddor ategol o fewn y prif un ar eu cyfer. Fel eithriad, roedd yr wyddor Ladin yn cael ei haildrefnu'n gyffredinol fel sylfaen64.

Beic arall: rydym yn storio tannau Unicode 30-60% yn fwy cryno nag UTF-8

Cyffyrddiadau terfynol

Gadewch i ni feddwl yn olaf am ble arall y gallwn wella rhywbeth.

Sylwch fod y fformat 101xxxxx xxxxxxxx xxxxxxxx yn eich galluogi i amgodio rhifau hyd at 0x1FFFFF, ac mae Unicode yn dod i ben yn gynharach, yn 0x10FFFF. Mewn geiriau eraill, bydd y pwynt cod olaf yn cael ei gynrychioli fel 10110000 11111111 11111111. Felly, gallwn ddweud os yw'r beit cyntaf o'r ffurf 1011xxxx (Lle xxxx mwy na 0), yna mae'n golygu rhywbeth arall. Er enghraifft, gallwch chi ychwanegu 15 nod arall yno sydd ar gael yn gyson i'w hamgodio mewn un beit, ond penderfynais ei wneud yn wahanol.

Gadewch i ni edrych ar y blociau Unicode hynny sydd angen tri beit nawr. Yn y bôn, fel y crybwyllwyd eisoes, cymeriadau Tsieineaidd yw'r rhain - ond mae'n anodd gwneud unrhyw beth â nhw, mae yna 21 mil ohonyn nhw. Ond hedfanodd hiragana a katakana yno hefyd - a does dim cymaint ohonyn nhw bellach, llai na dau gant. Ac, ers i ni gofio'r Japaneaid, mae yna emojis hefyd (mewn gwirionedd, maen nhw wedi'u gwasgaru mewn sawl man yn Unicode, ond mae'r prif flociau yn yr ystod 0x1F300 - 0x1FBFF). Os ydych chi'n meddwl am y ffaith bod yna nawr emojis sy'n cael eu cydosod o sawl pwynt cod ar unwaith (er enghraifft, yr emojisBeic arall: rydym yn storio tannau Unicode 30-60% yn fwy cryno nag UTF-8 yn cynnwys cymaint â 7 cod!), yna mae'n dod yn drueni llwyr i dreulio tri beit ar bob (7 × 3 = 21 beit er mwyn un eicon, yn hunllef).

Felly, rydym yn dewis ychydig o ystodau dethol sy'n cyfateb i emoji, hiragana a katakana, eu hail-rifo yn un rhestr barhaus a'u hamgodio fel dau beit yn lle tri:

1011xxxx xxxxxxxx

Gwych: yr emoji uchodBeic arall: rydym yn storio tannau Unicode 30-60% yn fwy cryno nag UTF-8, sy'n cynnwys 7 pwynt cod, yn cymryd 8 bytes yn UTF-25, ac rydym yn ei ffitio i mewn 14 (dim ond dau beit ar gyfer pob pwynt cod). Gyda llaw, gwrthododd Habr ei dreulio (yn yr hen a'r golygydd newydd), felly bu'n rhaid i mi ei fewnosod gyda llun.

Gadewch i ni geisio trwsio un broblem arall. Fel y cofiwn, yr wyddor sylfaenol yn ei hanfod uchel 6 did, yr ydym yn ei gadw mewn cof ac yn gludo i god pob symbol datgodio nesaf. Yn achos cymeriadau Tsieineaidd sydd yn y bloc 0x4E00 - 0x9FFF, dyma naill ai did 0 neu 1. Nid yw hyn yn gyfleus iawn: bydd angen i ni newid yr wyddor yn gyson rhwng y ddau werth hyn (h.y. gwario tri beit). Ond sylwch, yn y modd hir, o'r cod ei hun gallwn dynnu nifer y cymeriadau rydyn ni'n eu hamgodio gan ddefnyddio'r modd byr (ar ôl yr holl driciau a ddisgrifir uchod, dyma 10240) - yna bydd yr ystod o hieroglyffau yn symud i 0x2600 - 0x77FF, ac yn yr achos hwn, trwy gydol yr ystod gyfan hon, bydd y 6 did mwyaf arwyddocaol (allan o 21) yn hafal i 0. Felly, bydd dilyniannau o hieroglyffau yn defnyddio dau beit fesul hieroglyff (sy'n optimaidd ar gyfer ystod mor fawr), heb achosi switsys wyddor.

Atebion amgen: SCSU, BOCU-1

Mae'n debyg y bydd arbenigwyr Unicode, ar ôl darllen teitl yr erthygl, yn prysuro i'ch atgoffa bod yn uniongyrchol ymhlith safonau Unicode. Cynllun Cywasgu Safonol ar gyfer Unicode (SCSU), sy'n disgrifio dull amgodio tebyg iawn i'r un a ddisgrifir yn yr erthygl.

Rwy'n cyfaddef yn onest: dim ond ar ôl i mi ymgolli'n ddwfn wrth ysgrifennu fy mhenderfyniad y dysgais am ei fodolaeth. Pe bawn i'n gwybod amdano o'r dechrau, mae'n debyg y byddwn wedi ceisio ysgrifennu gweithrediad yn hytrach na meddwl am fy ymagwedd fy hun.

Yr hyn sy'n ddiddorol yw bod SCSU yn defnyddio syniadau tebyg iawn i'r rhai a es i ar fy mhen fy hun (yn lle'r cysyniad o "wyddor" maen nhw'n defnyddio "ffenestri", ac mae mwy ohonyn nhw ar gael nag sydd gen i). Ar yr un pryd, mae gan y fformat hwn anfanteision hefyd: mae ychydig yn agosach at algorithmau cywasgu na rhai amgodio. Yn benodol, mae'r safon yn rhoi llawer o ddulliau cynrychiolaeth, ond nid yw'n dweud sut i ddewis yr un gorau posibl - ar gyfer hyn, rhaid i'r amgodiwr ddefnyddio rhyw fath o heuristics. Felly, bydd amgodiwr SCSU sy'n cynhyrchu deunydd pacio da yn fwy cymhleth ac yn fwy beichus na fy algorithm.

Er mwyn cymharu, trosglwyddais weithrediad cymharol syml o SCSU i JavaScript - o ran cyfaint cod roedd yn debyg i'm UTF-C, ond mewn rhai achosion roedd y canlyniad yn ddegau y cant yn waeth (weithiau gall fod yn fwy na hynny, ond nid o lawer). Er enghraifft, amgodiwyd testunau yn Hebraeg a Groeg gan UTF-C 60% yn well na SCSU (yn ôl pob tebyg oherwydd eu wyddor gryno).

Ar wahân, fe ychwanegaf, ar wahân i SCSU, fod yna ffordd arall hefyd i gynrychioli Unicode yn gryno - BOCU-1, ond mae'n anelu at gydnawsedd MIME (nad oedd ei angen arnaf) ac mae'n cymryd agwedd ychydig yn wahanol at amgodio. Nid wyf wedi asesu ei effeithiolrwydd, ond ymddengys i mi ei bod yn annhebygol o fod yn uwch na SCSU.

Gwelliannau posib

Nid yw'r algorithm a gyflwynais yn gyffredinol yn ôl ei ddyluniad (mae'n debyg mai dyma lle mae fy nodau'n gwahaniaethu fwyaf oddi wrth nodau Consortiwm Unicode). Soniais eisoes iddo gael ei ddatblygu'n bennaf ar gyfer un dasg (storio geiriadur amlieithog mewn coeden rhagddodiad), ac efallai na fydd rhai o'i nodweddion yn addas iawn ar gyfer tasgau eraill. Ond gall y ffaith nad yw'n safon fod yn fantais - gallwch chi ei addasu'n hawdd i weddu i'ch anghenion.

Er enghraifft, yn y ffordd amlwg gallwch chi gael gwared ar bresenoldeb y wladwriaeth, gwneud codio heb wladwriaeth - peidiwch â diweddaru newidynnau offs, auxOffs и is21Bit yn yr amgodiwr a'r datgodiwr. Yn yr achos hwn, ni fydd yn bosibl pacio dilyniannau o nodau o'r un wyddor yn effeithiol, ond bydd gwarant bod yr un nod bob amser wedi'i amgodio gyda'r un beit, waeth beth fo'r cyd-destun.

Yn ogystal, gallwch chi deilwra'r amgodiwr i iaith benodol trwy newid y cyflwr diofyn - er enghraifft, canolbwyntio ar destunau Rwsieg, gosodwch yr amgodiwr a'r datgodiwr ar y dechrau offs = 0x0400 и auxOffs = 0. Mae hyn yn arbennig yn gwneud synnwyr yn achos modd di-wladwriaeth. Yn gyffredinol, bydd hyn yn debyg i ddefnyddio'r hen amgodio wyth-did, ond heb ddileu'r gallu i fewnosod nodau o bob Unicode yn ôl yr angen.

Anfantais arall a grybwyllwyd yn gynharach yw nad oes ffordd gyflym o ddod o hyd i ffin y cymeriad sydd agosaf at beit mympwyol mewn testun mawr sydd wedi'i amgodio yn UTF-C. Os torrwch yr olaf, dyweder, 100 beit o'r byffer wedi'i amgodio, rydych mewn perygl o gael sothach na allwch wneud unrhyw beth ag ef. Nid yw'r amgodio wedi'i gynllunio ar gyfer storio logiau aml-gigabyte, ond yn gyffredinol gellir cywiro hyn. Beit 0xBF ni ddylai byth ymddangos fel y beit cyntaf (ond gall fod yr ail neu'r trydydd). Felly, wrth amgodio, gallwch fewnosod y dilyniant 0xBF 0xBF 0xBF bob, dyweder, 10 KB - yna, os oes angen i chi ddod o hyd i ffin, bydd yn ddigon i sganio'r darn a ddewiswyd nes dod o hyd i farciwr tebyg. Yn dilyn yr olaf 0xBF yn sicr o fod yn ddechrau cymeriad. (Wrth ddadgodio, bydd angen anwybyddu'r dilyniant hwn o dri beit, wrth gwrs.)

Crynhoi

Os ydych chi wedi darllen hyd yma, llongyfarchiadau! Gobeithio eich bod chi, fel fi, wedi dysgu rhywbeth newydd (neu wedi adnewyddu eich cof) am strwythur Unicode.

Beic arall: rydym yn storio tannau Unicode 30-60% yn fwy cryno nag UTF-8
Tudalen demo. Mae enghraifft Hebraeg yn dangos y manteision dros UTF-8 a SCSU.

Ni ddylid ystyried yr ymchwil a ddisgrifir uchod yn dresmasu ar safonau. Fodd bynnag, rwy’n fodlon ar y cyfan â chanlyniadau fy ngwaith, felly rwy’n hapus â nhw rhannu: er enghraifft, mae llyfrgell JS miniog yn pwyso dim ond 1710 beit (ac nid oes ganddi unrhyw ddibyniaethau, wrth gwrs). Fel y soniais uchod, gellir dod o hyd i'w gwaith yn tudalen demo (mae yna hefyd set o destunau y gellir ei gymharu ag UTF-8 a SCSU arnynt).

Yn olaf, byddaf yn tynnu sylw unwaith eto at achosion lle mae UTF-C yn cael ei ddefnyddio Nid yw werth ei:

  • Os yw'ch llinellau'n ddigon hir (o 100-200 nod). Yn yr achos hwn, dylech feddwl am ddefnyddio algorithmau cywasgu fel datchwyddiant.
  • Os oes angen tryloywder ASCII, hynny yw, mae'n bwysig i chi nad yw'r dilyniannau wedi'u hamgodio yn cynnwys codau ASCII nad oeddent yn y llinyn gwreiddiol. Gellir osgoi'r angen am hyn os byddwch, wrth ryngweithio ag API trydydd parti (er enghraifft, gweithio gyda chronfa ddata), yn pasio'r canlyniad amgodio fel set haniaethol o beit, ac nid fel llinynnau. Fel arall, rydych mewn perygl o gael gwendidau annisgwyl.
  • Os ydych chi am allu dod o hyd i ffiniau nodau yn gyflym ar wrthbwyso mympwyol (er enghraifft, pan fydd rhan o linell wedi'i difrodi). Gellir gwneud hyn, ond dim ond trwy sganio'r llinell o'r dechrau (neu gymhwyso'r addasiad a ddisgrifiwyd yn yr adran flaenorol).
  • Os oes angen i chi berfformio gweithrediadau yn gyflym ar gynnwys llinynnau (trefnwch nhw, chwiliwch am is-linynnau ynddynt, cydgatenate). Mae hyn yn ei gwneud yn ofynnol i llinynnau gael eu datgodio yn gyntaf, felly bydd UTF-C yn arafach nag UTF-8 yn yr achosion hyn (ond yn gyflymach nag algorithmau cywasgu). Gan fod yr un llinyn bob amser yn cael ei amgodio yn yr un ffordd, nid oes angen cymharu datgodio yn union a gellir ei wneud ar sail beit wrth beit.

Diweddaru: y defnyddiwr Tyomitch yn y sylwadau isod postio graff yn amlygu terfynau cymhwysedd UTF-C. Mae'n dangos bod UTF-C yn fwy effeithlon nag algorithm cywasgu cyffredinol (amrywiad o LZW) cyn belled â bod y llinyn wedi'i bacio yn fyrrach ~140 nod (fodd bynnag, sylwaf i'r gymhariaeth gael ei gwneud ar un testun; ar gyfer ieithoedd eraill gall y canlyniad fod yn wahanol).
Beic arall: rydym yn storio tannau Unicode 30-60% yn fwy cryno nag UTF-8

Ffynhonnell: hab.com

Ychwanegu sylw