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
  • 相关阅读:
    Java 基础
    Java 数据类型
    Spring 拦截器实现事物
    SSH 配置日记
    Hibernate 知识提高
    Jsp、Servlet
    leetcode 97. Interleaving String
    leetcode 750. Number Of Corner Rectangles
    leetcode 748. Shortest Completing Word
    leetcode 746. Min Cost Climbing Stairs
  • 原文地址:https://www.cnblogs.com/jhcelue/p/7199918.html
Copyright © 2011-2022 走看看