内容

名称

Encode::PerlIO -- 关于 Encode 和 PerlIO 的详细文档

概述

在读取或写入文件、网络连接、管道等时,通常需要进行编码转换。如果 Perl 配置为使用新的 'perlio' IO 系统,那么 Encode 提供了一个“层”(参见 PerlIO),它可以在读取或写入数据时进行转换。

以下是盲诗人如何现代化编码

use Encode;
open(my $iliad,'<:encoding(iso-8859-7)','iliad.greek');
open(my $utf8,'>:utf8','iliad.utf8');
my @epic = <$iliad>;
print $utf8 @epic;
close($utf8);
close($illiad);

此外,新的 IO 系统还可以配置为读取/写入 UTF-8 编码的字符(如上所述,这很有效率)

open(my $fh,'>:utf8','anything');
print $fh "Any \x{0021} string \N{SMILEY FACE}\n";

以上两种“层”规范中的任何一种都可以通过 `use open ...` 编译指示设置为词法作用域的默认值。参见 open

打开句柄后,可以使用 `binmode` 更改其层。

如果没有进行任何配置,或者 Perl 本身使用系统的 IO 构建,那么写入操作假定文件句柄只接受 *字节*,如果写入句柄的字符大于 255,则会 `die`。读取时,句柄中的每个八位字节都成为一个字符中的一个字节。请注意,此默认行为与仅字节语言(包括 v5.6 之前的 Perl)的行为相同,足以处理本机 8 位编码(例如 iso-8859-1、EBCDIC 等)以及处理其他编码和二进制数据的任何遗留机制。

在其他情况下,程序有责任在进行写入之前使用上面的 API 将字符转换为字节,并在进行“字符操作”(例如 `lc`、`/\W+/` 等)之前将从句柄读取的字节转换为字符。

您还可以使用 PerlIO 转换您不想加载到内存中的大量数据。例如,在 ISO-8859-1(Latin 1)和 UTF-8(或 EBCDIC 机器上的 UTF-EBCDIC)之间进行转换

open(F, "<:encoding(iso-8859-1)", "data.txt") or die $!;
open(G, ">:utf8",                 "data.utf") or die $!;
while (<F>) { print G }

# Could also do "print G <F>" but that would pull
# the whole file into memory just to write it out again.

更多示例

open(my $f, "<:encoding(cp1252)")
open(my $g, ">:encoding(iso-8859-2)")
open(my $h, ">:encoding(latin9)")       # iso-8859-15

另请参见 encoding,了解如何更改脚本中数据的默认编码。

它是如何工作的?

以下是对文件句柄、PerlIO 和 Encode 如何交互的粗略示意图。

filehandle <-> PerlIO        PerlIO <-> scalar (read/printed)
                     \      /
                      Encode   

当 PerlIO 从任一方向接收数据时,它会填充一个缓冲区(目前为 1024 字节)并将缓冲区传递给 Encode。Encode 尝试转换有效部分并将其传递回 PerlIO,将无效部分(通常是部分字符)留在缓冲区中。然后,PerlIO 将更多数据追加到缓冲区,再次调用 Encode,依此类推,直到数据流结束。

为此,PerlIO 始终在 CHECK 设置为 1 的情况下调用 (de|en)code 方法。这确保了当方法遇到部分字符时,它会在正确的位置停止。以下是 PerlIO 和 Encode 尝试编码(从 utf8)超过 1024 字节,并且缓冲区边界恰好位于字符中间时发生的情况。

 A   B   C   ....   ~     \x{3000}    ....
41  42  43   ....  7E   e3   80   80  ....
<- buffer --------------->
<< encoded >>>>>>>>>>
                     <- next buffer ------

Encode 从开头转换到 \x7E,将 \xe3 留在缓冲区中,因为它无效(部分字符)。

不幸的是,这种方案不适用于基于转义的编码,例如 ISO-2022-JP。

行缓冲

现在让我们看看当您尝试从 ISO-2022-JP 解码,并且缓冲区在字符中间结束时会发生什么。

            JIS208-ESC   \x{5f3e}
 A   B   C   ....   ~   \e   $   B  |DAN | ....
41  42  43   ....  7E   1b  24  41  43  46 ....
<- buffer --------------------------->
<< encoded >>>>>>>>>>>>>>>>>>>>>>>

如您所见,下一个缓冲区以 \x43 开头。但 \x43 在 ASCII 中是 'C',在这种情况下是错误的,因为我们现在处于 JISX 0208 区域,因此它必须转换 \x43\x46,而不是 \x43。与 utf8 和 EUC 不同,在基于转义的编码中,您无法判断给定的八位字节是完整的字符还是仅仅是其中的一部分。

幸运的是,PerlIO 也支持行缓冲,如果您告诉 PerlIO 使用行缓冲而不是固定缓冲区。由于 ISO-2022-JP 保证在行尾恢复为 ASCII,因此在使用行缓冲时永远不会出现部分字符。

要告诉 PerlIO 使用行缓冲,请为您的编码对象实现 ->needs_lines 方法。有关详细信息,请参阅 Encode::Encoding

由于这些努力,大多数与 Encode 捆绑在一起的编码都支持 PerlIO,但这仍然留下了以下编码。

iso-2022-kr
MIME-B
MIME-Header
MIME-Q

幸运的是,iso-2022-kr 几乎不用(根据 Jungshik 的说法),MIME-* 非常不可能被馈送到 PerlIO,因为它们用于邮件头。有关详细信息,请参阅 Encode::MIME::Header

如何判断我的编码是否完全支持 PerlIO?

截至撰写本文时,任何其类属于 Encode::XS 和 Encode::Unicode 的编码都适用。Encode 模块有一个 perlio_ok 方法,您可以在将 PerlIO 编码应用于文件句柄之前使用它。以下是一个示例

my $use_perlio = perlio_ok($enc);
my $layer = $use_perlio ? "<:raw" : "<:encoding($enc)";
open my $fh, $layer, $file or die "$file : $!";
while(<$fh>){
  $_ = decode($enc, $_) unless $use_perlio;
  # .... 
}

另请参阅

Encode::EncodingEncode::SupportedEncode::PerlIOencodingperlebcdic"open" 在 perlfunc 中perlunicodeutf8,Perl Unicode 邮件列表 <[email protected]>