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 行的內容會被替換為「櫻桃派」;一個換行字元會將第 17 行與第 18 行分隔開來。這表示以下程式碼不會執行任何動作
chomp $array[17];
因為 chomp
的值會在寫回檔案時重新附加分隔符號。無法建立缺少尾端記錄分隔符號字串的檔案。
插入包含記錄分隔符號字串的記錄不受此模組支援。它可能會產生合理的結果,但此結果在未來版本中可能會有所改變。請使用「splice」插入記錄或用多個記錄取代一個記錄。
autochomp
通常,陣列元素會移除記錄分隔符號,因此如果檔案包含文字
Gold
Frankincense
Myrrh
繫結陣列看起來會包含 ("Gold", "Frankincense", "Myrrh")
。如果您將 autochomp
設定為 false 值,記錄分隔符號將不會被移除。如果上面的檔案使用
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, ...;
僅寫入開啟的處理常式無法使用。唯讀開啟的處理常式可以使用,只要您不嘗試修改陣列即可。處理常式必須附加至可尋址的資料來源,這表示沒有管線或 socket。如果 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
將會來不及捨棄所有變更。在未來版本的 Tie::File
中,可能會取消對 ->discard
的支援。
延遲寫入會快取在記憶體中,直到達到 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
來安全地同時存取。這是錯誤的。在 0.96 版之前,Tie::File 都不會支援安全的同時存取。
(這是拉丁文的「警告」)
已盡合理努力讓此模組有效率。不過,變更大型檔案中間記錄的大小總是會比較慢,因為新記錄之後的所有內容都必須移動。
繫結陣列的行為與一般陣列並不完全相同。例如
# This DOES print "How unusual!"
undef $a[10]; print "How unusual!\n" if defined $a[10];
將 Tie::File
陣列元素設為 undef
只會將檔案中對應的記錄清空。當您再次讀取時,您會得到空字串,因此假設為 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
若要連絡作者,請寄送電子郵件至:mjd-perl-tiefile+@plover.com
若要接收此模組發布新版本時的公告,請寄送空白電子郵件訊息至 mjd-perl-tiefile-subscribe@plover.com
。
此模組的最新版本,包括文件和任何重要新聞,都可以在以下位置取得
http://perl.plover.com/TieFile/
Tie::File
版本 0.96 的版權所有 (C) 2003 Mark Jason Dominus。
此程式庫是免費軟體;您可以在與 Perl 相同的條款下重新分發或修改它。
這些條款是您選擇的 (1) Perl Artistic Licence,或 (2) 由自由軟體基金會發布的 GNU General Public License 版本 2,或 (3) GNU General Public License 的任何後續版本。
此程式庫的發布是基於希望它會很有用,但沒有任何保證;甚至沒有隱含的 MERCHANTABILITY 或 FITNESS FOR A PARTICULAR PURPOSE 保證。詳情請參閱 GNU General Public License。
您應該已收到 GNU General Public License 的副本,連同此程式庫程式一起;它應該在 COPYING
檔案中。如果不是,請寫信至美國麻薩諸塞州波士頓市富蘭克林街 51 號 5 樓,Free Software Foundation, Inc., 02110-1301,美國
對於授權查詢,請透過以下方式連絡作者
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)。
更多測試。