bitstring և binary տվյալների տիպերը
String-ները մեր օրինակներում արդեն բազմիցս հանդիպել են՝ "hello", "world" և այլն։ Այս հոդվածում կպարզենք, թե ի՛նչ է string-ն իրականում։ Դրա համար նախ պետք է ծանոթանանք Elixir-ի երկու հիմնարար տիպերի՝ bitstring-ի և binary-ի հետ։
Եթե աշխատել եք Node.js-ով, հավանաբար ծանոթ եք Buffer տիպին. այն բայթերի հաջորդականություն է, որն օգտագործվում է, օրինակ, ֆայլեր կարդալիս։ JavaScript-ում string-ը և Buffer-ը երկու տարբեր աշխարհներ են։ Elixir-ում այդպես չէ. այստեղ string-ը հենց ինքն է բայթերի հաջորդականություն։ Հիմա տեսնենք, թե դա ինչ է նշանակում։
Բիթ և բայթ
Համակարգիչը ցանկացած տվյալ պահում է բիթերի տեսքով։ Մեկ բիթը կարող է ունենալ ընդամենը երկու արժեք՝ 0 կամ 1։ Ութ բիթը միասին կազմում են մեկ բայթ։ Մեկ բայթով կարելի է ներկայացնել 256 տարբեր արժեք (2⁸ = 256), այսինքն՝ 0-ից 255 թվերը։
Թվային տիպերի հոդվածում ծանոթացանք Elixir-ի թվերին։ Երկուական համակարգով թիվ գրելու համար օգտագործվում է 0b նախածանցը.
iex> 0b101
5
5 թիվը երկուական համակարգում 101 է։ Այս գիտելիքը հիմա պետք է գալու։
Ի՞նչ է bitstring-ը
Bitstring-ը (բառացի՝ բիթերի շարան) Elixir-ի հիմնարար տիպերից է. այն բիթերի անընդհատ հաջորդականություն է հիշողության մեջ։ Bitstring ստեղծվում է <<>> գրելաձևով.
iex> <<42>>
<<42>>
Սովորաբար ամեն արժեքի հատկացվում է 8 բիթ, այսինքն՝ մեկ բայթ.
iex> <<42>> == <<42::8>>
true
::8 գրառումը ցույց է տալիս, թե քանի բիթ է հատկացված արժեքին։ Հիշենք, որ մեկ բայթի ամենամեծ արժեքը 255-ն է։ Ի՞նչ կլինի, եթե փորձենք պահել ավելի մեծ թիվ.
iex> <<257>>
<<1>>
257-ը մեկ բայթում չի տեղավորվում, և չտեղավորված մասը պարզապես դեն է նետվում՝ առանց սխալի մասին զգուշացնելու։ Անգլերեն այս երևույթը կոչվում է truncation (կտրում)։ Մնում է միայն այն, ինչ տեղավորվել է բայթի մեջ. 257 − 256 = 1։
Լուծումը պարզ է. մեծ թվերին պետք է ավելի շատ բիթ հատկացնել։ 16 բիթը երկու բայթ է, և 257-ն այնտեղ արդեն ազատ տեղավորվում է.
iex> <<257::16>>
<<1, 1>>
Ստացանք երկու բայթ՝ 1 և 1։ Ինչո՞ւ հենց այդպես. առաջին բայթը «256-ականներն» են, երկրորդը՝ մնացորդը. 1 × 256 + 1 = 257։ Սա նույն սկզբունքն է, ինչ սովորական թվերում. 25-ի մեջ 2-ը տասնյակներն են, 5-ը՝ միավորները։
Ի՞նչ է binary-ն
Binary-ն bitstring է, որն ամբողջությամբ կազմված է բայթերից, այսինքն՝ նրա բիթերի քանակը բաժանվում է 8-ի։ Մեր մինչ այժմ տեսած բոլոր օրինակները հենց binary են.
iex> is_bitstring(<<1, 2, 3>>)
true
iex> is_binary(<<1, 2, 3>>)
true
iex> is_binary(<<257::16>>)
true
<<1, 2, 3>>-ը 3 բայթ է (24 բիթ), <<257::16>>-ը՝ 2 բայթ (16 բիթ). երկուսի բիթերի քանակն էլ բաժանվում է 8-ի։
Իսկ ինչո՞ւ են փաստացի մեկ տվյալների տիպի համար երկու անվանում օգտագործում։ Bitstring-ն ավելի ընդհանուր տիպ է. այն թույլ է տալիս աշխատել նաև ոչ ամբողջական բայթերի հետ։ Դա պետք է գալիս հազվադեպ, ցածր մակարդակի խնդիրներում. օրինակ՝ որոշ ցանցային արձանագրություններում (network protocols) մեկ բայթի մեջ մի քանի փոքր դաշտ է խտացված, և դրանք կարդալու համար բայթը պետք է «բացել» բիթերի մակարդակով։ Այս թեմայի մեջ հիմա չենք խորանա. սովորելու ողջ ընթացքում գործ կունենաք միայն ամբողջական բայթերի, այսինքն՝ binary-ների հետ։
Հիշեք միայն կանոնը. ամեն binary bitstring է, բայց ոչ ամեն bitstring է binary։
String-երը binary են
Հիմա ամենահետաքրքիր մասը.
iex> is_binary("hello")
true
Այո՛, string-ը Elixir-ում պարզապես binary է, որի բայթերը տեքստ են ներկայացնում։ "hello" բառի ամեն տառ զբաղեցնում է մեկ բայթ.
iex> byte_size("hello")
5
iex> String.length("hello")
5
byte_size/1 ֆունկցիան հաշվում է բայթերը, String.length/1-ը՝ նշանները (տառերը)։ Անգլերեն տեքստի դեպքում դրանք համընկնում են։ Բայց միշտ չէ, որ այդպես է։
Unicode և code point-եր
Որպեսզի բոլոր համակարգիչները տեքստը հասկանան նույն կերպ, ստեղծվել է Unicode ստանդարտը. այն աշխարհի գրեթե բոլոր նշաններին տալիս է իր հերթական համարը, որը կոչվում է code point։ Elixir-ում նշանի code point-ը կարելի է ստանալ ? գրելաձևով.
iex> ?a
97
iex> ?ö
246
iex> ?Ա
1329
UTF-8 կոդավորումը
Code point-երը թվեր են, իսկ թվերը պետք է ինչ-որ կերպ պահվեն բայթերում։ Այդ կանոնները սահմանում է կոդավորումը (encoding)։ Elixir-ի string-երը կոդավորված են UTF-8-ով։
UTF-8-ի հիմնական գաղափարը սա է. մինչև 127 code point ունեցող նշանները (անգլերեն տառերը, թվանշանները, կետադրական նշանները) զբաղեցնում են մեկ բայթ, մնացած բոլորը՝ երկու, երեք կամ չորս բայթ։
Վերցնենք գերմաներեն schön («գեղեցիկ») բառը։ Գերմաներենն ունի հատուկ տառեր՝ ö, ü, ä, ß։ Դրանց code point-երը 127-ից մեծ են. օրինակ՝ ö-ինը, ինչպես տեսանք վերևում, 246 է։ Ուստի UTF-8-ում այդ տառերը զբաղեցնում են երկու բայթ.
iex> String.length("schön")
5
iex> byte_size("schön")
6
Հինգ տառ, բայց վեց բայթ. s, c, h, n տառերը մեկական բայթ են, իսկ ö-ն՝ երկու։
Հիմա փորձենք հայերեն։ Հայերեն բոլոր տառերի code point-երը նույնպես 127-ից մեծ են, ուստի ամեն տառ UTF-8-ում զբաղեցնում է երկու բայթ.
iex> String.length("Բարև")
4
iex> byte_size("Բարև")
8
Չորս տառ՝ ութ բայթ։
Binary-ների միակցումը
Binary-ները միակցվում են (concatenation) <> օպերատորով.
iex> <<1, 2>> <> <<3, 4>>
<<1, 2, 3, 4>>
iex> "hello" <> " " <> "world"
"hello world"
Նկատեցի՞ք. string-ները «միակցելու» օպերատորն իրականում binary-ների միակցման օպերատորն է։ Դա տրամաբանական է, քանի որ string-երը binary են։
<>-ի օգնությամբ կարող ենք տեսնել string-ի իրական բայթերը։ Բանն այն է, որ iex-ը binary-ն ցույց է տալիս որպես string, երբ նրա բոլոր բայթերը վավեր տեքստ են կազմում։ Եթե վերջում ավելացնենք 0 բայթը, binary-ն այլևս վավեր string չի լինի, և iex-ը ցույց կտա բայթերն այնպես, ինչպես կան.
iex> "Բարև" <> <<0>>
<<212, 178, 213, 161, 214, 128, 214, 135, 0>>
Ահա թե ինչ տեսք ունի «Բարև» բառը հիշողության մեջ. ութ բայթ՝ գումարած մեր ավելացրած 0-ն։ 212, 178 զույգը Բ տառն է, 213, 161-ը՝ ա, և այսպես շարունակ։
Առաջին ծանոթությունը pattern matching-ի հետ
Elixir-ում = նշանը սովորական վերագրում չէ, ինչպես լայնորեն տարածված օբյեկտ կողնորոշված ծրագրավորման լեզուներում. այն կոչվում է match operator և շատ ավելի հզոր գործիք է։ Pattern matching-ը մեծ թեմա է, որին առանձին հոդված ենք նվիրելու։ Բայց առանց գոնե մեկ պարզ օրինակի այս հոդվածը թերի կլիներ, որովհետև հենց pattern matching-ն է binary-ների հետ աշխատանքը դարձնում այդքան հարմար։
Ենթադրենք ունենք գույն RGB ձևաչափով՝ երեք բայթ. կարմիր, կանաչ, կապույտ.
iex> color = <<255, 99, 71>>
<<255, 99, 71>>
iex> <<red, green, blue>> = color
<<255, 99, 71>>
iex> red
255
iex> green
99
iex> blue
71
Մեկ տողով binary-ի երեք բայթերը «բացվեցին» երեք փոփոխականների մեջ։ Սա նման է JavaScript-ում օգտագործվող destructuring assignment-ին։
Եթե այս օրինակը դեռ լիովին պարզ չէ, մի անհանգստացեք. pattern matching-ին մանրամասն կանդրադառնանք շատ շուտով։
Մի քանի խոսք charlist-ի մասին
Elixir-ում երբեմն կհանդիպեք նաև charlist տիպին, որը գրվում է ~c նախածանցով՝ ~c"hello"։ Ի տարբերություն string-ի՝ դա binary չէ, այլ code point-երի ցուցակ (list)։ Charlist-ները գործնականում հազվադեպ են պետք գալիս. հիմնականում հին Erlang գրադարանների հետ աշխատելիս։ Առայժմ բավական է հիշել. "hello"-ն string է, իսկ ~c"hello"-ն՝ բոլորովին այլ տիպ։
Փորձեք ինքներդ
Բացեք iex-ը և փորձեք հետևյալը։
- Գրեք ձեր անունը հայերենով և հաշվեք
String.length-ն ուbyte_size-ը։ Համոզվեք, որ բայթերը տառերից երկու անգամ շատ են։ - Ձեր անվանը կցեք
<> <<0>>և տեսեք, թե ինչ բայթերից է այն կազմված։ - Ստեղծեք ձեր գույնը
<<r, g, b>>տեսքով և pattern matching-ով առանձնացրեք երեք բաղադրիչները։