• erlang Unicode 处理


    最近在使用erlang做游戏服务器,而字符串在服务器编程中的地位是十分重要的,于是便想仔细研究下字符编码,以及erlang下的字符串处理。先从Unicode开始吧....

    【Unicode】

    Unicode标准确定世界绝大部分的字符编码值(code point),而对于code point的编码方式有很多种,这些编码方式称为UTF。我们需要区分开Unicode字符与Unicode编码,Unicode字符指的是Unicode标准中定义的编码值,这个值代表世界上独一无二的一个字符,而Unicode编码则是这些值在计算机中的表达方式,比如UTF-8就是Unicode的一种编码。这里我们主要谈论下Unicode编码在erlang下的处理,先谈谈以下几种编码吧...

    (1)单字节编码

    使用单个字节来保存字符编码,这种编码不能算是Unicode编码,因为它在Unicode标准出现前就有了,但他还是可以表示Unicode中编码小于256的符号。这部分字符集对应ISO-Latin-1字符集,在erlang中它就是latin1编码,这里的ISO-Latin-1和latin1不是同一个东东,一个是字符集,一个是编码.

    (2)UTF-8

    多字节编码,它使用1到4个字节来进行编码,也就是说UTF-8是变长的,使用的字节数会根据字符的编码大小而变化.它兼容按单字节编码的7bitASCII字符,因为这些字符在UTF-8中也只需要占据1个字节。当编码值大于127时UTF-8将使用多个字节来保存,并且在第一个字节里使用几个位来标注该字符是多字节的。因此UTF-8与大于127的单字节编码并不兼容,所以latin1与UTF-8并不是完全兼容的。

    (3)UTF-16

    它与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

     






  • 相关阅读:
    大数据离线分析平台 JavaSDK数据收集引擎编写
    大数据离线分析平台 需求分析(三)
    大数据离线分析平台 需求分析(二)
    大数据离线分析平台 需求分析(一)
    Hive 严格模式与非严格模式
    MapReduce案例:统计共同好友+订单表多表合并+求每个订单中最贵的商品
    Hadoop 目录分析及存储机制
    zookeeper 启动显示started,jps查看进程却没有,解决方法
    Azkaban介绍+安装部署+实战案例
    Flume 高可用配置案例+load balance负载均衡+ 案例:日志的采集及汇总
  • 原文地址:https://www.cnblogs.com/pbblog/p/3366932.html
Copyright © 2020-2023  润新知