最近在使用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>
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