內容

名稱

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,它會預設為整個記憶體限制。

選項格式

-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::Filetie 呼叫期間不會讀取或寫入檔案。(例外:使用 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」。

deferflushdiscardautodefer

請參閱下方「遞延寫入」。

offset

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

此方法傳回檔案中第 $n 筆記錄開頭的位元組偏移量。如果沒有此記錄,則傳回未定義值。

繫結至已開啟的檔案處理常式

如果 $fh 是檔案處理常式,例如由 IO::File 或其他 IO 模組傳回,您可以使用

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

類似地,如果您使用一般 opensysopen 開啟處理常式 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 限制,那麼最舊的記錄將會從讀取快取中過期,直到總大小低於限制為止。

pushpopshiftunshiftsplice 無法延遲。當你執行其中一個操作時,任何延遲資料都會寫入檔案,而操作會立即執行。這可能會在未來版本中變更。

如果你在啟用延遲寫入的情況下調整陣列大小,檔案將會立即調整大小,但延遲記錄不會寫入。這會造成一個令人驚訝的後果:@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 都不會支援安全的同時存取。

注意事項

(這是拉丁文的「警告」)

子類別化

此版本絕對不保證內部結構,內部結構可能會在不另行通知的情況下變更。模組的未來版本將會有定義完善且穩定的子類別化 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)。

更多測試。