erlang Unicode 處理

最近在使用erlang作遊戲服務器,而字符串在服務器編程中的地位是十分重要的,因而便想仔細研究下字符編碼,以及erlang下的字符串處理。先從Unicode開始吧....html

【Unicode】shell

Unicode標準肯定世界絕大部分的字符編碼值(code point),而對於code point的編碼方式有不少種,這些編碼方式稱爲UTF。咱們須要區分開Unicode字符與Unicode編碼,Unicode字符指的是Unicode標準中定義的編碼值,這個值表明世界上獨一無二的一個字符,而Unicode編碼則是這些值在計算機中的表達方式,好比UTF-8就是Unicode的一種編碼。這裏咱們主要談論下Unicode編碼在erlang下的處理,先談談如下幾種編碼吧...macos

(1)單字節編碼編程

使用單個字節來保存字符編碼,這種編碼不能算是Unicode編碼,由於它在Unicode標準出現前就有了,但他仍是能夠表示Unicode中編碼小於256的符號。這部分字符集對應ISO-Latin-1字符集,在erlang中它就是latin1編碼,這裏的ISO-Latin-1和latin1不是同一個東東,一個是字符集,一個是編碼.windows

(2)UTF-8服務器

多字節編碼,它使用1到4個字節來進行編碼,也就是說UTF-8是變長的,使用的字節數會根據字符的編碼大小而變化.它兼容按單字節編碼的7bitASCII字符,由於這些字符在UTF-8中也只須要佔據1個字節。當編碼值大於127時UTF-8將使用多個字節來保存,而且在第一個字節裏使用幾個位來標註該字符是多字節的。所以UTF-8與大於127的單字節編碼並不兼容,因此latin1與UTF-8並非徹底兼容的。app

(3)UTF-16async

它與UTF-8類似,也是多字節編碼,只是它的基本單位是16位,也就是說全部Unicode字符至少佔用2個字節,編碼數大甚至會使用4個字節。一些系統和程序只容許UTF-16使用2個字節,由於它基本足夠表達大多數字符了。固然因爲基本單位大於了1個字節,UTF-16就存在大編,小編兩個變種。函數

(4)UTF-32,UCS-4字體

最直接的編碼方式,使用4個字節來保存字符。與UTF-16同樣,存在大編,小編兩個變種。

研究到這裏,我產生了一個疑問,UTF-8和UTF16,32同樣都是多字節編碼,爲何UTF-8就不存在大編小編的變種呢?要解決這個問題,必須得明白UTF-8的具體編碼方式才行,因而google...

如上圖所示,UTF-8在表示128如下的字符時,使用一個字節,而且最高位是0,表示該字符是單字節。當須要多字節時,首位字節的高位由一個或多個1佔據,而且1後面跟隨一個0,來與code point的字節位分隔,後面的字節以10開始,10後面是code point的字節位。首位字節的1的個數表示了這個字符須要的字節數,這樣處理字符的程序就不要去查看後面以10開始的字節數量,便知道了該字符須要的字節數是多少。

好了,瞭解了UTF-8的具體編碼方式了,但仍是沒有解決以前的那個問題,就是UTF-8爲何沒有大小編的變種?仔細研究了後,發現UTF-8還有一個約定,就是編碼的高字節位在首字節,後面接着的字節依次保存後面的編碼字節位。這樣就約定了UTF-8固定的編碼字節保存方式。而UTF-16,UTF-32並無約定本身的編碼保存方式,他們依賴與編碼保存的環境,因此他們須要區別大小編。

UTF-16,32在文本流的第一個字符前,使用BOM(bytes order mark)來區別大小編。以UTF-16爲例,它的BOM是FEFF,若是編碼方編碼的是FEFF,而解碼方解碼獲得的是FFFE,那說明大小編不一致,就須要對解碼的字節進行處理。固然解碼方不處理BOM,那麼FEFF會被看成ZWNBSP character處理,估計就是什麼都不作的意思。BOM除了用來區分大小編碼外,還能夠區分編碼類型,好比UTF-8的BOM是EF BB BF。

【erlang字符串與Unicode處理】

瞭解了Unicode,接着談談erlang下對字符串和Unicode的處理。在erlang下,沒有單獨的string類型,它使用list包含一組int來表示,一個int表明一個字符編碼。在R13版本以前,它使用ISO-latin-1 (ISO8859-1)字符集來編碼,在R13以後擴展到Unicode。list的表示方式很容易擴展到Unicode字符集,由於它使用一個int來表示字符。但若是要將list轉換成二進制類型,Unicode就會有點問題。當code point < 256 時,字符是單字節編碼,erlang可使用latin1,那麼code point在內存中的表示是一個挨着一個的,這樣就能夠直接使用erlang:iolist_to_binary/1將list轉換到二進制數據。(在erlang中會使用這種方式來處理字符串,由於list是使用一個int來表示字符串,當只須要單字節編碼時,用二進制類型保存更節約空間) 可是當code point >= 256時,轉換成二進制時,就須要肯定Unicode的編碼方式,不能直接使用erlang:iolist_to_binary/1接口,須要使用unicode:characters_to_binary/{1,2,3}來轉換。默認狀況下,轉換時erlang使用UTF-8做爲標準的Unicode編碼。

  • 在R16B版本以後,erlang容許使用UTF-8來對源代碼編碼,默認下使用latin1編碼。能夠經過在源代碼文件前加入 %% -*- coding: utf-8 -*- 來設置編碼方式。string和註釋可使用UTF-8,但函數名和atom仍是使用ISO-latin-1字符集,這個有可能在R18改變。

  • 二進制類型的位語法中也加入了對Unicode的處理:

<<Ch/utf8,_/binary>> = Bin1,

<<Ch/utf16-little,_/binary>> = Bin2,

Bin3 = <<$H/utf32-little, $e/utf32-little, $l/utf32-little, $l/utf32-little, $o/utf32-little>>,

Bin4 = <<"Hello"/utf16>>

很是方便。。
  • erlang的輸出函數會啓發式的檢測輸入的list,binariy是不是能夠打印的字符。默認檢測的字符範圍是ISO-Latin-1,也能夠在啓動時經過+pc指定爲UTF-8。對於io(_lib):format/2函數,也可使用~tp來被指定範圍影響。

指定範圍爲Latin $ erl +pc latin1

Erlang R16B (erts-5.10.1) [source] [async-threads:0] [hipe] [kernel-poll:false] Eshell V5.10.1 (abort with ^G)

1> [1024].

[1024]

2> [1070,1085,1080,1082,1086,1076].

[1070,1085,1080,1082,1086,1076]

3> [229,228,246].

"åäö"

4> <<208,174,208,189,208,184,208,186,208,190,208,180>>.

<<208,174,208,189,208,184,208,186,208,190,208,180>>

5> <<229/utf8,228/utf8,246/utf8>>.

<<"åäö"/utf8>>

1> io:format("~tp~n",[{<<"åäö">>, <<"åäö"/utf8>>, <<208,174,208,189,208,184,208,186,208,190,208,180>>}]).

{<<"åäö">>,<<"åäö"/utf8>>,<<208,174,208,189,208,184,208,186,208,190,208,180>>}

指定範圍爲UTF-8
$ erl +pc unicode

Erlang R16B (erts-5.10.1) [source] [async-threads:0] [hipe] [kernel-poll:false] Eshell V5.10.1 (abort with ^G)

1> [1024].

"Ѐ"

2> [1070,1085,1080,1082,1086,1076].

"Юникод"

3> [229,228,246].

"åäö"

4> <<208,174,208,189,208,184,208,186,208,190,208,180>>.

<<"Юникод"/utf8>>

5> <<229/utf8,228/utf8,246/utf8>>.

<<"åäö"/utf8>>
1> io:format("~tp~n",[{<<"åäö">>, <<"åäö"/utf8>>, <<208,174,208,189,208,184,208,186,208,190,208,180>>}]).

{<<"åäö">>,<<"åäö"/utf8>>,<<"Юникод"/utf8>>}
  • erlang shell的交互Console也能夠支持Unicode和輸入和輸出,在windows上首先要確認是否有合適的字體,若是沒有可使用DejaVu。在Unix系統下,須要檢查Console是否支持Unicode,經過echo $LANG或者$LC_CTYPE來察看,還能夠經過io:getopts()來檢查當前Console使用的編碼。

$ LC_CTYPE=en_US.ISO-8859-1

erl Erlang R16B (erts-5.10.1) [source] [async-threads:0] [hipe] [kernel-poll:false] Eshell V5.10.1 (abort with ^G)

1> lists:keyfind(encoding, 1, io:getopts()).

{encoding,latin1}

2> q().

ok

$ LC_CTYPE=en_US.UTF-8

erl Erlang R16B (erts-5.10.1) [source] [async-threads:0] [hipe] [kernel-poll:false] Eshell V5.10.1 (abort with ^G)

1> lists:keyfind(encoding, 1, io:getopts()).

{encoding,unicode}

2>
  • 不過雖然字符串變量可以被視爲Unicode進行處理,可是erlang的代碼仍是被限制在ISO-latin-1字符集
2> Юникод.

* 1: illegal character
  • 接下來是關於erlang對文件命名不一樣編碼的處理:

  目前的操做系統,都支持Unicode編碼的文件命名,但不一樣的操做系統對文件命名編碼有不一樣的約定,因此erlang也有不一樣的處理。對於windows和macos,全部文件命名的編碼都強制性要求使用Unicode,macos要求UTF-8,雖然windows使用特殊的Unicode變種,但二者效果是相同。而Unix下面並非強制要求使用Unicode編碼,只是約定而已,那麼在Unix下存在有多種編碼的可能性,好比說一個文件名包含的字符串code point 屬於128到255之間,能夠編碼成ISO-lation-1也能夠編碼成UTF-8(128如下的code point 二者是相兼容的,具體參看前面Unicode的編碼介紹)。所以在windows和macos下,Erlang VM的默認行爲是工做在"Unicode file name translation mode"下面,在這個模式下,全部的文件名都會以一個Unicode的list返回,而且自動轉換成合適的編碼傳入到底層的文件系統。在Unix下沒有自動打開這個模式,默認使用ISO-Latin-1,那麼若是文件名編碼是使用的UTF-8,那麼就會返回「raw file names」,也就是一組包含編碼的整型list.(好比使用list_dir_all/1函數時)咱們能夠經過在VM運行時,加入+fnu來打開"Unicode file name translation mode",默認下它會使用 latin1做爲文件名編碼,也可使用file:native_name_encoding/0 來返回當前文件名使用的編碼。

http://www.erlang.org/doc/apps/stdlib/unicode_usage.html
相關文章
相關標籤/搜索