DB_File - Perl5 存取 Berkeley DB 版本 1.x
use DB_File;
[$X =] tie %hash, 'DB_File', [$filename, $flags, $mode, $DB_HASH] ;
[$X =] tie %hash, 'DB_File', $filename, $flags, $mode, $DB_BTREE ;
[$X =] tie @array, 'DB_File', $filename, $flags, $mode, $DB_RECNO ;
$status = $X->del($key [, $flags]) ;
$status = $X->put($key, $value [, $flags]) ;
$status = $X->get($key, $value [, $flags]) ;
$status = $X->seq($key, $value, $flags) ;
$status = $X->sync([$flags]) ;
$status = $X->fd ;
# BTREE only
$count = $X->get_dup($key) ;
@list = $X->get_dup($key) ;
%list = $X->get_dup($key, 1) ;
$status = $X->find_dup($key, $value) ;
$status = $X->del_dup($key, $value) ;
# RECNO only
$a = $X->length;
$a = $X->pop ;
$X->push(list);
$a = $X->shift;
$X->unshift(list);
@r = $X->splice(offset, length, elements);
# DBM Filters
$old_filter = $db->filter_store_key ( sub { ... } ) ;
$old_filter = $db->filter_store_value( sub { ... } ) ;
$old_filter = $db->filter_fetch_key ( sub { ... } ) ;
$old_filter = $db->filter_fetch_value( sub { ... } ) ;
untie %hash ;
untie @array ;
DB_File 是模組,讓 Perl 程式可以使用 Berkeley DB 版本 1.x 提供的設施(如果您有較新版本的 DB,請參閱 "使用 DB_File 搭配 Berkeley DB 版本 2 或更新版本")。假設您在閱讀此文件時手邊有 Berkeley DB 手冊頁的副本。此處定義的介面與 Berkeley DB 介面密切對應。
Berkeley DB 是 C 函式庫,提供一致的介面,可存取多種資料庫格式。DB_File 提供介面,可存取 Berkeley DB 目前支援的所有三種資料庫類型。
檔案類型為
此資料庫類型允許將任意鍵值對儲存在資料檔案中。這等同於其他雜湊套件(例如 DBM、NDBM、ODBM、GDBM 和 SDBM)提供的功能。但請記住,使用 DB_HASH 建立的檔案與上述任何其他套件都不相容。
Berkeley DB 內建預設雜湊演算法,這對大多數應用程式來說已足夠。如果您確實需要使用自己的雜湊演算法,可以在 Perl 中撰寫自己的雜湊演算法,並讓 DB_File 使用它。
btree 格式允許將任意鍵值對儲存在已排序且平衡的二元樹中。
與 DB_HASH 格式一樣,可以提供使用者定義的 Perl 常式,以執行鍵值的比較。不過,預設情況下,鍵值會以字典順序儲存。
DB_RECNO 允許使用與 DB_HASH 和 DB_BTREE 相同的鍵值對介面,來處理定長和變長平面文字檔案。在此情況下,鍵值將包含記錄(行)號碼。
雖然 DB_File 旨在與 Berkeley DB 版本 1 搭配使用,但它也可以與版本 2、3 或 4 搭配使用。在這種情況下,介面會受到 Berkeley DB 1.x 所提供的功能限制。在版本 2 或更高版本的介面有所不同的任何地方,DB_File 都會安排它像版本 1 一樣運作。此功能允許使用版本 1 建置的 DB_File 腳本移轉到版本 2 或更高版本,而無需進行任何變更。
如果您想要使用 Berkeley DB 2.x 或更高版本中提供的新功能,請改用 Perl 模組 BerkeleyDB。
注意:資料庫檔案格式已在 Berkeley DB 版本 2、3 和 4 中變更多次。如果您無法重新建立資料庫,您必須使用 Berkeley DB 附帶的 db_dump
或 db_dump185
工具傾印任何現有的資料庫。在您重新建置 DB_File 以使用 Berkeley DB 版本 2 或更高版本後,您的資料庫可以使用 db_load
重新建立。有關進一步的詳細資訊,請參閱 Berkeley DB 文件。
在將 Berkeley DB 的版本 2.x 或更高版本與 DB_File 搭配使用之前,請閱讀 "COPYRIGHT"。
DB_File 允許使用 Perl 5 中的 tie() 機制存取 Berkeley DB 檔案(有關完整詳細資訊,請參閱 "tie()" in perlfunc)。此功能允許 DB_File 使用關聯式陣列(適用於 DB_HASH 和 DB_BTREE 檔案類型)或一般陣列(適用於 DB_RECNO 檔案類型)存取 Berkeley DB 檔案。
除了 tie() 介面之外,也可以直接存取 Berkeley DB API 中提供的大部分函式。請參閱 "API 介面"。
Berkeley DB 使用 dbopen() 函式開啟或建立資料庫。以下是 dbopen() 的 C 原型
DB*
dbopen (const char * file, int flags, int mode,
DBTYPE type, const void * openinfo)
參數 type
是列舉,用於指定要使用的 3 種介面方法(DB_HASH、DB_BTREE 或 DB_RECNO)中的哪一種。根據實際選取哪一種,最後一個參數 openinfo 會指向一個資料結構,允許調整特定的介面方法。
此介面在 DB_File 中的處理方式略有不同。以下是使用 DB_File 的等效呼叫
tie %array, 'DB_File', $filename, $flags, $mode, $DB_HASH ;
filename
、flags
和 mode
參數直接等效於它們的 dbopen() 對應參數。最後一個參數 $DB_HASH 執行 dbopen() 中 type
和 openinfo
參數的功能。
在上面的範例中,$DB_HASH 實際上是對雜湊物件的預先定義參考。DB_File 有三個這樣的預先定義參考。除了 $DB_HASH 之外,還有 $DB_BTREE 和 $DB_RECNO。
在這些預先定義參考中允許的鍵僅限於等效 C 結構中使用的名稱。因此,例如,$DB_HASH 參考只允許稱為 bsize
、cachesize
、ffactor
、hash
、lorder
和 nelem
的鍵。
若要變更其中一個元素,只要像這樣指定給它
$DB_HASH->{'cachesize'} = 10000 ;
三個預先定義的變數 $DB_HASH、$DB_BTREE 和 $DB_RECNO 通常足以應付大部分應用程式。如果你確實需要建立這些物件的額外執行個體,則每個檔案類型都有建構函式可用。
以下是建構函式的範例,以及 DB_HASH、DB_BTREE 和 DB_RECNO 可用的有效選項。
$a = DB_File::HASHINFO->new();
$a->{'bsize'} ;
$a->{'cachesize'} ;
$a->{'ffactor'};
$a->{'hash'} ;
$a->{'lorder'} ;
$a->{'nelem'} ;
$b = DB_File::BTREEINFO->new();
$b->{'flags'} ;
$b->{'cachesize'} ;
$b->{'maxkeypage'} ;
$b->{'minkeypage'} ;
$b->{'psize'} ;
$b->{'compare'} ;
$b->{'prefix'} ;
$b->{'lorder'} ;
$c = DB_File::RECNOINFO->new();
$c->{'bval'} ;
$c->{'cachesize'} ;
$c->{'psize'} ;
$c->{'flags'} ;
$c->{'lorder'} ;
$c->{'reclen'} ;
$c->{'bfname'} ;
儲存在上方雜湊中的值大多是其 C 對應項的直接等效項。與其 C 對應項一樣,所有值都設定為預設值 - 這表示當你只想變更一個值時,你不必設定所有值。以下是一個範例
$a = DB_File::HASHINFO->new();
$a->{'cachesize'} = 12345 ;
tie %y, 'DB_File', "filename", $flags, 0777, $a ;
這裡有幾個選項需要額外說明。當使用時,鍵值 hash
、compare
和 prefix
的 C 對應項會儲存指向 C 函式的指標。在 DB_File 中,這些鍵值用於儲存對 Perl 子例程的參照。以下是每個子例程的範本
sub hash
{
my ($data) = @_ ;
...
# return the hash value for $data
return $hash ;
}
sub compare
{
my ($key, $key2) = @_ ;
...
# return 0 if $key1 eq $key2
# -1 if $key1 lt $key2
# 1 if $key1 gt $key2
return (-1 , 0 or 1) ;
}
sub prefix
{
my ($key, $key2) = @_ ;
...
# return number of bytes of $key2 which are
# necessary to determine that it is greater than $key1
return $bytes ;
}
請參閱 "變更 BTREE 排序順序" 以取得使用 compare
範本的範例。
如果你正在使用 DB_RECNO 介面,而且打算使用 bval
,你應該查看 "'bval' 選項"。
可以在呼叫 tie
時省略呼叫中最後 4 個參數的部分或全部,並讓它們採用預設值。由於 DB_HASH 是最常用的檔案格式,因此呼叫
tie %A, "DB_File", "filename" ;
等同於
tie %A, "DB_File", "filename", O_CREAT|O_RDWR, 0666, $DB_HASH ;
也可以省略檔案名稱參數,因此呼叫
tie %A, "DB_File" ;
等同於
tie %A, "DB_File", undef, O_CREAT|O_RDWR, 0666, $DB_HASH ;
請參閱 "記憶體中資料庫" 以討論在檔案名稱中使用 undef
。
Berkeley DB 允許使用 NULL (也就是 C 中的 (char *)0
) 代替檔案名稱來建立記憶體中資料庫。DB_File 使用 undef
而不是 NULL 來提供此功能。
DB_HASH 檔案格式可能是 DB_File 支援的三個檔案格式中最常用的。它也非常容易使用。
此範例說明如何建立資料庫、將鍵值對新增到資料庫、刪除鍵值對,以及最後列舉資料庫的內容。
use warnings ;
use strict ;
use DB_File ;
our (%h, $k, $v) ;
unlink "fruit" ;
tie %h, "DB_File", "fruit", O_RDWR|O_CREAT, 0666, $DB_HASH
or die "Cannot open file 'fruit': $!\n";
# Add a few key/value pairs to the file
$h{"apple"} = "red" ;
$h{"orange"} = "orange" ;
$h{"banana"} = "yellow" ;
$h{"tomato"} = "red" ;
# Check for existence of a key
print "Banana Exists\n\n" if $h{"banana"} ;
# Delete a key/value pair.
delete $h{"apple"} ;
# print the contents of the file
while (($k, $v) = each %h)
{ print "$k -> $v\n" }
untie %h ;
以下是輸出
Banana Exists
orange -> orange
tomato -> red
banana -> yellow
請注意,如同一般的關聯陣列,檢索到的鍵順序看似是隨機的。
當您想要以特定順序儲存資料時,DB_BTREE 格式會很有用。預設情況下,鍵會以字元順序儲存,但正如您將從下一節所示範例中看到,定義您自己的排序函式非常容易。
此腳本顯示如何覆寫 BTREE 使用的預設排序演算法。它將使用不區分大小寫的比較函式,而不是使用一般的字元順序。
use warnings ;
use strict ;
use DB_File ;
my %h ;
sub Compare
{
my ($key1, $key2) = @_ ;
"\L$key1" cmp "\L$key2" ;
}
# specify the Perl sub that will do the comparison
$DB_BTREE->{'compare'} = \&Compare ;
unlink "tree" ;
tie %h, "DB_File", "tree", O_RDWR|O_CREAT, 0666, $DB_BTREE
or die "Cannot open file 'tree': $!\n" ;
# Add a key/value pair to the file
$h{'Wall'} = 'Larry' ;
$h{'Smith'} = 'John' ;
$h{'mouse'} = 'mickey' ;
$h{'duck'} = 'donald' ;
# Delete
delete $h{"duck"} ;
# Cycle through the keys printing them in order.
# Note it is not necessary to sort the keys as
# the btree will have kept them in order automatically.
foreach (keys %h)
{ print "$_\n" }
untie %h ;
以下是上述程式碼的輸出。
mouse
Smith
Wall
如果您想要變更 BTREE 資料庫中的排序順序,有幾點需要注意
建立資料庫時,必須指定新的比較函式。
資料庫建立後,您無法變更排序順序。因此,每次存取資料庫時,您都必須使用相同的比較函式。
重複鍵完全由比較函式定義。在上述不區分大小寫的範例中,鍵「KEY」和「key」會被視為重複鍵,而指派給第二個鍵會覆寫第一個鍵。如果允許重複鍵(使用下面討論的 R_DUP 旗標),資料庫中只會儲存重複鍵的單一副本 --- 因此(再次以上述範例為例)將三個值指派給鍵「KEY」、「Key」和「key」只會在資料庫中留下第一個鍵「KEY」和三個值。在某些情況下,這會導致資訊遺失,因此在必要時應小心提供完全限定的比較函式。例如,如果兩個鍵在不區分大小寫的比較中相等,上述比較常式可以修改為另外比較大小寫。
sub compare {
my($key1, $key2) = @_;
lc $key1 cmp lc $key2 ||
$key1 cmp $key2;
}
現在,只有當鍵本身真正相等時,您才會有重複鍵。(注意:在 1996 年 11 月左右之前的 db 函式庫版本中,保留了這些重複鍵,因此可以在比較為相等的鍵集中復原原始鍵)。
BTREE 檔案類型可選擇允許將單一鍵與任意數量的值關聯。建立資料庫時,將 $DB_BTREE
的 flags 元素設定為 R_DUP 時,會啟用此選項。
如果您想要處理具有重複鍵的 BTREE 資料庫,使用繫結雜湊介面會遇到一些困難。考慮以下程式碼
use warnings ;
use strict ;
use DB_File ;
my ($filename, %h) ;
$filename = "tree" ;
unlink $filename ;
# Enable duplicate records
$DB_BTREE->{'flags'} = R_DUP ;
tie %h, "DB_File", $filename, O_RDWR|O_CREAT, 0666, $DB_BTREE
or die "Cannot open $filename: $!\n";
# Add some key/value pairs to the file
$h{'Wall'} = 'Larry' ;
$h{'Wall'} = 'Brick' ; # Note the duplicate key
$h{'Wall'} = 'Brick' ; # Note the duplicate key and value
$h{'Smith'} = 'John' ;
$h{'mouse'} = 'mickey' ;
# iterate through the associative array
# and print each key/value pair.
foreach (sort keys %h)
{ print "$_ -> $h{$_}\n" }
untie %h ;
以下是輸出
Smith -> John
Wall -> Larry
Wall -> Larry
Wall -> Larry
mouse -> mickey
如您所見,已成功建立 3 筆記錄,鍵為 Wall
- 唯一的問題是,當從資料庫中檢索這些記錄時,它們看似具有相同的值,即 Larry
。這個問題是由於關聯陣列介面的運作方式所造成。基本上,當關聯陣列介面用於擷取與特定鍵關聯的值時,它只會擷取第一個值。
雖然從上面的程式碼中可能無法立即看出,但關聯陣列介面可用於寫入具有重複鍵值的資料,但無法用於從資料庫中讀取這些資料。
解決這個問題的方法是使用 Berkeley DB API 方法,稱為 seq
。此方法允許循序存取鍵值對。有關 seq
方法和一般 API 的詳細資訊,請參閱 "API 介面"。
以下是使用 seq
API 方法改寫的上述指令碼。
use warnings ;
use strict ;
use DB_File ;
my ($filename, $x, %h, $status, $key, $value) ;
$filename = "tree" ;
unlink $filename ;
# Enable duplicate records
$DB_BTREE->{'flags'} = R_DUP ;
$x = tie %h, "DB_File", $filename, O_RDWR|O_CREAT, 0666, $DB_BTREE
or die "Cannot open $filename: $!\n";
# Add some key/value pairs to the file
$h{'Wall'} = 'Larry' ;
$h{'Wall'} = 'Brick' ; # Note the duplicate key
$h{'Wall'} = 'Brick' ; # Note the duplicate key and value
$h{'Smith'} = 'John' ;
$h{'mouse'} = 'mickey' ;
# iterate through the btree using seq
# and print each key/value pair.
$key = $value = 0 ;
for ($status = $x->seq($key, $value, R_FIRST) ;
$status == 0 ;
$status = $x->seq($key, $value, R_NEXT) )
{ print "$key -> $value\n" }
undef $x ;
untie %h ;
它會列印
Smith -> John
Wall -> Brick
Wall -> Brick
Wall -> Larry
mouse -> mickey
這次我們取得了所有鍵值對,包括與鍵 Wall
關聯的多個值。
為了在處理重複鍵時讓事情變得更簡單,DB_File 附帶了一些實用程式方法。
get_dup
方法協助從 BTREE 資料庫中讀取重複值。此方法可以採用下列形式
$count = $x->get_dup($key) ;
@list = $x->get_dup($key) ;
%list = $x->get_dup($key, 1) ;
在標量環境中,此方法傳回與鍵 $key
關聯的值的數量。
在清單環境中,它傳回與 $key
相符的所有值。請注意,這些值將以看似隨機的順序傳回。
在清單環境中,如果存在第二個參數且評估為 TRUE,則此方法傳回關聯陣列。關聯陣列的鍵對應於在 BTREE 中相符的值,而陣列的值則是特定值在 BTREE 中出現的次數。
因此,假設建立了上述資料庫,我們可以使用 get_dup
如下所示
use warnings ;
use strict ;
use DB_File ;
my ($filename, $x, %h) ;
$filename = "tree" ;
# Enable duplicate records
$DB_BTREE->{'flags'} = R_DUP ;
$x = tie %h, "DB_File", $filename, O_RDWR|O_CREAT, 0666, $DB_BTREE
or die "Cannot open $filename: $!\n";
my $cnt = $x->get_dup("Wall") ;
print "Wall occurred $cnt times\n" ;
my %hash = $x->get_dup("Wall", 1) ;
print "Larry is there\n" if $hash{'Larry'} ;
print "There are $hash{'Brick'} Brick Walls\n" ;
my @list = sort $x->get_dup("Wall") ;
print "Wall => [@list]\n" ;
@list = $x->get_dup("Smith") ;
print "Smith => [@list]\n" ;
@list = $x->get_dup("Dog") ;
print "Dog => [@list]\n" ;
它會列印
Wall occurred 3 times
Larry is there
There are 2 Brick Walls
Wall => [Brick Brick Larry]
Smith => [John]
Dog => []
$status = $X->find_dup($key, $value) ;
此方法檢查特定鍵值對是否存在。如果該對存在,則游標會指向該對,且此方法傳回 0。否則,此方法傳回非零值。
假設使用前一個範例中的資料庫
use warnings ;
use strict ;
use DB_File ;
my ($filename, $x, %h, $found) ;
$filename = "tree" ;
# Enable duplicate records
$DB_BTREE->{'flags'} = R_DUP ;
$x = tie %h, "DB_File", $filename, O_RDWR|O_CREAT, 0666, $DB_BTREE
or die "Cannot open $filename: $!\n";
$found = ( $x->find_dup("Wall", "Larry") == 0 ? "" : "not") ;
print "Larry Wall is $found there\n" ;
$found = ( $x->find_dup("Wall", "Harry") == 0 ? "" : "not") ;
print "Harry Wall is $found there\n" ;
undef $x ;
untie %h ;
會列印此內容
Larry Wall is there
Harry Wall is not there
$status = $X->del_dup($key, $value) ;
此方法會刪除特定的鍵值對。如果它們存在且已成功刪除,則傳回 0。否則,此方法傳回非零值。
再次假設存在 tree
資料庫
use warnings ;
use strict ;
use DB_File ;
my ($filename, $x, %h, $found) ;
$filename = "tree" ;
# Enable duplicate records
$DB_BTREE->{'flags'} = R_DUP ;
$x = tie %h, "DB_File", $filename, O_RDWR|O_CREAT, 0666, $DB_BTREE
or die "Cannot open $filename: $!\n";
$x->del_dup("Wall", "Larry") ;
$found = ( $x->find_dup("Wall", "Larry") == 0 ? "" : "not") ;
print "Larry Wall is $found there\n" ;
undef $x ;
untie %h ;
會列印此內容
Larry Wall is not there
BTREE 介面有一個功能,允許比對部分金鑰。此功能僅在使用 seq 方法和 R_CURSOR 旗標時可用。
$x->seq($key, $value, R_CURSOR) ;
以下是 dbopen 手冊頁中相關的引述,其中定義了 seq 與 R_CURSOR 旗標的用法
Note, for the DB_BTREE access method, the returned key is not
necessarily an exact match for the specified key. The returned key
is the smallest key greater than or equal to the specified key,
permitting partial key matches and range searches.
在以下範例腳本中,match
子程式使用此功能來尋找並列印給定部分金鑰的第一個相符金鑰/值配對。
use warnings ;
use strict ;
use DB_File ;
use Fcntl ;
my ($filename, $x, %h, $st, $key, $value) ;
sub match
{
my $key = shift ;
my $value = 0;
my $orig_key = $key ;
$x->seq($key, $value, R_CURSOR) ;
print "$orig_key\t-> $key\t-> $value\n" ;
}
$filename = "tree" ;
unlink $filename ;
$x = tie %h, "DB_File", $filename, O_RDWR|O_CREAT, 0666, $DB_BTREE
or die "Cannot open $filename: $!\n";
# Add some key/value pairs to the file
$h{'mouse'} = 'mickey' ;
$h{'Wall'} = 'Larry' ;
$h{'Walls'} = 'Brick' ;
$h{'Smith'} = 'John' ;
$key = $value = 0 ;
print "IN ORDER\n" ;
for ($st = $x->seq($key, $value, R_FIRST) ;
$st == 0 ;
$st = $x->seq($key, $value, R_NEXT) )
{ print "$key -> $value\n" }
print "\nPARTIAL MATCH\n" ;
match "Wa" ;
match "A" ;
match "a" ;
undef $x ;
untie %h ;
以下是輸出
IN ORDER
Smith -> John
Wall -> Larry
Walls -> Brick
mouse -> mickey
PARTIAL MATCH
Wa -> Wall -> Larry
A -> Smith -> John
a -> mouse -> mickey
DB_RECNO 提供平面文字檔案的介面。支援變動長度和固定長度記錄。
為了使 RECNO 更相容於 Perl,所有 RECNO 陣列的陣列偏移量從 0 開始,而不是像 Berkeley DB 中的 1。
與一般的 Perl 陣列一樣,可以使用負索引來存取 RECNO 陣列。索引 -1 指陣列的最後一個元素,-2 指倒數第二個元素,依此類推。嘗試存取陣列開頭之前的元素會引發致命執行時期錯誤。
bval 選項的操作值得討論。以下是 Berkeley DB 1.85 recno 手冊頁中對 bval 的定義
The delimiting byte to be used to mark the end of a
record for variable-length records, and the pad charac-
ter for fixed-length records. If no value is speci-
fied, newlines (``\n'') are used to mark the end of
variable-length records and fixed-length records are
padded with spaces.
第二個句子是錯誤的。實際上,只有當 dbopen 中的 openinfo 參數為 NULL 時,bval 才會預設為 "\n"
。如果使用任何非 NULL 的 openinfo 參數,將會使用 bval 中的實際值。這表示您在使用 openinfo 參數中的任何選項時,都必須指定 bval。此文件錯誤將在 Berkeley DB 的下一個版本中修正。
這澄清了 Berkeley DB 本身的狀況。DB_File 呢?嗯,上面引述中定義的行為非常有用,因此 DB_File 符合此行為。
這表示您可以指定其他選項(例如 cachesize),而 bval 仍會預設為變動長度記錄的 "\n"
,以及固定長度記錄的空白。
另外請注意,bval 選項只允許您指定單一位元組作為分隔符號。
以下是一個使用 RECNO 的簡單範例(如果您使用的是早於 5.004_57 的 Perl 版本,此範例將無法執行 -- 請參閱 "額外 RECNO 方法" 以取得解決方法)。
use warnings ;
use strict ;
use DB_File ;
my $filename = "text" ;
unlink $filename ;
my @h ;
tie @h, "DB_File", $filename, O_RDWR|O_CREAT, 0666, $DB_RECNO
or die "Cannot open file 'text': $!\n" ;
# Add a few key/value pairs to the file
$h[0] = "orange" ;
$h[1] = "blue" ;
$h[2] = "yellow" ;
push @h, "green", "black" ;
my $elements = scalar @h ;
print "The array contains $elements entries\n" ;
my $last = pop @h ;
print "popped $last\n" ;
unshift @h, "white" ;
my $first = shift @h ;
print "shifted $first\n" ;
# Check for existence of a key
print "Element 1 Exists with value $h[1]\n" if $h[1] ;
# use a negative index
print "The last element is $h[-1]\n" ;
print "The 2nd last element is $h[-2]\n" ;
untie @h ;
以下是腳本的輸出
The array contains 5 entries
popped black
shifted white
Element 1 Exists with value blue
The last element is green
The 2nd last element is yellow
如果您使用的是 5.004_57 之前的 Perl 版本,則繫結陣列介面相當受限。在上述範例指令碼中,push
、pop
、shift
、unshift
或決定陣列長度將無法與繫結陣列搭配使用。
為了讓介面對舊版 Perl 更為實用,DB_File 提供多種方法來模擬遺失的陣列操作。所有這些方法都可透過繫結呼叫傳回的物件存取。
以下是這些方法
將 list
的元素推送到陣列末端。
移除並傳回陣列的最後一個元素。
移除並傳回陣列的第一個元素。
將 list
的元素推送到陣列開頭。
傳回陣列中的元素數量。
傳回陣列的剪輯。
以下是更完整的範例,其中使用了一些上述說明的方法。它也直接使用 API 介面(請參閱 "THE API INTERFACE")。
use warnings ;
use strict ;
my (@h, $H, $file, $i) ;
use DB_File ;
use Fcntl ;
$file = "text" ;
unlink $file ;
$H = tie @h, "DB_File", $file, O_RDWR|O_CREAT, 0666, $DB_RECNO
or die "Cannot open file $file: $!\n" ;
# first create a text file to play with
$h[0] = "zero" ;
$h[1] = "one" ;
$h[2] = "two" ;
$h[3] = "three" ;
$h[4] = "four" ;
# Print the records in order.
#
# The length method is needed here because evaluating a tied
# array in a scalar context does not return the number of
# elements in the array.
print "\nORIGINAL\n" ;
foreach $i (0 .. $H->length - 1) {
print "$i: $h[$i]\n" ;
}
# use the push & pop methods
$a = $H->pop ;
$H->push("last") ;
print "\nThe last record was [$a]\n" ;
# and the shift & unshift methods
$a = $H->shift ;
$H->unshift("first") ;
print "The first record was [$a]\n" ;
# Use the API to add a new record after record 2.
$i = 2 ;
$H->put($i, "Newbie", R_IAFTER) ;
# and a new record before record 1.
$i = 1 ;
$H->put($i, "New One", R_IBEFORE) ;
# delete record 3
$H->del(3) ;
# now print the records in reverse order
print "\nREVERSE\n" ;
for ($i = $H->length - 1 ; $i >= 0 ; -- $i)
{ print "$i: $h[$i]\n" }
# same again, but use the API functions instead
print "\nREVERSE again\n" ;
my ($s, $k, $v) = (0, 0, 0) ;
for ($s = $H->seq($k, $v, R_LAST) ;
$s == 0 ;
$s = $H->seq($k, $v, R_PREV))
{ print "$k: $v\n" }
undef $H ;
untie @h ;
而這是它的輸出
ORIGINAL
0: zero
1: one
2: two
3: three
4: four
The last record was [four]
The first record was [zero]
REVERSE
5: last
4: three
3: Newbie
2: one
1: New One
0: first
REVERSE again
5: last
4: three
3: Newbie
2: one
1: New One
0: first
備註
與其這樣反覆運算陣列 @h
foreach $i (@h)
有必要使用這個
foreach $i (0 .. $H->length - 1)
或這個
for ($a = $H->get($k, $v, R_FIRST) ;
$a == 0 ;
$a = $H->get($k, $v, R_NEXT) )
請注意,兩次使用 put
方法時,記錄索引都是使用變數 $i
指定,而不是文字值本身。這是因為 put
會透過該參數傳回插入行的記錄號碼。
除了使用繫結雜湊或陣列存取 Berkeley DB 之外,也可以直接使用 Berkeley DB 文件中定義的大部分 API 函數。
為執行此操作,您需要儲存繫結傳回的物件副本。
$db = tie %hash, "DB_File", "filename" ;
完成後,你可以直接像這樣使用 DB_File 方法存取 Berkeley DB API 函式
$db->put($key, $value, R_NOOVERWRITE) ;
重要:如果你儲存了從 tie
傳回的物件副本,基礎資料庫檔案將不會關閉,直到繫結變數解除繫結,且所有儲存物件的副本都已銷毀。
use DB_File ;
$db = tie %hash, "DB_File", "filename"
or die "Cannot tie filename: $!" ;
...
undef $db ;
untie %hash ;
請參閱 "The untie() Gotcha" 以取得更多詳細資訊。
在 dbopen 中定義的所有函式都可用,但 close() 和 dbopen() 本身除外。支援函式的 DB_File 方法介面已實作,以盡可能反映 Berkeley DB 的運作方式。特別注意
方法會傳回狀態值。所有方法在成功時傳回 0。所有方法在發生錯誤時傳回 -1,並將 $!
設定為確切的錯誤碼。傳回碼 1 通常(但並非總是)表示資料庫中不存在指定的鍵。
已定義其他傳回碼。請參閱下方和 Berkeley DB 文件以取得詳細資訊。Berkeley DB 文件應作為明確的來源。
每當 Berkeley DB 函式透過其參數之一傳回資料時,等效的 DB_File 方法也會執行完全相同的動作。
如果你小心,就可以在同一程式碼片段中將 API 呼叫與繫結雜湊/陣列介面混合使用。雖然目前用於實作繫結介面的方法中只有少數使用游標,但你應該在每次使用繫結雜湊/陣列介面時,都假設游標已變更。例如,此程式碼可能無法執行你預期的動作
$X = tie %x, 'DB_File', $filename, O_RDWR|O_CREAT, 0777, $DB_BTREE
or die "Cannot tie $filename: $!" ;
# Get the first key/value pair and set the cursor
$X->seq($key, $value, R_FIRST) ;
# this line will modify the cursor
$count = scalar keys %x ;
# Get the second key/value pair.
# oops, it didn't, it got the last key/value pair!
$X->seq($key, $value, R_NEXT) ;
可以重新排列上述程式碼以解決問題,如下所示
$X = tie %x, 'DB_File', $filename, O_RDWR|O_CREAT, 0777, $DB_BTREE
or die "Cannot tie $filename: $!" ;
# this line will modify the cursor
$count = scalar keys %x ;
# Get the first key/value pair and set the cursor
$X->seq($key, $value, R_FIRST) ;
# Get the second key/value pair.
# worked this time.
$X->seq($key, $value, R_NEXT) ;
在 dbopen 中定義的所有常數,可用於下方定義的方法中的旗標參數,也可用。請參閱 Berkeley DB 文件以取得旗標值的精確意義。
以下是可用方法的清單。
給定一個鍵 ($key
),此方法會從資料庫中讀取與其關聯的值。從資料庫中讀取的值會傳回 $value
參數。
如果鍵不存在,方法會傳回 1。
目前未定義此方法的旗標。
將金鑰/值對儲存在資料庫中。
如果您使用 R_IAFTER 或 R_IBEFORE 旗標,$key
參數將設定為已插入金鑰/值對的記錄編號。
有效的旗標為 R_CURSOR、R_IAFTER、R_IBEFORE、R_NOOVERWRITE 和 R_SETCURSOR。
從資料庫中移除所有金鑰為 $key
的金鑰/值對。
傳回碼 1 表示資料庫中沒有要求的金鑰。
目前唯一有效的旗標為 R_CURSOR。
傳回底層資料庫的檔案描述符。
請參閱 "鎖定:fd 的問題",了解為何不應使用 fd
鎖定資料庫。
此介面允許從資料庫中循序檢索。請參閱 dbopen 以取得完整詳細資料。
$key
和 $value
參數都將設定為從資料庫中讀取的金鑰/值對。
flags 參數是強制性的。有效的旗標值為 R_CURSOR、R_FIRST、R_LAST、R_NEXT 和 R_PREV。
將任何快取的緩衝區沖洗至磁碟。
目前唯一有效的旗標為 R_RECNOSYNC。
DBM 篩選器是一段程式碼,當您總是想要對 DBM 資料庫中的所有金鑰和/或值進行相同的轉換時可以使用。一個範例是,當您需要在寫入資料庫之前將資料編碼為 UTF-8,然後在從資料庫檔案讀取時解碼 UTF-8。
有兩種方法可以使用 DBM 篩選器。
使用下面定義的低階 API。
使用 DBM_Filter 模組。此模組隱藏了下面定義的 API 的複雜性,並附帶許多「罐裝」篩選器,涵蓋了一些常見的用例。
建議使用 DBM_Filter 模組。
有四種方法與 DBM 篩選器相關聯。所有方法都以相同的方式運作,每個方法都用於安裝(或解除安裝)單一 DBM 篩選器。每個方法都預期單一參數,即子程式的參考。它們之間唯一的差異是安裝篩選器的位置。
總而言之
如果已使用此方法安裝篩選器,則每次將金鑰寫入 DBM 資料庫時,都會呼叫該篩選器。
如果已使用此方法安裝篩選器,則每次將值寫入 DBM 資料庫時,都會呼叫該篩選器。
如果已使用此方法安裝篩選器,則每次從 DBM 資料庫中讀取金鑰時,都會呼叫該篩選器。
如果已使用此方法安裝篩選器,則每次從 DBM 資料庫讀取值時,都會呼叫它。
您可以使用任何方法組合,從無到全部四種。
所有篩選器方法都會傳回現有的篩選器(如果存在),或在不存在時傳回 undef
。
若要刪除篩選器,請傳遞 undef
給它。
Perl 呼叫每個篩選器時,$_
的本地副本將包含要篩選的鍵或值。透過修改 $_
的內容來達成篩選。篩選器的回傳碼會被忽略。
考慮以下情況。您有一個 DBM 資料庫,需要與第三方 C 應用程式共用。C 應用程式假設所有鍵和值都是 NULL 終止。不幸的是,當 Perl 寫入 DBM 資料庫時,它不會使用 NULL 終止,因此您的 Perl 應用程式必須自行管理 NULL 終止。當您寫入資料庫時,您必須使用類似以下的內容
$hash{"$key\0"} = "$value\0" ;
同樣地,在考慮現有鍵/值的長度時,需要考慮 NULL。
如果您可以在主應用程式碼中忽略 NULL 終止問題,並有一個機制可以在您寫入資料庫時自動將終止 NULL 新增到所有鍵和值,並在您從資料庫讀取時將它們移除,那會好得多。我敢肯定您已經猜到了,這是 DBM 篩選器可以非常輕鬆修復的問題。
use warnings ;
use strict ;
use DB_File ;
my %hash ;
my $filename = "filt" ;
unlink $filename ;
my $db = tie %hash, 'DB_File', $filename, O_CREAT|O_RDWR, 0666, $DB_HASH
or die "Cannot open $filename: $!\n" ;
# Install DBM Filters
$db->filter_fetch_key ( sub { s/\0$// } ) ;
$db->filter_store_key ( sub { $_ .= "\0" } ) ;
$db->filter_fetch_value( sub { s/\0$// } ) ;
$db->filter_store_value( sub { $_ .= "\0" } ) ;
$hash{"abc"} = "def" ;
my $a = $hash{"ABC"} ;
# ...
undef $db ;
untie %hash ;
希望每個篩選器的內容都是不言自明的。兩個「擷取」篩選器都會移除終止 NULL,兩個「儲存」篩選器都會新增終止 NULL。
以下是另一個真實範例。預設情況下,每當 Perl 寫入 DBM 資料庫時,它總是會將鍵和值寫入為字串。因此,當您使用此方法時
$hash{12345} = "something" ;
鍵 12345 會以 5 位元組字串「12345」儲存在 DBM 資料庫中。如果您實際上希望鍵以 C int 儲存在 DBM 資料庫中,您必須在寫入時使用 pack
,在讀取時使用 unpack
。
以下是這樣做的 DBM 篩選器
use warnings ;
use strict ;
use DB_File ;
my %hash ;
my $filename = "filt" ;
unlink $filename ;
my $db = tie %hash, 'DB_File', $filename, O_CREAT|O_RDWR, 0666, $DB_HASH
or die "Cannot open $filename: $!\n" ;
$db->filter_fetch_key ( sub { $_ = unpack("i", $_) } ) ;
$db->filter_store_key ( sub { $_ = pack ("i", $_) } ) ;
$hash{123} = "def" ;
# ...
undef $db ;
untie %hash ;
這次只使用了兩個篩選器 -- 我們只需要處理鍵的內容,因此不需要安裝任何值篩選器。
在這個模組的 1.72 版之前,建議使用「fd」函式傳回的檔案句柄來鎖定 DB_File 資料庫。不幸的是,此技術已被證明有根本上的缺陷(感謝 David Harris 追蹤此問題)。使用時請自行承擔風險!
鎖定技術如下。
$db = tie(%db, 'DB_File', 'foo.db', O_CREAT|O_RDWR, 0644)
|| die "dbcreat foo.db $!";
$fd = $db->fd;
open(DB_FH, "+<&=$fd") || die "dup $!";
flock (DB_FH, LOCK_EX) || die "flock: $!";
...
$db{"Tom"} = "Jerry" ;
...
flock(DB_FH, LOCK_UN);
undef $db;
untie %db;
close(DB_FH);
簡單來說,發生的事情如下
使用「tie」開啟資料庫。
使用 fd 和 flock 鎖定資料庫。
讀取和寫入資料庫。
解除鎖定並關閉資料庫。
以下是問題的關鍵。在步驟 2 中開啟 DB_File 資料庫的副作用是,資料庫中的初始區塊會從磁碟讀取並快取在記憶體中。
要了解為什麼這是一個問題,請考慮當兩個程序(例如「A」和「B」)都想要使用上述鎖定步驟更新同一個 DB_File 資料庫時會發生什麼事。假設程序「A」已經開啟資料庫並具有寫入鎖定,但它尚未實際更新資料庫(它已完成步驟 2,但尚未開始步驟 3)。現在程序「B」嘗試開啟同一個資料庫 - 步驟 1 會成功,但它會在步驟 2 中被封鎖,直到程序「A」釋放鎖定。這裡要注意的重要一點是,此時此刻兩個程序都已經快取了資料庫中相同的初始區塊。
現在程序「A」更新資料庫,並碰巧變更了初始緩衝區中的一些資料。程序「A」終止,將所有快取的資料沖刷到磁碟並釋放資料庫鎖定。此時,磁碟上的資料庫將正確反映程序「A」所做的變更。
在鎖定被釋放後,程序「B」現在可以繼續。它也更新資料庫,但不幸的是,它也修改了初始緩衝區中的資料。一旦該資料被沖刷到磁碟,它將覆寫程序「A」對資料庫所做的一些或全部變更。
此情境的結果充其量是一個不包含您預期內容的資料庫。最糟的情況是資料庫會損毀。
競爭程序更新同一個 DB_File 資料庫時,上述情況並非每次都會發生,但它確實說明了為什麼不應該使用此技術。
從 2.x 版開始,Berkeley DB 內建鎖定支援。這個模組的配套模組 BerkeleyDB 提供了這個鎖定功能的介面。如果您認真考慮鎖定 Berkeley DB 資料庫,我強烈建議使用 BerkeleyDB。
如果使用 BerkeleyDB 不是一個選項,CPAN 上有許多可用於實作鎖定的模組。每個模組實作鎖定的方式不同,並有不同的目標。因此,了解差異非常重要,這樣您才能為您的應用程式選擇正確的模組。以下是三個鎖定包裝器
一個 DB_File 包裝器,它會為讀取存取建立資料庫檔案的副本,這樣您就擁有一種多版本並行讀取系統。但是,更新仍然是序列化的。用於讀取可能很長且可能會發生一致性問題的資料庫。
一個 DB_File wrapper,具有在使用資料庫時鎖定和解鎖資料庫的能力。透過在取得或釋放鎖定時重新繫結資料庫,避免了繫結前 flock 的問題。由於在工作階段中段釋放和重新取得鎖定的靈活性,如果應用程式遵循 POD 文件中的提示,這可以被按摩成一個適用於長時間更新和/或讀取的系統。
一個極輕量的 DB_File wrapper,在繫結資料庫之前僅 flock 一個鎖定檔,並在解除繫結後釋放鎖定。允許將同一個鎖定檔用於多個資料庫,以避免死鎖問題(如果需要)。用於更新讀取快速且簡單,且 flock 鎖定語意足夠的資料庫。
在技術上,沒有理由讓 Berkeley DB 資料庫無法同時由 Perl 和 C 應用程式共用。
在這個領域中報告的大多數問題,都歸結於 C 字串以 NULL 結束,而 Perl 字串則不然。請參閱 "DBM FILTERS",以取得解決此問題的通用方法。
以下是一個真實範例。Netscape 2.0 會記錄您造訪的地點,以及您上次造訪的時間,並儲存在 DB_HASH 資料庫中。這通常儲存在檔案 ~/.netscape/history.db 中。資料庫中的金鑰欄位是地點字串,而值欄位是地點上次造訪的時間,儲存為 4 位元組的二進位值。
如果您尚未猜到,地點字串會儲存為以 NULL 結束。這表示您在存取資料庫時需要小心。
以下是一段程式碼片段,它大致基於 Tom Christiansen 的 ggh 程式碼(可從最近的 CPAN 檔案庫中的 authors/id/TOMC/scripts/nshist.gz 取得)。
use warnings ;
use strict ;
use DB_File ;
use Fcntl ;
my ($dotdir, $HISTORY, %hist_db, $href, $binary_time, $date) ;
$dotdir = $ENV{HOME} || $ENV{LOGNAME};
$HISTORY = "$dotdir/.netscape/history.db";
tie %hist_db, 'DB_File', $HISTORY
or die "Cannot open $HISTORY: $!\n" ;;
# Dump the complete database
while ( ($href, $binary_time) = each %hist_db ) {
# remove the terminating NULL
$href =~ s/\x00$// ;
# convert the binary time into a user friendly string
$date = localtime unpack("V", $binary_time);
print "$date $href\n" ;
}
# check for the existence of a specific key
# remember to add the NULL
if ( $binary_time = $hist_db{"http://mox.perl.com/\x00"} ) {
$date = localtime unpack("V", $binary_time) ;
print "Last visited mox.perl.com on $date\n" ;
}
else {
print "Never visited mox.perl.com\n"
}
untie %hist_db ;
如果您使用 Berkeley DB API,強烈建議您閱讀 "perltie 中的 untie 陷阱"。
即使您目前沒有使用 API 介面,也值得一讀。
以下是說明此問題的範例,從 DB_File 的角度來看
use DB_File ;
use Fcntl ;
my %x ;
my $X ;
$X = tie %x, 'DB_File', 'tst.fil' , O_RDWR|O_TRUNC
or die "Cannot tie first time: $!" ;
$x{123} = 456 ;
untie %x ;
tie %x, 'DB_File', 'tst.fil' , O_RDWR|O_CREAT
or die "Cannot tie second time: $!" ;
untie %x ;
執行時,指令碼會產生此錯誤訊息
Cannot tie second time: Invalid argument at bad.file line 14.
儘管上述錯誤訊息指的是指令碼中的第二個 tie() 陳述式,但問題的根源實際上是其前面的 untie() 陳述式。
讀過 perltie 之後,你可能已經猜到這個錯誤是由儲存在 $X
中的繫結物件的額外副本所造成。如果你還沒有猜到,那麼問題歸結為 DB_File 解構函式 DESTROY 直到繫結物件的所有參考都已毀損後才會被呼叫。繫結變數 %x
和上述的 $X
都持有對該物件的參考。呼叫 untie() 會毀損第一個參考,但 $X
仍持有有效的參考,因此解構函式不會被呼叫,而且資料庫檔案 tst.fil 將保持開啟狀態。Berkeley DB 然後報告嘗試開啟已開啟的資料庫,透過萬用「無效引數」並無幫助。
如果你使用 -w
旗標執行指令碼,錯誤訊息會變成
untie attempted while 1 inner references still exist at bad.file line 12.
Cannot tie second time: Invalid argument at bad.file line 14.
這精確指出真正的問題。最後,現在可以修改指令碼,在 untie 之前毀損 API 物件,以修正原始問題
...
$x{123} = 456 ;
undef $X ;
untie %x ;
$X = tie %x, 'DB_File', 'tst.fil' , O_RDWR|O_CREAT
...
如果你查看 DB_File 所建立的資料庫檔案內容,有時會包含 Perl 指令碼的一部分。
這是因為 Berkeley DB 使用動態記憶體配置緩衝區,這些緩衝區將隨後寫入資料庫檔案。由於是動態的,因此在 DB 分配記憶體之前,記憶體可以被用於任何目的。由於 Berkeley DB 在分配記憶體後不會清除記憶體,因此未使用的部分將包含隨機垃圾。在 Perl 指令碼寫入資料庫的情況下,隨機垃圾將對應於指令碼編譯期間碰巧使用的動態記憶體區域。
除非你不喜歡 Perl 指令碼的一部分可能嵌入在資料庫檔案中,否則這無需擔心。
雖然 DB_File 無法直接做到這一點,但有一個模組可以透明地覆蓋在 DB_File 上來完成這項壯舉。
請查看 MLDBM 模組,它在 CPAN 的 modules/by-module/MLDBM 目錄中提供。
如果你使用 UTF-8 資料,並且想要從/寫入 Berkeley DB 資料庫檔案,通常會收到這則訊息。
處理這個問題最簡單的方法是使用預先定義的「utf8」DBM_Filter(請參閱 DBM_Filter),它設計用於處理這種情況。
以下範例顯示如果同時預期金鑰和值為 UTF-8 時,你需要什麼。
use DB_File;
use DBM_Filter;
my $db = tie %h, 'DB_File', '/tmp/try.db', O_CREAT|O_RDWR, 0666, $DB_BTREE;
$db->Filter_Key_Push('utf8');
$db->Filter_Value_Push('utf8');
my $key = "\N{LATIN SMALL LETTER A WITH ACUTE}";
my $value = "\N{LATIN SMALL LETTER E WITH ACUTE}";
$h{ $key } = $value;
當 tie
呼叫中的其中一個參數錯誤時,你會收到這則錯誤訊息。不幸的是,有相當多的參數會出錯,因此很難找出是哪一個。
以下是幾個可能性
嘗試在未關閉資料庫的情況下重新開啟資料庫。
使用 O_WRONLY 旗標。
當你的指令碼中有 strict 'subs'
pragma(或完整的 strict pragma)時,你會遇到這個特定的錯誤訊息。考慮這個指令碼
use warnings ;
use strict ;
use DB_File ;
my %x ;
tie %x, DB_File, "filename" ;
執行它會產生有問題的錯誤
Bareword "DB_File" not allowed while "strict subs" in use
若要解決這個錯誤,請將字詞 DB_File
放入單引號或雙引號中,如下所示
tie %x, "DB_File", "filename" ;
雖然這看起來可能很麻煩,但讓所有指令碼中都有一個 use strict
絕對是值得的。
關於 DB_File 或使用它的文章。
Perl 中的全文字搜尋,Tim Kientzle (tkientzle@ddj.com),Dr. Dobb's Journal,第 295 期,1999 年 1 月,第 34-41 頁
已移至變更檔案。
某些舊版本的 Berkeley DB 在使用 RECNO 檔案格式的固定長度記錄時會出現問題。這個問題已在 Berkeley DB 的 1.85 版中修復。
我確定程式碼中還有錯誤。如果你發現任何錯誤,或可以建議任何改進,歡迎你提出意見。
一般回饋/問題/錯誤報告應傳送至 https://github.com/pmqs/DB_File/issues(優先)或 https://rt.cpan.org/Public/Dist/Display.html?Name=DB_File。
DB_File 附帶標準 Perl 原始碼發行版。請在目錄 ext/DB_File 中尋找。由於 Perl 發行版本之間的時間間隔,隨 Perl 附帶的版本很可能已過時,因此最新版本隨時可在 CPAN 上找到(有關詳細資訊,請參閱 perlmodlib 中的「CPAN」),目錄為 modules/by-module/DB_File。
DB_File 設計為可與任何版本的 Berkeley DB 搭配使用,但功能僅限於版本 1 所提供的功能。如果您想使用 Berkeley DB 2.x 或更高版本提供的最新功能,請改用 Perl 模組 BerkeleyDB。
Berkeley DB 的官方網站為 http://www.oracle.com/technology/products/berkeley-db/db/index.html。所有版本的 Berkeley DB 都可以在這裡找到。
或者,您可以在最近的 CPAN 檔案庫中找到 Berkeley DB 版本 1,位置為 src/misc/db.1.85.tar.gz。
著作權所有 (c) 1995-2022 Paul Marquess。保留所有權利。此程式為自由軟體;您可以在與 Perl 相同的條款下重新散布或修改它。
雖然 DB_File 受 Perl 授權條款保護,但它所使用的函式庫 Berkeley DB 並不受保護。Berkeley DB 有其自己的著作權和授權條款。請參閱 AGPL 以取得更多詳細資訊。請花時間閱讀 Berkeley DB 授權條款,並決定它如何影響您使用此 Perl 模組。
perl、dbopen(3)、hash(3)、recno(3)、btree(3)、perldbmfilter、DBM_Filter
DB_File 介面由 Paul Marquess <pmqs@cpan.org> 編寫。