perlunitut - Perl Unicode 教程
仅仅抛出字符串的日子已经一去不复返了。众所周知,现代程序需要能够交流带有滑稽口音的字母,以及欧元符号等内容。这意味着程序员需要养成新的习惯。编写支持 Unicode 的软件很容易,但正确地编写它确实需要纪律。
关于字符集和文本编码有很多知识需要了解。最好花一整天时间来学习所有这些知识,但可以在几分钟内学习基础知识。
不过,这些不是最基本的知识。我们假设您已经知道字节和字符之间的区别,并且意识到(并接受!)有许多不同的字符集和编码,并且您的程序必须明确说明它们。推荐阅读 Joel Spolsky 在 http://joelonsoftware.com/articles/Unicode.html 上发表的“每个软件开发人员绝对、肯定必须了解的有关 Unicode 和字符集的绝对最低限度(没有借口!)”。
本教程以相当绝对的术语进行讲解,并且仅提供了 Perl 提供的丰富字符字符串相关功能的有限视图。对于大多数项目来说,这些信息可能就足够了。
首先明确一些事情非常重要。这是本教程最重要的部分。此视图可能与您在网络上找到的其他信息相冲突,但这主要是因为许多来源都是错误的。
您可能需要多次重新阅读本节...
Unicode 是一个字符集,可以容纳大量字符。字符的序数值称为码点。(但在实践中,码点和字符之间的区别是模糊的,因此这些术语通常可以互换使用。)
有许多、许多码点,但计算机使用字节,而一个字节只能容纳 256 个值。Unicode 的字符比这多得多,因此您需要一种方法来使这些字符可访问。
Unicode 使用几种竞争性编码进行编码,其中 UTF-8 使用最广泛。在 Unicode 编码中,可以使用多个后续字节来存储单个码点,或简单来说:字符。
UTF-8 是一种 Unicode 编码。许多人认为 Unicode 和 UTF-8 是同一回事,但事实并非如此。还有更多的 Unicode 编码,但世界上大部分地区已将 UTF-8 标准化。
UTF-8 将前 128 个码点(0..127)与 ASCII 相同处理。每个字符仅占用一个字节。所有其他字符都使用复杂方案编码为两个到四个字节。幸运的是,Perl 为我们处理了这一点,因此我们不必担心这一点。
文本字符串或字符字符串由字符组成。字节在这里无关紧要,编码也是如此。每个字符就是:字符。
在文本字符串上,您可以执行以下操作
$text =~ s/foo/bar/;
if ($string =~ /^\d+$/) { ... }
$text = ucfirst $text;
my $character_count = length $text;
字符的值(ord
、chr
)是相应的 Unicode 代码点。
二进制字符串或字节字符串由字节构成。在这里,您没有字符,只有字节。与外部世界(当前 Perl 进程之外的任何内容)的所有通信都以二进制方式进行。
在二进制字符串上,您会执行类似以下操作
my (@length_content) = unpack "(V/a)*", $binary;
$binary =~ s/\x00\x0F/\xFF\xF0/; # for the brave :)
print {$fh} $binary;
my $byte_count = length $binary;
编码(作为动词)是从文本到二进制的转换。要进行编码,您必须提供目标编码,例如 iso-8859-1
或 UTF-8
。某些编码(如 iso-8859
(“latin”) 范围)不支持完整的 Unicode 标准;无法表示的字符在转换中丢失。
解码是从二进制到文本的转换。要进行解码,您必须知道在编码阶段使用了什么编码。最重要的是,它必须是可以解码的。将 PNG 图像解码为文本字符串没有什么意义。
Perl 具有内部格式,它是一种用于对文本字符串进行编码的编码,以便可以将它们存储在内存中。所有文本字符串都采用这种内部格式。事实上,文本字符串永远不会采用任何其他格式!
您不必担心这种格式是什么,因为在解码或编码时会自动进行转换。
在您的标准标题中添加以下行
use Encode qw(encode decode);
或者,如果您懒惰,只需
use Encode;
程序的典型输入/输出流是
1. Receive and decode
2. Process
3. Encode and output
当然,如果您的输入是二进制的,并且应该保持二进制,则不应将其解码为文本字符串。但在所有其他情况下,您都应该对其进行解码。
如果您不知道数据是如何编码的,则无法可靠地进行解码。如果您有选择,最好对 UTF-8 进行标准化。
my $foo = decode('UTF-8', get 'http://example.com/');
my $bar = decode('ISO-8859-1', readline STDIN);
my $xyzzy = decode('Windows-1251', $cgi->param('foo'));
处理过程与您之前了解的一样。唯一的区别是您现在使用的是字符,而不是字节。如果您使用 substr
或 length
之类的东西,这会非常有用。
重要的是要认识到文本字符串中没有字节。当然,Perl 有其内部编码来将字符串存储在内存中,但忽略它。如果您必须对字节数执行任何操作,最好将该部分移动到步骤 3,即编码字符串之后。然后,您将确切地知道它在目标字符串中将有多少个字节。
将文本字符串编码为二进制字符串的语法与解码一样简单
$body = encode('UTF-8', $body);
如果您需要了解字符串的字节长度,现在是最佳时机。因为$body
现在是一个字节字符串,length
将报告字节数,而不是字符数。字符数不再已知,因为字符仅存在于文本字符串中。
my $byte_count = length $body;
如果您使用的协议支持一种方法,让接收者知道您使用的字符编码,请使用该功能帮助接收端!例如,电子邮件和 HTTP 支持 MIME 头,因此您可以使用Content-Type
头。它们还可以有Content-Length
来指示字节数,如果已知该数字,则始终建议提供该数字。
"Content-Type: text/plain; charset=UTF-8",
"Content-Length: $byte_count"
对接收到的所有内容进行解码,对发送出的所有内容进行编码。(如果是文本数据。)
阅读本文档后,您还应该阅读perlunifaq,然后阅读perluniintro。
感谢 Squirrel Consultancy 的 Johan Vromans。他在阿姆斯特丹 Perl Mongers 会议期间对 UTF-8 的抱怨让我产生了兴趣,并决心找出如何以不易损坏的方式在 Perl 中使用字符编码。
感谢 TTY 的 Gerard Goossen。他的演讲“UTF-8 在野外”(2006 年荷兰 Perl 研讨会)激发了我发表我的想法并撰写本教程。
感谢在多个 Perl IRC 频道中询问此类内容的人,并不断提醒我需要一个更简单的解释。
感谢在本文档公开之前为我审阅本文档的人。他们是:Benjamin Smith、Jan-Pieter Cornet、Johan Vromans、Lukas Mai、Nathan Gray。
Juerd Waalboer <#####@juerd.nl>