内容

名称

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 当前支持的所有三种数据库类型。

文件类型为

DB_HASH

此数据库类型允许将任意键/值对存储在数据文件中。这等同于 DBM、NDBM、ODBM、GDBM 和 SDBM 等其他哈希包提供的功能。但请记住,使用 DB_HASH 创建的文件与上述任何其他包都不兼容。

Berkeley DB 中内置了一个默认哈希算法,它足以满足大多数应用程序的需求。如果您确实需要使用自己的哈希算法,则可以在 Perl 中编写自己的算法,让 DB_File 使用它。

DB_BTREE

btree 格式允许将任意键/值对存储在已排序的平衡二叉树中。

与 DB_HASH 格式一样,可以提供用户定义的 Perl 例程来执行键的比较。不过,默认情况下,键按词法顺序存储。

DB_RECNO

DB_RECNO 允许使用与 DB_HASH 和 DB_BTREE 中相同的键/值对界面来操作固定长度和可变长度的纯文本文件。在这种情况下,键将由记录(行)号组成。

使用 DB_File 与 Berkeley DB 版本 2 或更高版本

尽管 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_dumpdb_dump185 实用工具转储任何现有数据库。在您重新构建 DB_File 以使用 Berkeley DB 版本 2 或更高版本后,可以使用 db_load 重新创建数据库。有关更多详细信息,请参阅 Berkeley DB 文档。

在使用 2.x 或更高版本的 Berkeley DB 与 DB_File 之前,请阅读 "COPYRIGHT"

Berkeley DB 接口

DB_File 允许使用 Perl 5 中的 tie() 机制访问 Berkeley DB 文件(有关详细信息,请参阅 "tie()" in perlfunc)。此功能允许 DB_File 使用关联数组(适用于 DB_HASH 和 DB_BTREE 文件类型)或普通数组(适用于 DB_RECNO 文件类型)访问 Berkeley DB 文件。

除了 tie() 接口之外,还可以直接访问 Berkeley DB API 中提供的大多数函数。请参阅 "THE API INTERFACE"

打开 Berkeley DB 数据库文件

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 ;

filenameflagsmode 参数是其 dbopen() 对应参数的直接等效项。最后一个参数 $DB_HASH 执行 dbopen() 中 typeopeninfo 参数的功能。

在上面的示例中,$DB_HASH 实际上是对哈希对象的预定义引用。DB_File 有三个这样的预定义引用。除了 $DB_HASH 之外,还有 $DB_BTREE 和 $DB_RECNO。

每个预定义引用中允许的键仅限于等效 C 结构中使用的名称。因此,例如,$DB_HASH 引用只允许名为 bsizecachesizeffactorhashlordernelem 的键。

要更改其中一个元素,只需像这样对其进行赋值

$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 ;

这里需要对几个选项进行额外的讨论。使用时,键 hashcompareprefix 的 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_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

当您希望按给定顺序存储数据时,DB_BTREE 格式非常有用。默认情况下,键将按词法顺序存储,但正如您将从下一部分所示的示例中看到的那样,定义您自己的排序函数非常容易。

更改 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 数据库中的排序顺序,需要记住以下几点

  1. 创建数据库时必须指定新的比较函数。

  2. 一旦创建了数据库,您就无法更改排序顺序。因此,您必须在每次访问数据库时使用相同的比较函数。

  3. 重复键完全由比较函数定义。在上述不区分大小写的示例中,键:“KEY”和“key”将被视为重复项,并且分配给第二个键将覆盖第一个键。如果允许重复项(使用下面讨论的 R_DUP 标志),则仅将重复键的单个副本存储在数据库中 --- 因此(再次使用上面的示例)将三个值分配给键:“KEY”、“Key”和“key”将只在数据库中保留第一个键:“KEY”和三个值。在某些情况下,这会导致信息丢失,因此在必要时应注意提供完全限定的比较函数。例如,如果两个键在不区分大小写的比较中相等,则可以修改上述比较例程以另外区分大小写比较

    sub compare {
        my($key1, $key2) = @_;
        lc $key1 cmp lc $key2 ||
        $key1 cmp $key2;
    }

    现在,只有当键本身真正相同时,你才会有重复项。(注意:在 1996 年 11 月左右之前的数据库库版本中,保留了此类重复键,因此可以恢复比较为相等的键集中的原始键)。

处理重复键

BTREE 文件类型可以选择性地允许将单个键与任意数量的值关联。在创建数据库时将 $DB_BTREE 的标志元素设置为 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。此问题是由关联数组接口的工作方式引起的。基本上,当关联数组接口用于获取与给定键关联的值时,它只会检索第一个值。

尽管从上面的代码中可能并不明显,但关联数组接口可用于编写具有重复键的值,但不能用于从数据库中读回它们。

解决此问题的方法是使用名为 seq 的 Berkeley DB API 方法。此方法允许顺序访问键/值对。有关 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() 方法

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 =>      []

find_dup() 方法

$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

del_dup() 方法

$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 手册页中相关引文,其中定义了 R_CURSOR 标志与 seq 一起使用

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

DB_RECNO 提供了平面文本文件的接口。支持可变长度和固定长度记录。

为了使 RECNO 与 Perl 更兼容,所有 RECNO 数组的数组偏移量从 0 开始,而不是像 Berkeley DB 中那样从 1 开始。

与正常的 Perl 数组一样,可以使用负索引访问 RECNO 数组。索引 -1 指向数组的最后一个元素,-2 指向倒数第二个元素,依此类推。尝试访问数组开始之前的元素将引发致命运行时错误。

'bval' 选项

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

其他 RECNO 方法

如果您使用的是早于 5.004_57 的 Perl 版本,则绑定的数组接口非常有限。在上面的示例脚本中,pushpopshiftunshift 或确定数组长度将无法与绑定的数组一起使用。

为了使该接口对较旧版本的 Perl 更有用,DB_File 提供了许多方法来模拟缺少的数组操作。所有这些方法都可以通过从 tie 调用返回的对象访问。

以下是这些方法

$X->push(list) ;

list 的元素推送到数组的末尾。

$value = $X->pop ;

移除并返回数组的最后一个元素。

$X->shift

移除并返回数组的第一个元素。

$X->unshift(list) ;

list 的元素推送到数组的开头。

$X->length

返回数组中的元素数量。

$X->splice(offset, length, elements);

返回数组的拼接。

另一个示例

以下是一个更完整的示例,其中使用了一些上面描述的方法。它还直接使用 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

注释

  1. 不要像这样遍历数组 @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) )
  2. 请注意,每次使用 put 方法时,记录索引都是使用变量 $i 指定的,而不是使用文本值本身。这是因为 put 将通过该参数返回插入行的记录号。

API 接口

除了使用关联哈希或数组访问 Berkeley DB 之外,还可以直接使用 Berkeley DB 文档中定义的大多数 API 函数。

为此,你需要存储从关联中返回的对象的副本。

$db = tie %hash, "DB_File", "filename" ;

完成此操作后,你可以像这样直接访问 Berkeley DB API 函数作为 DB_File 方法

$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"

除了 close() 和 dbopen() 本身之外,dbopen 中定义的所有函数均可用。支持的函数的 DB_File 方法接口已实现为尽可能反映 Berkeley DB 的工作方式。特别是请注意

dbopen 中定义的用于在下面定义的方法的 flags 参数中的所有常量也可用。有关标志值的确切含义,请参阅 Berkeley DB 文档。

以下是可用方法的列表。

$status = $X->get($key, $value [, $flags]) ;

给定一个键 ($key),此方法从数据库中读取与之关联的值。从数据库中读取的值在 $value 参数中返回。

如果键不存在,则该方法返回 1。

目前未为此方法定义任何标志。

$status = $X->put($key, $value [, $flags]) ;

将键/值对存储在数据库中。

如果您使用 R_IAFTER 或 R_IBEFORE 标志,则 $key 参数将具有已插入键/值对的记录号。

有效标志为 R_CURSOR、R_IAFTER、R_IBEFORE、R_NOOVERWRITE 和 R_SETCURSOR。

$status = $X->del($key [, $flags]) ;

从数据库中删除所有键为 $key 的键/值对。

返回代码 1 表示请求的键不在数据库中。

R_CURSOR 是目前唯一的有效标志。

$status = $X->fd ;

返回底层数据库的文件描述符。

请参阅 "锁定:fd 的问题",了解为何不应使用 fd 锁定数据库的说明。

$status = $X->seq($key, $value, $flags) ;

此接口允许从数据库中进行顺序检索。有关完整详细信息,请参阅 dbopen

$key$value 参数都将设置为从数据库中读取的键/值对。

flags 参数是必需的。有效的标志值是 R_CURSOR、R_FIRST、R_LAST、R_NEXT 和 R_PREV。

$status = $X->sync([$flags]) ;

将所有缓存的缓冲区刷新到磁盘。

R_RECNOSYNC 是目前唯一有效的标志。

DBM 过滤器

DBM 过滤器是一段代码,在您始终希望对 DBM 数据库中的所有键和/或值进行相同转换时使用。一个示例是,在将数据写入数据库之前需要将其编码为 UTF-8,然后在从数据库文件中读取时对 UTF-8 进行解码。

有两种方法可以使用 DBM 过滤器。

  1. 使用下面定义的底层 API。

  2. 使用 DBM_Filter 模块。此模块隐藏了下面定义的 API 的复杂性,并附带了许多“罐装”过滤器,涵盖了一些常见用例。

建议使用 DBM_Filter 模块。

DBM 过滤器底层 API

有四种方法与 DBM 过滤器相关联。所有方法的工作方式相同,每个方法都用于安装(或卸载)单个 DBM 过滤器。每个方法都期望一个参数,即对子例程的引用。它们之间的唯一区别是安装过滤器的放置位置。

总结一下

filter_store_key

如果已使用此方法安装了过滤器,那么每次将键写入 DBM 数据库时都会调用该过滤器。

filter_store_value

如果已使用此方法安装了过滤器,那么每次将值写入 DBM 数据库时都会调用该过滤器。

filter_fetch_key

如果已使用此方法安装了过滤器,那么每次从 DBM 数据库读取键时都会调用该过滤器。

filter_fetch_value

如果已使用此方法安装了过滤器,那么每次从 DBM 数据库读取值时都会调用该过滤器。

您可以使用这些方法的任意组合,从无到全部四种。

所有过滤器方法都会返回现有的过滤器(如果存在)或 undef(如果不存在)。

要删除过滤器,请将 undef 传递给它。

过滤器

当 Perl 调用每个过滤器时,$_ 的本地副本将包含要过滤的键或值。通过修改 $_ 的内容来实现过滤。将忽略过滤器返回的代码。

一个示例——NULL 终止问题。

考虑以下场景。您有一个 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。

另一个示例 -- 键是 C int。

这是另一个实际示例。默认情况下,每当 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 ;

这次只使用了两个过滤器 -- 我们只需要操作键的内容,因此无需安装任何值过滤器。

提示和技巧

锁定:fd 的问题

在此模块的 1.72 版本之前,推荐的锁定DB_File数据库技术是从“fd”函数返回的文件句柄中获取 flock。不幸的是,此技术已被证明存在根本缺陷(感谢 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);

简单来说,会发生以下情况

  1. 使用“tie”打开数据库。

  2. 使用 fd 和 flock 锁定数据库。

  3. 读写数据库。

  4. 解锁并关闭数据库。

这是问题的关键。在步骤 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 上有许多可用于实现锁定的模块。每个模块都以不同的方式实现锁定,并且有不同的目标。因此,了解差异非常值得,以便您可以为您的应用程序选择合适的模块。以下是三个锁定包装器

Tie::DB_Lock

一个 DB_File 包装器,它为读取访问创建数据库文件的副本,以便您拥有某种多版本并发读取系统。但是,更新仍然是串行的。用于读取可能很长且可能发生一致性问题的数据库。

Tie::DB_LockFile

一个 DB_File 包装器,它可以在使用数据库时锁定和解锁数据库。通过在获取或释放锁时简单地重新绑定数据库来避免 tie-before-flock 问题。由于在会话过程中释放和重新获取锁的灵活性,如果应用程序遵循 POD 文档中的提示,可以将其按摩成一个适用于长时间更新和/或读取的系统。

DB_File::Lock

一个非常轻量的 DB_File 封装器,它在关联数据库之前简单地锁定一个锁文件,并在取消关联之后释放该锁。如果需要,它允许将同一个锁文件用于多个数据库以避免死锁问题。对于更新读取快速且简单的数据库以及 flock 锁定语义足够的情况,可以使用它。

与 C 应用程序共享数据库

从技术上来说,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 ;

untie() 陷阱

如果你使用 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.

这指出了真正的问题。最后,现在可以通过在解绑之前销毁 API 对象来修改脚本以修复原始问题

...
$x{123} = 456 ;

undef $X ;
untie %x ;

$X = tie %x, 'DB_File', 'tst.fil' , O_RDWR|O_CREAT
...

常见问题

为什么我的数据库中有 Perl 源代码?

如果您查看由 DB_File 创建的数据库文件的内容,有时可能会包含其中一部分 Perl 脚本。

发生这种情况是因为 Berkeley DB 使用动态内存分配缓冲区,随后这些缓冲区将被写入数据库文件。由于是动态的,因此在 DB 分配之前,内存可以用于任何目的。由于 Berkeley DB 在分配内存后不会清除内存,因此未使用的部分将包含随机垃圾。在 Perl 脚本被写入数据库的情况下,随机垃圾将对应于在编译脚本期间碰巧使用的动态内存区域。

除非您不喜欢 Perl 脚本的一部分可能嵌入在数据库文件中,否则不必担心。

如何使用 DB_File 存储复杂的数据结构?

虽然 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 调用中的一个参数错误时,您将收到此错误消息。不幸的是,有很多参数是错误的,因此很难弄清楚是哪一个。

以下是一些可能性

  1. 尝试在不关闭数据库的情况下重新打开数据库。

  2. 使用 O_WRONLY 标志。

“不允许裸字 'DB_File'”是什么意思?

当脚本中包含 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 或使用它的文章。

  1. Perl 中的全文本搜索,蒂姆·金茨勒 ([email protected]),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 存档中的 src/misc/db.1.85.tar.gz 中找到 Berkeley DB 1 版。

版权

版权所有 (c) 1995-2022 Paul Marquess。保留所有权利。此程序是免费软件;您可以在与 Perl 相同的条款下重新分发和/或修改它。

虽然 DB_File 受 Perl 许可证的约束,但它所使用的库(即 Berkeley DB)不受约束。Berkeley DB 有自己的版权和许可证。有关更多详细信息,请参阅 AGPL。请花时间阅读 Berkeley DB 许可证,并决定它如何影响您对该 Perl 模块的使用。

另请参阅

perldbopen(3)hash(3)recno(3)btree(3)perldbmfilterDBM_Filter

作者

DB_File 接口由 Paul Marquess <[email protected]> 编写。