6-二進位制資料-字串-字元列表

StraightDave發表於2014-09-11

6-二進位制資料-字串-字元列表

UTF-8和Unicode
二進位制(和bitstring)
字元列表

在“基本型別”一章中介紹了字串,以及如何使用is_binary/1函式來檢查它:

iex> string = "hello"
"hello"
iex> is_binary string
true

本章將學習理解,二進位制資料(binaries)是個啥,它怎麼和字串扯上關係的,以及用單引號包裹的值,'像這樣',是啥意思。

6.1-UTF-8和Unicode

Elixir中,字串就是UTF-8編碼的二進位制資料(binaries)。為了弄清這句話啥意思,我們要先理解兩個概念:bytes和code point的區別。

“二進位制資料”(binaries)這個翻譯不一定準確。它其實就是以二進位制為內容的列表。下文有些地方可能會簡單寫成“二進位制”。如有模糊的地方,一般也會用原文標出。
而bytes和code points就不怎麼翻譯了(bytes有時會翻譯成位元組),可根據上下文語境理解。

字母a的code point是97,而字母ł的code point是322。
當把字串"hełło"寫到硬碟上的時候,需要將其code point轉化為位元組(bytes)。 如果一個位元組對應一個code point,那是儲存不了"hełło"的,因為字母ł的code point是322,超過了一個位元組所能儲存的最大數值(255)。
但是如你所見,該字母能夠顯示到螢幕上,說明還是有一定的解決方法的。方法就是編碼

要用位元組表示code point,我們需要在一定程度上對其進行編碼。 Elixir使用UTF-8為預設編碼格式。 當我們說,某個字串是UTF-8編碼的二進位制資料(binaries),這句話意思是該字串是一串位元組,以一定方法組織,來表示特定的一串code points。

因此當我們儲存字母ł的時候,實際上是用兩個位元組來表示它。 這就是為什麼有時候對同一字串呼叫函式byte_size/1String.length/1結果不一樣:

iex> string = "hełło"
"hełło"
iex> byte_size string
7
iex> String.length string
5

UTF-8需要1個位元組來表示code points:‘h’,‘e’和‘o’各用一個,而‘ł’用2個位元組。 在Elixir中,可以使用?運算子獲取某字元的code point值:

iex> ?a
97
iex> ?ł
322

你還可以使用String模組裡的函式,將字串切成單獨的code points:

iex> String.codepoints("hełło")
["h", "e", "ł", "ł", "o"]

Elixir為字串操作提供了強大的支援。實際上,Elixir通過了文章“字串型別破了”記錄的所有測試。

不僅如此,因為字串是二進位制資料,Elixir還提供了更強大的底層型別的操作。下面就來介紹該底層型別---二進位制資料。

6.2-二進位制資料(和bitstring)

在Elixir中可以用<<>>定義一個二進位制資料:

iex> <<0, 1, 2, 3>>
<<0, 1, 2, 3>>
iex> byte_size <<0, 1, 2, 3>>
4

一個二進位制只是一連串位元組。這些位元組可以以任何方法組織,即使湊不成一個合法的字串:

iex> String.valid?(<<239, 191, 191>>)
false

字串的拼接操作實際上是二進位制的拼接操作:

iex> <<0, 1>> <> <<2, 3>>
<<0, 1, 2, 3>>

一個常見技巧是,通過給某字串尾部拼接一個空位元組<<0>>,來看看該字串內部二進位制的樣子:

iex> "hełło" <> <<0>>
<<104, 101, 197, 130, 197, 130, 111, 0>>

二進位制中的每個數值都表示一個byte,因此其最大是255。 如果超出了255,二進位制允許你再提供一個修飾符,標識一下那個位置的儲存空間大小,使其可以滿足儲存要求。或者使用修飾符將其轉換為utf8編碼後的形式(變成多個位元組的二進位制),再儲存:

iex> <<255>>
<<255>>
iex> <<256>> # truncated
<<0>>
iex> <<256 :: size(16)>> # use 16 bits (2 bytes) to store the number
<<1, 0>>
iex> <<256 :: utf8>> # the number is a code point
"Ā"
iex> <<256 :: utf8, 0>>
<<196, 128, 0>>

如果一個byte是8 bits,那如果我們給一個size是1 bit的修飾符會怎樣?:

iex> <<1 :: size(1)>>
<<1::size(1)>>
iex> <<2 :: size(1)>> # truncated
<<0::size(1)>>
iex> is_binary(<< 1 :: size(1)>>)
false
iex> is_bitstring(<< 1 :: size(1)>>)
true
iex> bit_size(<< 1 :: size(1)>>)
1

這樣(每個元素是1 bit)就不再是二進位制資料(人家每個元素是byte,至少8 bits)了,而是bitstring,就是一串位元! 所以實際上二進位制資料(binary)就是一串位元(bitstring),只是它容納的位元總數必須是8的倍數。

我們也可以對二進位制資料或bitstring做模式匹配:

iex> <<0, 1, x>> = <<0, 1, 2>>
<<0, 1, 2>>
iex> x
2
iex> <<0, 1, x>> = <<0, 1, 2, 3>>
** (MatchError) no match of right hand side value: <<0, 1, 2, 3>>

注意(沒有修改器標識的情況下)二進位制資料中的每個元素都應該匹配8 bits。 因此上面最後的例子,匹配的左右兩端不具有相同容量,因此出現錯誤。

下面是使用了修飾符標識的匹配例子:

iex> <<0, 1, x :: binary>> = <<0, 1, 2, 3>>
<<0, 1, 2, 3>>
iex> x
<<2, 3>>

上面的模式僅在二進位制尾部元素被修改器標識為又一個二進位制時才正確。 字串的連線操作也是一個意思:

iex> "he" <> rest = "hello"
"hello"
iex> rest
"llo"

總之,記住字串是UTF-8編碼的二進位制資料,而二進位制資料是特殊的、bit數量是8的倍數的bitstring。 這種機制增加了Elixir在處理bits或bytes時的靈活性。 現實中99%的時候你會用到is_binary/1byte_size/1函式,來跟二進位制資料打交道。

6.3-字元列表

字元列表就是字元的列表。 雙引號包裹字串,單引號包裹字元列表。

iex> 'hełło'
[104, 101, 322, 322, 111]
iex> is_list 'hełło'
true
iex> 'hello'
'hello'

字元列表儲存的不是位元組,而是字元的code points(實際上就是這些code points的普通列表)。 如果某字元不屬於ASCII範圍,iex就列印它的code point。

實際應用中,字元列表常被用來做一些老的庫,或者同Erlang平臺互動時使用的引數。因為這些老庫不接受二進位制資料作為引數。
將字元列表和字串之間轉換,使用函式to_string/1to_char_list/1

iex> to_char_list "hełło"
[104, 101, 322, 322, 111]
iex> to_string 'hełło'
"hełło"
iex> to_string :hello
"hello"
iex> to_string 1
"1"

注意這些函式是多型的。它們不但轉化字元列表和字串,還能轉化字串和整數,等等。

相關文章