【问题标题】:Perl's default string encoding and representationPerl 的默认字符串编码和表示
【发布时间】:2013-06-17 20:15:01
【问题描述】:

如下:

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 的默认值吗?

【问题讨论】:

  • Perl 不会将事物保存在编码中。它的字符串总是被解码。只有未解码的字符串可能处于某种编码中。

标签: string perl utf-8 character-encoding


【解决方案1】:

在 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

(在内部,一个名为“utf8”的 UTF-8 扩展用于存储 72 位字符的字符串。除了意识到性能影响外,这不是您应该知道的,但是有些错误会暴露这个事实。)

Encode 的is_utf8 报告标量包含的字符串类型。这是一个除了调试我之前提到的错误之外完全没有用的功能。

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

无论是存储在浮点数、有符号整数还是无符号整数中,零都是零。类似地,字符串的存储格式也没有传达关于字符串值的信息。

  • 您可以像存储 72 位字符串一样轻松地将代码点存储在 8 位字符串中(如果它们足够小的话)。
  • 您可以像存储 8 位字符串一样轻松地将字节存储在 72 位字符串中。

实际上,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

【讨论】:

  • substr($s, 0, 1) 指的是字符串的第一个字符?所以本质上第一个字符可以有一个值>255,这意味着它没有存储在一个字节中?我开始明白了吗?
  • 是的,第一个字符。是的,它的值可以大于 255。根据所使用的存储格式和字符的值,它可能使用多个字节进行存储。
  • 我添加了一个 sn-p,在我的答案底部显示 0x80 存储为一个或两个字节。
  • 你说的可能是什么意思?我们如何在单个字节中存储大于255 的值?
  • 我觉得认为 Perl 在内部将字符串保持在“某种默认编码”而不是“在某种内部表示中”只会让更多人感到困惑而不是帮助。最好将字符串视为逻辑代码点的序列。我相信知道这个逻辑字符串的确切内存布局对万分之一的人没有帮助,对其余大多数人都是有害的。
【解决方案2】:

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

不安静,大括号内的数值是代码点。整个 \x 表达式只是一个字符的符号。字符有多种表示法,其中大多数以反斜杠开头,但常见的一种是简单的字符串文字。你不妨写:

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

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

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

但是这是如何工作的呢?

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

当然,计算机不能,内部数据结构必须有某种形式的内部编码。因为"Perl can't keep a secret",细节偶尔会泄露出去,所以造成了太多的混乱。

这句话中的所有字符(包括 ASCII 字符)都是用 UTF-8 编码的吗?

不,内部编码是宽松的 UTF8(没有破折号)。它没有 UTF-8(又名 UTF-8-strict)所具有的一些限制。

  1. 在我的 64 位系统上,UTF-8 最高为 0x10_ffff,UTF8 最高为 0xffff_ffff_ffff_ffff。不过,大于 0xffff_ffff 的代码点会发出不可移植警告。
  2. 在 UTF-8 中,某些代码点是非字符或非法字符。在 UTF8 中,一切皆有可能。

编码::is_utf8

… 是一个内部函数,是clearly marked as such。作为程序员的你不应该偷看。但既然你想偷看,没有人可以阻止你。 Devel::Peek::Dump 是了解内部结构的更好工具。

阅读 http://p3rl.org/UNI 了解 Perl 编码主题的介绍。

【讨论】:

  • @daxim:True, but so far your string is a string of characters, not a buffer of octets. 这是什么意思? perl 中如何声明八位字节的缓冲区?
  • 我忽略了有时内部编码不是 UTF8; you 已经很好地覆盖了它。
  • Cratylus,您可以通过对字符串进行编码来创建八位组。有几种方法可以做到这一点,显式的和隐式的。通读p3rl.org/UNI 了解所有方法,以及何时更喜欢哪种方法。 - 获取octets的另一种方法是从磁盘文件、标准I/O流、数据库、命令行参数、环境变量、套接字等中读取它们raw,也就是说跳过通常的解码步骤。
  • 八位字节是指 8 位字节/值?而等效的解码格式是 8 位还是 72 位值?
  • 我已经在我之前附加到这个答案的评论中链接到八位字节的定义。
【解决方案3】:

is_utf8 是一个名字不好的函数,它并不意味着你认为它意味着什么,或者与它有任何关系任何。您的问题的答案是 $string 没有编码,因为它没有编码。当您使用某种编码调用Encode::encode 时,其结果将是一个已编码的字符串,并且具有已知的编码

【讨论】:

  • 这个Encode::is_utf8($string, 1) 也返回true 并根据perldocIf CHECK is true, also checks whether STRING contains well-formed UTF-8。顺便说一句,我对 perldoc 感到很头疼......
猜你喜欢
  • 2018-10-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-08
  • 2011-07-27
  • 1970-01-01
  • 2013-06-17
相关资源
最近更新 更多