William Jiang

JavaScript,PHP,Node,Perl,LAMP Web Developer – http://williamjxj.com; https://github.com/williamjxj?tab=repositories

Perl Encode

转载至:http://www.cnblogs.com/starspace/archive/2008/12/10.html.
This is a very good and helpful article, I copy here for quick reference.

perl internal form

在Perl看来, 字符串只有两种形式。 一种是octets, 即8位序列, 也就是我们通常说的字节数组. 另一种utf8编码的字符串, perl管它叫string。 也就是说: Perl只认识两种编码: Ascii(octets)和utf8(string)。

utf8 flag

那么perl如何确定一个字符串是octets还是utf8编码的字符串呢? perl可没有什么智能, 他完全是靠字符串上的utf8 flag。在perl内部, 字符串结构由两部分组成: 数据和utf8 flag。比如字符串“中国”在perl内部的存储是这样:

utf8 flag 数据
On 中国

如果utf8 flag是On的话, perl就会把中国当成utf8字符串来处理, 如果utf8 flag为Off, perl就会把他当成octets来处理。所有字符串相关的函数包括正则表达式都会受utf8 flag的影响。让我们来看个例子:

use Encode;
use strict;
my $str = "中国";
Encode::_utf8_on($str);
print length($str) . "\n";
Encode::_utf8_off($str);
print length($str) . "\n";
# 运行结果是:
# 2
# 6

这里我们使用Encode模块的_utf8_on函数和_utf8_off函数来开关字符串“中国”的utf8 flag。可以看到, utf8 flag打开的时候,“中国”被当成utf8字符串处理, 所以其长度是2。 utf8 flag关闭的时候,“中国”被当成octets(字节数组)处理, 出来的长度是6(我的编辑器用的是utf8编码, 如果你的编辑器用的是gb2312编码, 那么长度应该是4)。
如何确定一个字符串的utf8 flag是否已开启? 使用Encode::is_utf8($str)。这个函数并不是用来检测一个字符串是不是utf8编码, 而是仅仅看看它的utf8 flag是否开启。

unicode转码

如果你有一个字符串“中国”, 它是gb2312编码的。 如果它的utf8 flag是关闭的, 它就会被当成octets来处理, length()会返回4, 这通常不是你想要的。 而如果你开启它的utf8 flag, 则它会被当做utf8编码的字符串来处理。由于它本来的编码是gb2312的, 不是utf8的,这就可能导致错误发生。由于gb2312和utf8内码范围部分重叠, 所以很多时候, 不会有错误报出来, 但是perl可能已经错误地拆解了字符。严重的时候, perl会报警, 说某个字节不是合法的utf8内码。

解决的方法很显然, 如果你的字符串本来不是utf8编码的, 应该先把它转成utf8编码, 并且使它的utf8 flag处于开启状态。对于一个gb2312编码的字符串, 你可以使用

$str = Encode::decode("gb2312", $str);

来将其转化为utf8编码并开启utf8 flag。如果你的字符串编码本来就是utf8, 只是utf8 flag没有打开, 那么你可以使用以下三种方式中的任一种来开启utf8 flag:

$str = Encode::decode_utf8($str);
$str = Encode::decode("utf8", $str);
Encode::_utf8_on($str);

字符串连接

点. 是字符串连接操作符。连接两个字符串时, 如果两个字符串的utf8 flag都是Off, 那么结果字符串也是Off. 如果其中任何一个字符串的utf8 flag是On的话, 那么结果字符串的utf8 flag将是On。连接字符串并不会改变它们原来的编码, 所以如果你把两个不同编码的字符串连在一起, 那么以后不管对这个字符串怎么转码, 都总会有一段是乱码。这种情况一定要避免, 连接两个字符串之前应该确保它们编码一致。如有必要, 先进行转码, 再连接字符串。

perl unicode编程基本原则

对于任何要处理的unicode字符串, 1)把它的编码转换成utf8; 2)开启它的utf8 flag

字符串来源

为了应用上面说到的基本原则, 我们首先要知道字符串本来的编码和utf8 flag开关情况, 这里我们讨论几种情况。

  1. 命令行参数和标准输入。
    从命令行参数或标准输入(STDIN)来的字符串, 它的编码跟locale有关。如果你的locale是zh_CN或zh_CN.gb2312, 那么进来的字符串就是gb2312编码, 如果你的locale是zh_CN.gbk, 那么进来的编码就是gbk, 如果你的编码是zh_CN.UTF8, 那进来的编码就是utf8。 不管是什么编码, 进来的字符串的utf8 flag都是关闭的状态。
  2. 你的源代码里的字符串。
    这要看你编写源代码时用的是什么编码。 在editplus里, 你可以通过“文件”->“另存为”查看和更改编码。 在linux下, 你可以cat一个源代码文件, 如果中文正常显示, 说明源代码的编码跟locale是一致的。源代码里的字符串的utf8 flag同样是关闭的状态。

    如果你的源代码里含有中文, 那么你最好遵循这个原则: 1) 编写代码时使用utf8编码, 2)在文件的开头加上“use utf8;”语句。这样, 你源代码里的字符串就都会是utf8编码的, 并且utf8 flag也已经打开。

  3. 从文件读入。
    这个毫无疑问, 你的文件是什么编码, 读进来就是什么编码了。读进来以后, utf8 flag是off状态。
  4. 抓取网页。
    网页是什么编码就是什么编码, utf8 flag是off状态。网站的编码可以从响应头里或者html的 <head> 标签里获得。也有可能出现响应头和html head里都没说明编码的情况, 这个就是做的很不礼貌的网页了。

输出

字符串在程序内被正确地处理后, 要展现给用户。这时我们需要把字符串从perl internal form转化成用户能接受的形式。简单地说, 就是把字符串从utf8编码转换成输出的编码或表现界面的编码。这时候, 我们使用$str = Encode::encode(‘charset’, $str);。同样可以分为几种情况。

  1. 标准输出。
    标准输出的编码跟locale一致。输出的时候utf8 flag应该关闭, 不然就会出现我们前面看到的警告:

    Wide character in print at unicode.pl line 10.

  2. GUI程序。
    这个应该是不用干什么, utf8编码, utf8 flag开启就行。没有实际测试过。
  3. http post。
    看网页表单要求什么编码。utf8 flag开或关无所谓, 因为http post发送出去的只是字符串中的数据部分, 不管utf8 flag。

PerlIO

PerlIO为我们的输入/输出转码提供了便利。它可以针对某个文件句柄, 输入的时候自动帮你转码并开启utf8 flag, 输出的时候, 自动帮你转码并关闭utf8 flag。假设你的终端locale是gb2312, 看下面的例子:

use strict;
binmode(STDIN, ":encoding(gb2312)");
binmode(STDOUT, ":encoding(gb2312)");
while (<>) {
 chomp;
 print $_, length, "\n";
}
# 运行后输入"中国", 结果: 
# 中国2

这样我们就省去了输入和输出时转码的麻烦。PerlIO可以作用于任何文件句柄, 具体请参考perldoc PerlIO。

相关API

都是’use Encode’模块的:

  • $octets = encode(ENCODING, $string [, CHECK])
    把字符串从utf8编码转成指定的编码, 并关闭utf8 flag。
  • $string = decode(ENCODING, $octets [, CHECK])
    把字符串从其他编码转成utf8编码, 并开启utf8 flag, 不过有个例外就是, 如果字符串是仅仅ascii编码或EBCDIC编码的话, 不开启utf8 flag。
  • is_utf8(STRING [, CHECK])
    看看utf8 flag是否开启。如果第二个参数为真, 则同时检查编码是否符合utf8。这个检测不一定准确, 跟decode方式检测效果一样。
  • _utf8_on(STRING)
    打开字符串的utf flag
  • _utf8_off(STRING)
    关闭字符串的utf flag

utf8和utf-8

前面我们提到的一直都是utf8。在perl中, utf8和utf-8是不一样的。utf-8是指国际上标准的utf-8定义, 而utf8是perl在国际标准上做了一些扩展, 能兼容的内码要比国际标准的多一些。perl internal form使用的是utf8。另外顺便提一下, 字符集的名称是不区分大小写的并且“_”和“-”是等价的。

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: