erlang 中文编码显示乱码问题

Posted 养码青年

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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([$\n|Cs], Pc, _I) -> [$\n|build(Cs, Pc, 0)];

build([$\t|Cs], Pc, I) -> [$\t|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 >= $\000 ->  

[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 中文编码显示乱码问题的主要内容,如果未能解决你的问题,请参考以下文章

如何解决eclipse显示中文乱码问题

如何解决HTML网页中文显示乱码

请问Eclipse中源码的中文部分都显示成乱码如何解决?

Visual Studio Code 1.44 解决中文代码显示乱码问题(小白图文教程)

PHP界面显示中文乱码

解决windows文件在linux系统中显示乱码的问题