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
,它将默认设置为整个内存限制。
-mode
是mode
的同义词。-recsep
是recsep
的同义词。-memory
是memory
的同义词。您明白了。
tie
调用返回一个对象,比如$o
。您可以调用
$rec = $o->FETCH($n);
$o->STORE($n, $rec);
分别获取或存储第$n
行的记录;类似地,其他绑定的数组方法也是如此。(有关详细信息,请参阅perltie。)您还可以对该对象调用以下方法
flock
$o->flock(MODE)
将锁定绑定的文件。MODE
与Perl内置flock
函数的第二个参数具有相同的含义;例如LOCK_SH
或LOCK_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
, discard
和 autodefer
参见下方 "延迟写入"。
offset
$off = $o->offset($n);
此方法返回文件中第 $n
个记录的起始字节偏移量。如果不存在这样的记录,则返回未定义的值。
如果 $fh
是一个文件句柄,例如由 IO::File
或其他 IO
模块返回的句柄,您可以使用
tie @array, 'Tie::File', $fh, ...;
类似地,如果您使用常规的 open
或 sysopen
打开了该句柄 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
限制,最旧的记录将从读取缓存中过期,直到总大小低于限制。
push
、pop
、shift
、unshift
和splice
无法延迟。当你执行其中一项操作时,任何延迟数据都会写入文件,操作会立即执行。这可能会在未来版本中改变。
如果你在启用延迟写入的情况下调整数组大小,文件将立即调整大小,但延迟记录不会被写入。这有一个令人惊讶的结果:@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 版本之前不支持安全的并发访问。
(这是拉丁语中的“警告”。)
已经尽了合理的努力使该模块高效。然而,在大型文件的中间更改记录的大小总是相当慢的,因为新记录之后的所有内容都必须移动。
绑定数组的行为与普通数组的行为并不完全相同。例如
# This DOES print "How unusual!"
undef $a[10]; print "How unusual!\n" if defined $a[10];
undef
-ing 一个 Tie::File
数组元素只是将文件中相应的记录清空。当您再次读取它时,您将获得空字符串,因此据称已 undef
的值将被定义。类似地,如果您禁用了 autochomp
,那么
# This DOES print "How unusual!" if 'autochomp' is disabled
undef $a[10];
print "How unusual!\n" if $a[10];
因为当 autochomp
被禁用时,$a[10]
将被读回为 "\n"
(或任何记录分隔符字符串)。
还有一些其他的细微差别,特别是关于 exists
和 delete
的,但总的来说,对应关系非常密切。
我假设,由于该模块关注的是文件 I/O,因此几乎所有对它的正常使用都将是 I/O 密集型的。这意味着维护模块内部复杂数据结构的时间将被实际执行 I/O 的时间所支配。当有机会花费 CPU 时间来避免执行 I/O 时,我通常会尝试这样做。
您可能很想认为延迟写入就像事务一样,flush
就像 commit
,discard
就像 rollback
,但事实并非如此,所以不要这样想。
每个记录偏移量和每个缓存条目都有很大的内存开销:每个缓存的数据记录大约 310 字节,每个偏移量表条目大约 21 字节。
每个记录的开销将限制您每个文件可以访问的记录的最大数量。请注意,通过 $x = scalar @tied_file
访问数组的长度会访问**所有**记录并存储它们的偏移量。foreach (@tied_file)
也是如此,即使您提前退出循环。
本版本对内部实现没有任何承诺,内部实现可能会在未经通知的情况下更改。该模块的未来版本将具有定义明确且稳定的子类化 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)。
更多测试。