Perl的默认字符串编码和表示方式

6
在下面的代码中: my $string = "Can you \x{FB01}nd my r\x{E9}sum\x{E9}?\n"; x{FB01}x{E9} 是代码点。代码点通过编码方案编码为一系列八位字节。
因此,具有代码点 \x{FB01} 的字符 è 是字符串 $string 的一部分。但是这是怎么实现的呢?这个句子中所有字符(包括ASCII字符)都使用 UTF-8 编码吗?
如果是,那么我为什么会得到以下行为?
my $str = "Some arbitrary string\n";  

if(Encode::is_utf8($str)) {  
        print "YES str IS UTF8!\n";  
}  
else {  
        print "NO str IT IS NOT UTF8\n";   
}  

这会打印出"NO str IT IS NOT UTF8\n"
另外,Encode::is_utf8($string)返回true
那么,$string$str有何不同,一个被认为是UTF-8,而另一个则不是?
无论如何,$str的编码是什么? ASCII?这是Perl的默认值吗?

2
Perl不会将东西保存在编码中。它的字符串总是解码的。只有未解码的字符串可能在某些编码中。 - tchrist
3个回答

9

C语言中,字符串是一组八位字节,但Perl有两种字符串存储格式:

  • 8位值的字符串。
  • 72位值的字符串。(实际上限制在32位或64位。)

因此,在字符串中存储代码点时,不需要进行编码。

my $s = "\x{2660}\x{2661}";
say length $s;                            # 2
say sprintf '%X', ord substr($s, 0, 1);   # 2660
say sprintf '%X', ord substr($s, 1, 1);   # 2661

内部使用UTF-8的扩展"utf8"来存储72位字符的字符串。这不是你必须知道的事情,除了意识到性能影响之外,但有些错误会暴露这个事实。

Encode的"is_utf8"函数报告标量包含的字符串类型。它是一个毫无用处的函数,除了调试我之前提到的错误。

  • 一个8位字符串可以存储"abc"的值(或OP中的字符串$str),因此Perl使用更有效率的8位(UTF8=0)字符串格式。
  • 一个8位字符串无法存储"\x{2660}\x{2661}"的值(或OP中的字符串$string),因此Perl使用72位(UTF8=1)字符串格式。

零就是零,无论它存储在浮点数、有符号整数还是无符号整数中。同样,字符串的存储格式不传达关于字符串值的任何信息。

  • 你可以将代码点存储在8位字符串中(如果它们足够小),就像72位字符串一样容易。
  • 你可以将字节存储在72位字符串中,就像8位字符串一样容易。

事实上,Perl会随意在两种格式之间切换。例如,如果你将$string$str连接起来,你将得到一个72位格式的字符串。

你可以使用内置函数utf8::downgradeutf8::upgrade更改字符串的存储格式,以解决错误问题。

utf8::downgrade($s);  # Switch to strings of  8-bit values (UTF8=0).
utf8::upgrade($s);    # Switch to strings of 72-bit values (UTF8=1).

你可以使用Devel::Peek查看效果。
>perl -MDevel::Peek -e"$s=chr(0x80); utf8::downgrade($s); Dump($s);"
SV = PV(0x7b8a74) at 0x4a84c4
  REFCNT = 1
  FLAGS = (POK,pPOK)
  PV = 0x7bab9c "\200"\0
  CUR = 1
  LEN = 12

>perl -MDevel::Peek -e"$s=chr(0x80); utf8::upgrade($s); Dump($s);"
SV = PV(0x558a6c) at 0x1cc843c
  REFCNT = 1
  FLAGS = (POK,pPOK,UTF8)
  PV = 0x55ab94 "\302\200"\0 [UTF8 "\x{80}"]
  CUR = 2
  LEN = 12

如果它大于255,那么它将需要超过一个字节。如果它在128..255之间,则需要一个或两个字节。如果它在0..127之间,则始终只需要一个字节。 - ikegami
好的,但是从perldoc中可以看到:将标量值STRING从Perl的内部形式编码为ENCODING,并返回一个八位字节序列。 我理解内部形式是您指定的8位或72位值?这种形式会转换为8位值吗? - Cratylus
4
我认为将Perl中的字符串视为在内部保持“某个默认编码”,而不是“某种内部表示形式”,只会比有帮助更多地让人感到困惑。更好的做法是将字符串视为逻辑代码点序列。我认为,了解这个逻辑字符串的确切内存布局对于一万个人中的一个人都没有帮助,对大多数人来说都是有害的。 - tchrist
@ikegami:我的意思是这样的:perl -MDevel::Peek -E 'my $a = "ż"; my $b = "\x{17c}"; Dump $a; Dump $b; Dump "$b - $a"' -- 注意没有使用 use utf8;(这是一个错误)。 - Jakub Narębski
@JakubNarębski 预期输出是这样的。 "ż" 是一个字节串(没有设置 utf8 标志),由 两个 八位组/码点组成,因为我的控制台使用 UTF-8 编码。 "\x{17c}"一个 码点;utf8 标志被设置了。这两个字符串存储相同的八位组序列是无关紧要的;这些都是内部细节,这些序列的解释差别很大。连接后,你得到 2+3+1=6 个码点,这是可以预料的。没什么好看的。始终在系统边界上对数据进行编码和解码,以避免发生这种情况。 - amon
显示剩余18条评论

5

\x{FB01}和\x{E9}是代码点。

不完全正确,大括号内的数字值才是代码点。整个\x表达式只是一个字符的表示法。有几种字符表示法,其中大多数以反斜杠开头,但常见的是简单的字符串文字。你也可以这样写:

use utf8;
my $string = "Can you find my résumé?\n";
#                     ↑       ↑   ↑

代码点通过编码方案编码为一系列八位字节。

没错,但到目前为止,您的字符串只是一个字符的字符串,而不是八位字节的缓冲区。

那这是怎么工作的呢?

字符串由字符组成。这只是 Perl 的模型。作为程序员,您应该在这个级别上处理它。

当然,计算机不能这样做,内部数据结构必须具有某种形式的内部编码。太多的混乱发生是因为 "Perl 无法保守秘密", 细节偶尔会泄漏出来。

这个句子中的所有字符(包括 ASCII 字符)都是通过 UTF-8 编码的吗?

不是,内部编码是松散的 UTF8(没有破折号)。它没有 UTF-8(也称为 UTF-8-strict)具有的某些限制。

  1. UTF-8可以达到0x10_ffff,而UTF8在我的64位系统上可以达到0xffff_ffff_ffff_ffff。但是超过0xffff_ffff的码位将会发出不可移植警告。
  2. 在UTF-8中,某些码位是非字符或非法字符。在UTF8中则没有限制。

Encode::is_utf8

...是一个内部函数,并且已经明确标记为这样。作为程序员,您不应该窥探。但是既然您想窥探,就没有人能阻止您。Devel::Peek::Dump是一个更好的工具,用于访问内部。

请阅读http://p3rl.org/UNI,以了解Perl中编码的介绍。


@daxim: "没错,但是到目前为止你的字符串只是一串字符,不是一个八位字节的缓冲区。" 这是什么意思?在Perl中如何声明一个八位字节的缓冲区? - Cratylus
我忽略了有时内部编码不是UTF8的情况;你已经很好地解决了这个问题。 - daxim
Cratylus,你可以通过从字符字符串进行编码来创建八位字节。有几种明确和隐含的方法可以这样做。请阅读http://p3rl.org/UNI以了解所有方法,并在何时更喜欢哪种方法。-获取[八位字节](http://p3rl.org/Encode#octet)的另一种方法是从磁盘文件、标准I/O流、数据库、命令行参数、环境变量、套接字等中直接读取它们,也就是跳过通常的解码步骤。 - daxim
你所说的八位字节/值是指八个比特位吗?而等效的解码格式是8或72比特位的值? - Cratylus
我已经在之前的评论中附上了“八位组”定义的链接。 - daxim

3

is_utf8是一个名字不太合适的函数,它并不意味着你认为的那样,并且与此毫无关系。你问题的答案是$string没有编码,因为它没有被编码过。当你用某个编码调用Encode::encode时,其结果将是一个已编码的字符串,并且具有已知的编码。


这个 Encode::is_utf8($string, 1) 也返回 true,根据 perldoc 的说明:如果 CHECK 为真,还会检查 STRING 是否包含格式正确的 UTF-8。顺便说一下,我对 perldoc 感到非常头疼... - Cratylus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接