使用 Perl 创建和使用数据库
Perl 程序员,如同任何其他语言的程序员一样,通常需要存储大量数据。为了使这些数据易于管理,它需要以方便访问的格式存储。使存储的数据易于写入也总是有好处的。
即使 Perl 是一种出色的文本处理语言,但在许多情况下,更结构化的类似数据库的格式可以提供更快速的访问。此外,Perl 脚本可能还需要读取或写入也通过 C 程序访问的数据库。
为了实现这一点,Perl 发行版包含一些软件包,使 Perl 程序员可以访问 Unix 环境中可用的各种不同数据库格式。这些格式包括:Berkeley DB 格式、自由软件基金会的 GDBM 格式和 NDBM 格式。
关联数组(或“哈希”)是 Perl 程序员可用的最强大的数据结构之一。对于那些熟悉传统数组(在 C、Pascal 或 Perl 中)的人来说,关联数组可以被认为是一个使用任意字符串而不是整数下标索引的数组。基本上,关联数组是一种数据结构,允许程序员将一个字符串(键)与另一个字符串(其值)关联起来。
这是一个关联数组的示例,可用于将星期几的缩写名称转换为其全名。
%days = ( "Sun", "Sunday", "Mon", "Monday", "Tue", "Tuesday", "Wed", "Wednesday", "Thu", "Thursday", "Fri", "Friday", "Sat", "Saturday" );
变量名 days 前面的 % 用于告诉 Perl 该变量是一个关联数组。如图所示,关联数组通过使用相互关联的值对进行初始化。
要访问存储在关联数组中的数据,您可以使用类似于以下的语法
$long_name = $days{"Sun"};
此表达式会将标量变量 long_name 设置为与键“Sun”关联的值,在本例中为字符串“Sunday”。
您已经可以看到,关联数组可以成为组织 Perl 脚本内部使用的数据的强大工具。通过创建由多个字段组成的 值,这种技术可以很容易地扩展到更有用的东西。例如,这个简单的地址簿数据库,其中关联数组值中的多个字段用冒号分隔
$phone_db = ( "Bill Jones", "123 West Avenue:New York, NY:12345", "Jane Smith", "6789 1st Street:Chicago, IL:56789" );
可以使用如下表达式在此数据库中添加新条目
$phone_db{"Bill Smith"} = join(":", $street, $city, $zip_code);可以使用如下表达式从此简单数据库中提取数据
($street, $city, $zip_code) = split(/:/, $phone_db{"Bill Smith"});正如您所见,这些数组对于在 Perl 脚本内部操作数据非常方便。但是,我们如何轻松地将这些数据导出到文件中,以便我们的脚本或其他程序可以访问这些数据?一种简单的方法是使用文本文件,数据库的字段用冒号分隔。这种方法将使从 Perl 脚本中写出数据库变得非常简单。可以使用如下代码来完成
while (($name, $record) = each %phone_db) { print "$name:$record\n"; }这种方法不利于在文件中执行搜索,因为我们需要读取文件中平均一半的行才能找到我们正在寻找的记录。此外,用其他语言(例如 C)编写代码来搜索这样的文件可能不如我们编写的 Perl 脚本那么简单。
为了解决这个问题,Perl 支持将关联数组“绑定”到上述各种类型的数据库格式。这使得 Perl 程序员可以像对关联数组执行操作一样轻松地创建、访问和更新流行的 Unix 数据库格式的数据库。
Perl 版本 5 包含一组“软件包”,用于操作各种数据库格式。这些软件包是
DB_File—用于 Berkeley DB 数据库
GDBM_File—用于自由软件基金会的 GDBM 数据库
NDBM_File
ODBM_File
SDBM_File
要使用任何这些数据库软件包,Perl 程序员必须在脚本的开头 包含 该软件包,使用以下语句
use DB_File;此外,还需要包含 Fcntl 软件包。这可以通过在脚本开头包含以下内容来完成
use Fcntl;Perl 发行版中包含这些软件包中每一个的手册页。为了简洁起见,本文仅讨论 DB_File 软件包及其关联的 Berkeley DB 数据库。
数据库在 Perl 中使用 tie() 函数打开。此函数负责将关联数组与数据库软件包“连接”起来。对关联数组执行的操作然后由数据库软件包转换为对数据库文件本身进行操作的函数调用。
这是一个使用 DB_File 数据库软件包打开名为“phone.db”的数据库的示例
tie (%phone_db, DB_File, "phone.db") || die ("Cannot open phone.db");
此命令将名为 phone_db 的关联数组绑定到名为“phone.db”的 Berkeley DB 数据库文件。在本例中,该文件必须存在并且必须可由 Perl 脚本读取。
创建数据库几乎和打开数据库一样简单。以下命令将在当前目录中创建一个名为“phone.db”的数据库,并将文件的权限设置为所有者读写,其他人只读。仅当文件尚不存在时才会创建该文件。如果数据库文件在当前目录中存在,则将简单地打开该数据库文件以供 Perl 脚本进行读写访问。
tie (%phone_db, DB_File, "phone.db", O_CREAT|O_RDWR, 0644) || die ("Cannot create or open phone.db");
O_CREAT 和 O_RDWR 标志与用作 Unix open() 系统调用的参数的标志相同。它们指定如果文件不存在则应创建该文件,并以读写访问权限打开。
从数据库读取数据的方式与从关联数组读取数据的方式完全相同。如果键已知,则可以使用如下表达式从文件中读取特定记录
$record = $phone_db{"Bill Smith"};
可以使用如下代码扫描数据库文件中的所有记录(以看似随机的顺序)
while (($name, $record) = each %phone_db) { [ commands to process data here ] }在每次遍历 while 循环时,$name 标量变量将被设置为数据库中的键值,$record 变量将被设置为与该键关联的数据。
可以通过在关联数组中创建一个新键并设置键的值来将新数据写入数据库。这可以通过类似于以下的命令来完成
$phone_db{"Bill Smith"} = $data;
其中 $data 是要与键“Bill Smith”关联的信息。对关联数组所做的任何更改都将写入其对应的数据库文件。
可以从数据库中删除键,其方式与从 Perl 中的关联数组中删除项目的方式完全相同——使用 delete() 函数。以下代码删除了数据库中指的是“Bill Smith”的记录。
delete $phone_db{"Bill Smith"};
对关联数组的更改可能不会立即写入数据库文件。为了确保更改已成功写入数据库文件,必须关闭该文件。
关闭数据库文件涉及到从数据库包中 解除绑定 关联数组。这可以通过以下方式使用 untie() 函数来完成
untie(%phone_db);
这将关闭数据库文件,并在必要时对文件进行更新。关联数组 %phone_db 现在不再用于访问数据库中的记录。
此处提供的所有示例都使用默认类型的 Berkeley DB 数据库,即 DB_HASH 类型。这种形式的数据库使用哈希表(就像 Perl 所做的那样)来存储数据库文件中的键及其值。Berkeley DB 软件包提供了另外两种类型的数据库:DB_BTREE 和 DB_RECNO。
DB_BTREE 格式使用排序的平衡二叉树来存储键值对。这种格式允许以排序顺序存储和读取数据,而不是 DB_HASH 格式产生的看似随机的顺序。默认比较例程按词汇顺序(按字母顺序)对数据库文件中的键进行排序。DB_File 手册页更详细地讨论了这种格式,并展示了如何用您自己的例程替换默认比较例程。
DB_RECNO 格式旨在操作平面文本文件。它与普通 Perl 数组(而不是关联数组)绑定(使用 tie())。使用数字索引此数组会提供在该数据库文件行中找到的文本。DB_File 手册页中也更详细地讨论了这种格式。
所需的数据库文件格式是通过 tie() 函数的附加参数指定的。
tie (%phone_db, DB_File, "phone.db", O_RDONLY, 0644, $DB_BTREE) || die ("Cannot open phone.db");
此命令将以只读访问模式打开名为“phone.db”的 DB_BTREE 数据库。如果该文件不存在,则命令失败。
有时需要在 Perl 脚本中对关联数组进行排序。按关联数组的键值进行排序可以这样做
for (sort keys %phone_db) { print "$_ = $phone_db{$_}\n"; }
此循环的每次迭代都会将 $_ 标量设置为按字母顺序从关联数组提供的键值。这种方法非常适合按键对关联数组进行排序。按关联数组的值排序稍微困难一些
sub sort_by_value { ( $phone_db{$a} cmp $phone_db{$b} ) || \ ( $a cmp $b ); } for (sort sort_by_value keys %phone_db) { print "$_ = $phone_db{$_}\n"; }这段代码使用一个特殊例程替换了 sort() 用于对其给定元素进行排序的默认例程。此例程 sort_by_value 首先按值对关联数组进行排序,其次按键进行排序(即,当两个值相同时,比较它们各自的键以确定哪个应该先出现)。
请记住,这两种对关联数组进行排序的方法实际上并没有以任何方式重新排列数组。它们只是提供了一种以特定排序顺序从关联数组中提取每个键值对的方法。
列表 1 中提供了一个关于如何在 Perl 中使用数据库的示例,这是一个旨在维护网站点击次数数据库的简短脚本。该脚本读取 NCSA HTTPD 访问 日志文件,将信息存储在数据库中,并创建一个 HTML 页面来显示站点的所有统计信息。
此实现并不完整——它仅跟踪访问了哪些文档及其大小。更完整的实现还可以存储有关访问 Web 服务器的主机的信息。在特定时间间隔后“使”数据库中的条目“过期”的方法也是一个方便的功能。
该脚本首先读取现有的数据库文件,并将所有数据放入以文档文件名索引的关联数组中。接下来,脚本从标准输入读取访问日志文件,并将数据放入存储统计信息的关联数组中。最后,脚本创建一个使用表格显示统计信息的 HTML 页面。
Randy Scott 是密尔沃基工程学院计算机工程专业的高年级学生。他使用 Unix 和 C 编程已有近三年,并在过去六个月中成为狂热的 Perl 爱好者。有关本文的任何问题或评论,可以发送至 scottr@bork.com。