zoukankan      html  css  js  c++  java
  • erlang 中文编码显示乱码问题

    许久没做erlang开发了,近期有网友问到erlang的问题。就抽时间看下。问题是这种。模块有中文。将中文直接打印出来。shell下显示会出现乱码。但假设先将中文转成binary。就行正常显示出来。

    shell中文乱码问题

    这里以一个简单的样例,说明下:

    -module(m).
    -compile(export_all).
    
    test() ->
        io:format("~ts~n", ["中国"]),
        io:format("~ts~n", [list_to_binary("中国")]).
    以R17之前的erlang版本号编译。然后測试下结果:
    Eshell V5.10.3  (abort with ^G)
    1> c(m).
    {ok, m}
    2> m:test().
    中国
    中国
    ok
    打印下erlang汇编码,这个test函数实现例如以下:
    {function, test, 0, 2}.
      {label,1}.
        {line,[{location,"erl.erl",4}]}.
        {func_info,{atom,erl},{atom,test},0}.
      {label,2}.
        {allocate,0,0}.
        {move,{literal,[[228,184,173,229,155,189]]},{x,1}}.
        {move,{literal,"~ts~n"},{x,0}}.
        {line,[{location,"erl.erl",5}]}.
        {call_ext,2,{extfunc,io,format,2}}.
        {move,{literal,[<<228,184,173,229,155,189>>]},{x,1}}.
        {move,{literal,"~ts~n"},{x,0}}.
        {line,[{location,"erl.erl",6}]}.
        {call_ext_last,2,{extfunc,io,format,2},0}.
    实际上,erlang在编译代码时会做优化。数据已知的话。list_to_binary在编译期就被优化掉了。
    所以。test函数优化后例如以下:
    test() ->
      io:format("~ts~n", [[228,184,173,229,155,189]]),
      io:format("~ts~n", [<<228,184,173,229,155,189>>]).

    io:format/2 对中文的处理

    看了 io:format/2 的实现代码,关键代码为下面两步:
    1、格式化数据: io_lib:format/2 
    2、打印到shell: io:put_chars/1
    如今用上面样例中的数据,
    3> L = io_lib:format("~ts",[<<228,184,173,229,155,189>>]).
    [[20013,22269]]
    4> io:put_chars(L).
    中国ok
    

    这里分析 io_lib:format/2 的代码,说说 ~ts 的处理过程。
    %% io_lib.erl
    
    format(Format, Args) ->
        case catch io_lib_format:fwrite(Format, Args) of
     {'EXIT',_} ->
         erlang:error(badarg, [Format, Args]);
     Other ->
         Other
        end.
    
    实现代码在 io_lib_format模块,例如以下:
    %% io_lib_format.erl
    
    fwrite(Format, Args) when is_atom(Format) ->
        fwrite(atom_to_list(Format), Args);
    fwrite(Format, Args) when is_binary(Format) ->
        fwrite(binary_to_list(Format), Args);
    fwrite(Format, Args) ->
        Cs = collect(Format, Args),   %% 收集格式化信息。生成控制结构
        Pc = pcount(Cs),              %% 计算请求打印的数量
        build(Cs, Pc, 0).             %% 解析控制结构,生成数据
    
    collect([$~|Fmt0], Args0) -> %% 格式化參数以 ~打头,否则忽略
        {C,Fmt1,Args1} = collect_cseq(Fmt0, Args0),
        [C|collect(Fmt1, Args1)];
    collect([C|Fmt], Args) ->
        [C|collect(Fmt, Args)];
    collect([], []) -> [].
    
    collect_cseq(Fmt0, Args0) ->
        {F,Ad,Fmt1,Args1} = field_width(Fmt0, Args0),
        {P,Fmt2,Args2} = precision(Fmt1, Args1),
        {Pad,Fmt3,Args3} = pad_char(Fmt2, Args2),
        {Encoding,Fmt4,Args4} = encoding(Fmt3, Args3),
        {Strings,Fmt5,Args5} = strings(Fmt4, Args4),
        {C,As,Fmt6,Args6} = collect_cc(Fmt5, Args5),
        {{C,As,F,Ad,P,Pad,Encoding,Strings},Fmt6,Args6}.
    
    %% 检查format 參数含有 t, 然后打标记 unicode。其它记latin1
    encoding([$t|Fmt],Args) ->
        true = hd(Fmt) =/= $l,  %% 确保不是传入 ~tl
        {unicode,Fmt,Args};
    encoding(Fmt,Args) ->
        {latin1,Fmt,Args}.
    再看下以上build部分的代码。代码过长,做了删节:
    %% io_lib_format.erl
    
    build([{C,As,F,Ad,P,Pad,Enc,Str}|Cs], Pc0, I) ->
        S = control(C, As, F, Ad, P, Pad, Enc, Str, I),  %% 处理控制结构
        Pc1 = decr_pc(C, Pc0),
        if
     Pc1 > 0 -> [S|build(Cs, Pc1, indentation(S, I))];
     true -> [S|build(Cs, Pc1, I)]
        end;
    build([$
    |Cs], Pc, _I) -> [$
    |build(Cs, Pc, 0)];
    build([$	|Cs], Pc, I) -> [$	|build(Cs, Pc, ((I + 8) div 8) * 8)];
    build([C|Cs], Pc, I) -> [C|build(Cs, Pc, I+1)];
    build([], _Pc, _I) -> [].
    
    
    control($w, [A], F, Adj, P, Pad, _Enc, _Str, _I) ->
        term(io_lib:write(A, -1), F, Adj, P, Pad);
    control($p, [A], F, Adj, P, Pad, Enc, Str, I) ->
        print(A, -1, F, Adj, P, Pad, Enc, Str, I);
    control($W, [A,Depth], F, Adj, P, Pad, _Enc, _Str, _I) when is_integer(Depth) ->
        term(io_lib:write(A, Depth), F, Adj, P, Pad);
    control($P, [A,Depth], F, Adj, P, Pad, Enc, Str, I) when is_integer(Depth) ->
        print(A, Depth, F, Adj, P, Pad, Enc, Str, I);
    control($s, [A], F, Adj, P, Pad, _Enc, _Str, _I) when is_atom(A) ->
        string(atom_to_list(A), F, Adj, P, Pad);
    control($s, [L0], F, Adj, P, Pad, latin1, _Str, _I) ->  %% 处理 ~s,假设数据标记是 latin1
        L = iolist_to_chars(L0),
        string(L, F, Adj, P, Pad);
    control($s, [L0], F, Adj, P, Pad, unicode, _Str, _I) -> %% 处理 ~s,假设数据标记是 unicode
        L = cdata_to_chars(L0),
        uniconv(string(L, F, Adj, P, Pad));
    control($e, [A], F, Adj, P, Pad, _Enc, _Str, _I) when is_float(A) ->
        %% 该函数太长了,不是讨论重点,做了删节
    
    cdata_to_chars([C|Cs]) when is_integer(C), C >= $00 ->
        [C | cdata_to_chars(Cs)];
    cdata_to_chars([I|Cs]) ->
        [cdata_to_chars(I) | cdata_to_chars(Cs)];
    cdata_to_chars([]) ->
        [];
    cdata_to_chars(B) when is_binary(B) -> %% 假设数据是binary,做一下unicode转换
        case catch unicode:characters_to_list(B) of
            L when is_list(L) -> L;
            _ -> binary_to_list(B)
        end.
    可想而知。假设没有不是 ~ts。或者不是binary。都不会做转换。


    探讨乱码问题

    回过头再看前面的乱码问题,相信不少人会有这3个疑问:
    1、为什么会有乱码问题
    2、为什么latin1能表示中文
    3、为什么utf8保存的代码在shell下显示乱码

    如今看下这3个问题:
    1、为什么会有乱码问题
    乱码问题的产生,是由于数据记录的字符集和显示的字符集不一样,就会有乱码问题。好比你用gbk记录,然后试图用utf8去读取。
    那为何英文、数字没问题,就中文这些有问题呢?
    这是由于。后来出现的字符集都在最早出现的标准字符集(7比特ASCII码)的基础上拓展,沿用了ASCII码对于英文、数字这些字符的编码设定。

    可是,对于拓展字符集每一个语种都有自己的定义方式,相同一段字符数据用不同的字符集就有不同的解释。

    这就是乱码出现的原因。

    所以。再后来就有unicode的出现,unicode 标准涵盖了世界上的全部字符、标点和符号,不论是哪个平台、程序或语言,unicode 都可以进行文本数据的处理、存储和交换。

    2、那为什么latin1能表示中文?
    在R17前。代码都是默认以latin1方式读取和编译的,生成的字符信息以 latin1形式保存。(这个与shell直接打印中文有本质差别)
    实际上,使用latin1无法将中文解读出来,仅仅是latin1读取数据时没有破坏原来的编码信息

    假设原文以utf8记录。显示的时候又以utf8表示,就能正常显示。


    3、为什么utf8保存的代码在shell下显示乱码
    这是由于unicode和utf8是有差别的,shell使用unicode字符集。代码使用utf8保存,假设不做转换,直接显示就会有问题。

    正是这样。io_lib:format/2 也对编码做了特殊处理,但也局限于前面所述的情况。

    顺便提一下,erlang最開始是使用latin1作为字符集,在R13A后開始支持unicode字符集。而对源码的utf8编码支持,是R16A之后,同一时候支持以utf8读写文件。

    直到R17后,erlang才将utf8做为源码的默认编码,在这之前。源码都以latin1形式读取和编译的。R17假设想改变默认编码,方法就是在模块首行加 %% coding: latin-1


    小结下。将utf8数据转换成unicode编码。方法例如以下:
    utf8_list_to_string(List) ->
        unicode:characters_to_list(list_to_binary(List)).
    那读写utf8文件呢,怎么避免出现乱码呢?
    read_and_write() ->
        {ok,Bin} = file:read_file("file.txt"),
        MyList = case unicode:characters_to_list(Bin) of
          L when is_list(L) -> L; 
          _ -> binary_to_list(Bin)
        end,
        {ok,G} = file:open("new_file.txt",[write,{encoding,utf8}]), 
        io:put_chars(G,MyList), 
        file:close(G). 

    问题到这里就告一段落了,当然。erlang中文问题还不止如此,兴许会继续讨论。

    好久没搞erlang了。突然心血来潮,写了这篇文章。希望喜欢。


    2016/3/2 补充中文乱码问题的探讨
    參考:
    http://blog.csdn.net/mycwq/article/details/50762572
    http://erlang.org/doc/apps/stdlib/unicode_usage.html
  • 相关阅读:
    Android最佳性能实践(二)——分析内存的使用情况
    Android最佳性能实践(一)——合理管理内存
    Java反射机制
    Java基础知识总结之IO流
    Java之IO流详解
    CentOS 6.4安装中文支持
    because it violates the following Content Security Policy directive: "default-src 'self'". Note that 'style-src' was not explicitly set, so 'default-src' is used as a fallback.错误应该怎么解决?
    在asp.net添加引用Microsoft.VisualBasic全过程
    SQL SERVER 2012安装介质
    Microsoft SQL Server附加数据库错误:5123
  • 原文地址:https://www.cnblogs.com/jhcelue/p/7199918.html
Copyright © 2011-2022 走看看