内容

名称

Tie::File - 通过 Perl 数组访问磁盘文件中的行

概要

use Tie::File;

tie @array, 'Tie::File', filename or die ...;

$array[0] = 'blah';      # first line of the file is now 'blah'
                           # (line numbering starts at 0)
print $array[42];        # display line 43 of the file

$n_recs = @array;        # how many records are in the file?
$#array -= 2;            # chop two records off the end


for (@array) {
  s/PERL/Perl/g;        # Replace PERL with Perl everywhere in the file
}

# These are just like regular push, pop, unshift, shift, and splice
# Except that they modify the file in the way you would expect

push @array, new recs...;
my $r1 = pop @array;
unshift @array, new recs...;
my $r2 = shift @array;
@old_recs = splice @array, 3, 7, new recs...;

untie @array;            # all finished

描述

Tie::File 将一个普通文本文件表示为一个 Perl 数组。数组中的每个元素对应于文件中的一个记录。文件的首行为数组的元素 0;第二行为元素 1,依此类推。

文件不会被加载到内存中,因此即使对于巨大的文件也能正常工作。

对数组的更改会立即反映到文件中。

懒惰的人和初学者现在可以停止阅读手册了。

recsep

什么是“记录”?默认情况下,它的含义与<...>运算符相同:它是一个以$/结尾的字符串,通常是"\n"。(小例外:在 DOS 和 Win32 系统上,“记录”是以"\r\n"结尾的字符串。)你可以在tie调用中提供recsep选项来更改“记录”的定义。

tie @array, 'Tie::File', $file, recsep => 'es';

这表示记录以字符串es分隔。如果文件包含以下数据

Curse these pesky flies!\n

那么@array将显示为包含四个元素。

"Curse th"
"e p"
"ky fli"
"!\n"

不允许使用未定义的值作为记录分隔符。Perl 的特殊“段落模式”语义(类似于$/ = "")不会被模拟。

从绑定数组中读取的记录末尾不会包含记录分隔符字符串;这是为了允许

$array[17] .= "extra";

按预期工作。

(参见下面的"autochomp"。)存储到数组中的记录将在写入文件之前追加记录分隔符字符串,前提是它们还没有包含该字符串。例如,如果记录分隔符字符串是"\n",那么以下两行代码执行完全相同的事情

$array[17] = "Cherry pie";
$array[17] = "Cherry pie\n";

结果是文件第 17 行的内容将被替换为“Cherry pie”;一个换行符将第 17 行与第 18 行分隔开。这意味着这段代码将不会做任何事情

chomp $array[17];

因为chomp后的值将在写入文件时重新附加分隔符。无法创建缺少尾部记录分隔符字符串的文件。

此模块不支持插入包含记录分隔符字符串的记录。它可能会产生合理的结果,但此结果可能会在将来的版本中发生变化。使用“splice”插入记录或用多个记录替换一个记录。

autochomp

通常,数组元素会移除记录分隔符,因此如果文件包含以下文本

Gold
Frankincense
Myrrh

则绑定数组将显示为包含("Gold", "Frankincense", "Myrrh")。如果你将autochomp设置为假值,则不会移除记录分隔符。如果上面的文件与以下代码绑定

tie @gifts, "Tie::File", $gifts, autochomp => 0;

那么数组@gifts将显示为包含("Gold\n", "Frankincense\n", "Myrrh\n"),或者(在 Win32 系统上)("Gold\r\n", "Frankincense\r\n", "Myrrh\r\n")

mode

通常,指定的文件将被打开以进行读写访问,如果文件不存在,则会创建该文件。(也就是说,在open调用中提供标志O_RDWR | O_CREAT。)如果你想更改此行为,可以在mode选项中提供其他标志。有关可用标志的列表,请参见Fcntl。例如

# open the file if it exists, but fail if it does not exist
use Fcntl 'O_RDWR';
tie @array, 'Tie::File', $file, mode => O_RDWR;

# create the file if it does not exist
use Fcntl 'O_RDWR', 'O_CREAT';
tie @array, 'Tie::File', $file, mode => O_RDWR | O_CREAT;

# open an existing file in read-only mode
use Fcntl 'O_RDONLY';
tie @array, 'Tie::File', $file, mode => O_RDONLY;

不支持以只写或追加模式打开数据文件。

memory

这是Tie::File在管理文件时任何时候都会消耗的内存量的上限。这用于两件事:管理读取缓存和管理延迟写入缓冲区

从文件中读取的记录被缓存,以避免重复读取它们。如果您两次读取相同的记录,第一次它将被存储在内存中,第二次将从读取缓存中获取。读取缓存中的数据量不会超过您为memory指定的数值。如果Tie::File想要缓存新记录,但读取缓存已满,它将通过从读取缓存中过期访问最少的记录来腾出空间。

默认内存限制为 2Mib。您可以通过提供memory选项来调整最大读取缓存大小。参数是所需的缓存大小,以字节为单位。

# I have a lot of memory, so use a large cache to speed up access
tie @array, 'Tie::File', $file, memory => 20_000_000;

将内存限制设置为 0 将禁止缓存;每次检查记录时都会从磁盘中获取记录。

memory值不是内存使用的绝对或精确限制。Tie::File对象除了读取缓存和延迟写入缓冲区之外还包含一些结构,这些结构的大小不会计入memory

缓存本身消耗每个缓存记录约 310 字节,因此如果您的文件有许多短记录,您可能需要减少缓存内存限制,否则缓存开销可能会超过缓存数据的尺寸。

dw_size

(这是一个高级功能。第一次阅读时跳过此部分。)

如果您使用延迟写入(参见下面的"延迟写入"),那么您写入数组中的数据不会直接写入文件;相反,它将被保存到延迟写入缓冲区中,以便稍后写入。延迟写入缓冲区中的数据也会计入您使用memory选项设置的内存限制。

您可以设置dw_size选项来限制可以保存在延迟写入缓冲区中的数据量。此限制不得超过总内存限制。例如,如果您将dw_size设置为 1000,将memory设置为 2500,这意味着最多可以保存 1000 字节的延迟写入。读取缓存可用的空间会变化,但始终至少为 1500 字节(如果延迟写入缓冲区已满),并且可以增长到 2500 字节(如果延迟写入缓冲区为空)。

如果您没有指定dw_size,它将默认设置为整个内存限制。

选项格式

-modemode的同义词。-recseprecsep的同义词。-memorymemory的同义词。您明白了。

公共方法

tie调用返回一个对象,比如$o。您可以调用

$rec = $o->FETCH($n);
$o->STORE($n, $rec);

分别获取或存储第$n行的记录;类似地,其他绑定的数组方法也是如此。(有关详细信息,请参阅perltie。)您还可以对该对象调用以下方法

flock

$o->flock(MODE)

将锁定绑定的文件。MODE与Perl内置flock函数的第二个参数具有相同的含义;例如LOCK_SHLOCK_EX | LOCK_NB。(这些常量由use Fcntl ':flock'声明提供。)

MODE是可选的;默认值为LOCK_EX

Tie::File维护一个内部表,其中包含它在文件中看到的每个记录的字节偏移量。

当您使用flock锁定文件时,Tie::File假设读取缓存不再可靠,因为另一个进程可能在上次读取文件后修改了该文件。因此,成功调用flock将丢弃读取缓存的内容和内部记录偏移表。

Tie::File承诺以下操作序列将是安全的

my $o = tie @array, "Tie::File", $filename;
$o->flock;

特别是,Tie::File不会tie调用期间读取或写入文件。(例外:使用mode => O_TRUNC当然会在tie调用期间擦除文件。如果您想安全地执行此操作,请在没有O_TRUNC的情况下打开文件,锁定文件,然后使用@array = ()。)

解锁文件的最佳方法是丢弃对象并解除数组的绑定。在不解除绑定文件的情况下解锁文件可能是不安全的,因为如果您这样做,更改可能会保留在对象内部未写入。这就是没有解锁快捷方式的原因。如果您真的想提前解锁文件,您知道该怎么做;如果您不知道该怎么做,就不要这样做。

这里所有关于文件锁定的常见警告都适用。特别是,请注意,Perl 中的文件锁定是建议性的,这意味着持有锁不会阻止任何人读取、写入或擦除文件;它只是阻止他们在同一时间获得另一个锁。锁类似于绿灯:如果您有绿灯,这并不能阻止迎面而来的白痴横向撞向您;它只是保证您,白痴在同一时间也没有绿灯。

autochomp

my $old_value = $o->autochomp(0);    # disable autochomp option
my $old_value = $o->autochomp(1);    #  enable autochomp option

my $ac = $o->autochomp();   # recover current value

请参阅上面"autochomp"

defer, flush, discardautodefer

参见下方 "延迟写入"

offset

$off = $o->offset($n);

此方法返回文件中第 $n 个记录的起始字节偏移量。如果不存在这样的记录,则返回未定义的值。

绑定到已打开的文件句柄

如果 $fh 是一个文件句柄,例如由 IO::File 或其他 IO 模块返回的句柄,您可以使用

tie @array, 'Tie::File', $fh, ...;

类似地,如果您使用常规的 opensysopen 打开了该句柄 FH,您可以使用

tie @array, 'Tie::File', \*FH, ...;

以只写方式打开的句柄将无法工作。以只读方式打开的句柄将可以工作,只要您不尝试修改数组。句柄必须附加到可寻址的数据源——这意味着不能是管道或套接字。如果 Tie::File 可以检测到您提供了一个不可寻址的句柄,tie 调用将抛出异常。(在 Unix 系统上,它可以检测到这一点。)

请注意,Tie::File 仅会关闭它在内部打开的任何文件句柄。如果您像上面那样传递了一个文件句柄,您“拥有”该文件句柄,并负责在解除绑定 @array 后关闭它。

Tie::File 在它内部打开的文件句柄上调用 binmode,但在用户传递的文件句柄上不调用。为了保持一致性,尤其是在跨平台使用绑定文件时,您可能希望在绑定文件之前在文件句柄上调用 binmode

延迟写入

(这是一个高级功能。第一次阅读时跳过此部分。)

通常,修改 Tie::File 数组会立即写入底层文件。每次赋值,例如 $a[3] = ...,都会重写文件中必要的部分;通常,从第 3 行到结尾的所有内容都需要重写。这是最简单、最透明的行为。即使对于大型文件,性能也相当好。

但是,在某些情况下,这种行为可能会过慢。例如,假设您有一个包含一百万条记录的文件,并且您想执行以下操作

for (@FILE) {
  $_ = "> $_";
}

第一次遍历循环时,您将从第 0 行到结尾重写整个文件。第二次遍历循环时,您将从第 1 行到结尾重写整个文件。第三次遍历循环时,您将从第 2 行到结尾重写整个文件。依此类推。

如果在这种情况下性能不可接受,您可以延迟实际写入,然后一次性完成所有写入。以下循环对于大型文件将执行得快得多

(tied @a)->defer;
for (@a) {
  $_ = "> $_";
}
(tied @a)->flush;

如果 Tie::File 的内存限制足够大,所有写入都将在内存中完成。然后,当您调用 ->flush 时,整个文件将在一次传递中重写。

(实际上,前面的讨论有点儿夸大其词。你不需要启用延迟写入就能获得这种常见情况下的良好性能,因为Tie::File会自动为你完成,除非你明确告诉它不要。请参阅下面的"自动延迟"。)

调用->flush会将数组恢复到立即写入模式。如果你想丢弃延迟写入,你可以调用->discard而不是->flush。请注意,在某些情况下,部分数据可能已经写入,->discard可能无法丢弃所有更改。对->discard的支持可能会在Tie::File的未来版本中被移除。

延迟写入被缓存到内存中,直到达到dw_size选项指定的限制(见上文)。如果延迟写入缓冲区已满,你尝试写入更多延迟数据,缓冲区将被刷新。所有缓冲数据将立即写入,缓冲区将被清空,现在空出的空间将用于未来的延迟写入。

如果延迟写入缓冲区尚未满,但缓冲区和读取缓存的总大小超过了memory限制,最旧的记录将从读取缓存中过期,直到总大小低于限制。

pushpopshiftunshiftsplice无法延迟。当你执行其中一项操作时,任何延迟数据都会写入文件,操作会立即执行。这可能会在未来版本中改变。

如果你在启用延迟写入的情况下调整数组大小,文件将立即调整大小,但延迟记录不会被写入。这有一个令人惊讶的结果:@a = (...)会立即擦除文件,但实际数据的写入会被延迟。这可能是一个错误。如果这是一个错误,它将在未来版本中修复。

自动延迟

Tie::File试图猜测何时延迟写入可能会有帮助,并自动开启和关闭它。

for (@a) {
  $_ = "> $_";
}

在这个例子中,只有前两个赋值会立即完成;此后,对文件的任何更改都会被延迟,直到达到用户指定的内存限制。

通常情况下,您可以忽略这一点,直接使用模块而无需考虑延迟。但是,特殊应用程序可能需要对哪些写入被延迟进行精细控制,或者可能要求所有写入都是立即的。要禁用自动延迟功能,请使用

(tied @o)->autodefer(0);

tie @array, 'Tie::File', $file, autodefer => 0;

类似地,->autodefer(1) 重新启用自动延迟,而 ->autodefer() 恢复自动延迟设置的当前值。

并发访问文件

如果您希望多个进程同时访问同一个文件,则缓存和延迟写入是不合适的。此模块内部执行的其他优化也与并发访问不兼容。该模块的未来版本将支持一个 concurrent => 1 选项,该选项允许安全的并发访问。

以前版本的文档建议使用 memory => 0 来实现安全的并发访问。这是错误的。Tie::File 在 0.96 版本之前不支持安全的并发访问。

注意事项

(这是拉丁语中的“警告”。)

子类化

本版本对内部实现没有任何承诺,内部实现可能会在未经通知的情况下更改。该模块的未来版本将具有定义明确且稳定的子类化 API。

DB_File 怎么样?

人们有时会指出 DB_File 会做类似的事情,并询问为什么需要 Tie::File 模块。

您可能更喜欢 Tie::File 的原因有很多。可以在 http://perl.plover.com/TieFile/why-not-DB_File 上找到一个列表。

作者

Mark Jason Dominus

要联系作者,请发送电子邮件至:[email protected]

要接收有关此模块新版本发布的通知,请向 [email protected] 发送空白电子邮件。

此模块的最新版本,包括文档和任何重要新闻,将在以下位置提供

http://perl.plover.com/TieFile/

许可证

Tie::File 版本 0.96 版权所有 (C) 2003 Mark Jason Dominus。

此库是免费软件;您可以根据与 Perl 本身相同的条款重新分发和/或修改它。

这些条款是您选择的任何一项:(1) Perl 艺术许可证,或 (2) 自由软件基金会发布的 GNU 通用公共许可证版本 2,或 (3) GNU 通用公共许可证的任何更高版本。

此库按“现状”提供,不提供任何形式的明示或暗示保证,包括但不限于适销性保证和特定用途适用性保证。有关更多详细信息,请参阅 GNU 通用公共许可证。

您应该已经收到此库程序的 GNU 通用公共许可证副本;它应该在 COPYING 文件中。如果没有,请写信给自由软件基金会,Inc.,51 Franklin Street,Fifth Floor,Boston,MA 02110-1301,USA

有关许可证查询,请联系作者

Mark Jason Dominus
255 S. Warnock St.
Philadelphia, PA 19107

保证

Tie::File 版本 0.98 不附带任何形式的保证。有关详细信息,请参阅许可证。

感谢

衷心感谢 Jarkko Hietaniemi,感谢他在我还没有写完的时候同意将它加入核心,以及他的一贯帮助、支持和能力。(通常规则是“选择任何一个”。)还要特别感谢 Abhijit Menon-Sen 为所有相同的事情做出的贡献。

特别感谢 Craig Berry 和 Peter Prymmer(为 VMS 可移植性提供帮助),Randy Kobes(为 Win32 可移植性提供帮助),Clinton Pierce 和 Autrijus Tang(为超出职责范围的英勇的最后一刻 Win32 测试),Michael G Schwern(为测试建议),以及其他 CPAN 测试人员(为一般测试)。

特别感谢 Tels 为建议了几项速度和内存优化。

特别感谢:Edward Avis / Mattia Barbon / Tom Christiansen / Gerrit Haase / Gurusamy Sarathy / Jarkko Hietaniemi(再次感谢)/ Nikola Knezevic / John Kominetz / Nick Ing-Simmons / Tassilo von Parseval / H. Dieter Pearcey / Slaven Rezic / Eric Roode / Peter Scott / Peter Somu / Autrijus Tang(再次感谢)/ Tels(再次感谢)/ Juerd Waalboer / Todd Rinaldo

待办事项

更多测试。(一些我还没想到的东西。)

段落模式?

固定长度模式。留空模式。

也许可以添加自动锁定模式?

对于模块的许多常见用途,读取缓存是一个负担。例如,插入单个记录或扫描文件一次的程序的缓存命中率为零。这表明需要进行一项重大优化:缓存应最初禁用。以下是一种混合方法:最初,缓存被禁用,但缓存代码会维护有关如果启用缓存,命中率将有多高的统计信息。当它看到命中率足够高时,它会自行启用。此代码中的 STAT 注释是此实现的开始。

使用 fcntl() 进行记录锁定?然后模块可能会支持撤消日志并获得真正的交易。那将是一个多么了不起的壮举。

跟踪最高缓存记录。这将允许连续读取更快地跳过缓存查找(如果从 1..N 读取,并且在开始时缓存为空,则最后一个缓存值将始终为 N-1)。

更多测试。