Chinaunix首页 | 论坛 | 博客
  • 博客访问: 46138
  • 博文数量: 31
  • 博客积分: 2000
  • 博客等级: 大尉
  • 技术积分: 335
  • 用 户 组: 普通用户
  • 注册时间: 2009-05-26 22:47
文章分类

全部博文(31)

文章存档

2011年(1)

2009年(30)

我的朋友

分类: C/C++

2009-06-03 00:06:03

数据管理(六)

数据库

我们已经了解了如何使用文件来存储数据,所以为什么我们应该使用数据库呢?非常简单,在某些环境下,数据库特性提供了更好的方法来解决问题。使用数据库要好于存储文件,有下面两个理由:

我们可以存储尺寸上变化的数据记录,而这使用普通,无结构的文件是难于实现的。
数据库的存储与数据的读取使用索引。最大的好处就在于这个索引不必是一个简单的记录号,这在普通文件中是很容易使用的,而是字符串。

dbm数据库

所有的Linux版本,以及大多数的Unix变种,都会带有一个基本,但是十分有效的例程数据存储集合,名为dbm数据。dbm数据库对于存储相对静止的索引数据是十分优秀的。一些数据库的纯粹主义者会认为dbm根本就不是一个数据,而只是一个简单的索引文件存储系统。X/Open规范,然而,将dbm作为一个数据库,所以在我们的书中也会这样考虑。

dbm简介

尽管自由的关系型数据库,例如MySQL以及PostgreSQL,的兴趣,dbm数据库仍然在Linux中扮演着十分重要的角色。使用RPM的发行版本,例如RedHat以有SUSE,使用dbm作为安将包信息的底层存储介质。LDAP,Open LDAP的开源实现,也使用dbm作为存储机制。dbm的优点是很容易构建成为一个二进制包,因为并没有安装单独的服务器,不安装底层库也没有风险。

dbm数据库允许我们使用索引存储大小变化的数据结构,然后使用索引或是简单的搜索数据库来获取数据。dbm数据库对于那些经常访问,但是很少更新的数据是最好的选择,因为他的特点就是创建实体慢,但是读取却相当快。

在一这点上,我们遇到一个问题:几年以来,存在多个具有不同API与特点的dbm数据库变体。有原始的dbm集合,新的dbm集合,称之为ndbm,以及GNU实现,gdbm。GNU实现可以同时模拟老的dbm以及ndbm接口,但是本质上却有一个与其他的实现完全不同的接口。不同的Linux版本带有不同版本的dbm库,尽管大多数的选择是使用gdbm库,因为他可以模拟另外两个接口类型。

在这里我们会关注ndbm接口,因为那是由X/Open标准化的,而且因为他要比gdbm实现的使用更为简单。

获得dbm

如果我们对其他的dbm实现感兴趣,有一个BSD授权的版本可以在ftp://ftp.cs.berkeley.edu/ucb/4bsd/或是处得到。Sleepycat软件()有一个开源产品,伯克利数据也支持dbm/ndbm接口。GNU版本可以在处理获得。

故障修复也重新安装dbm

这一章的编写假设我们已经安装了与X/OPEN兼容的dbm版本。如果我们在编译这些例子时遇到问题,或者是由于不存在ndbm.h头文件,此时编译器会抱怨dbm没有定义,或者是编译程序的链接过程失败--此时我们可以尝试安装dbm库的GNU版本的更新版本以解决这些问题。

检测我们所拥有的dbm版本的最简单的方法就是查找包含文件gdbm.h,ndbm.h以及dbm.h。我们会发现后两者位于一个子目录中,例如/usr/include/gdbm,这就意味着底层的实现是gdbm,但是已经为我们安装了兼容的库文件。如果我们不能在我们的系统上找到ndbm.h文件,我们可以自己安装GNU gdbm接口。首先,创建一个临时目录。然后到站点搜索gdbm库的最新版本。其文件名也许是gdbm_?_?_tar.gz的形式。将文件下载到我们的临时目录中,并且使用'tar zxvf '来解开文件。然后阅读README文件,这会告诉我们如何编译与安装库文件。通常我们首先运行./configure命令来检测我们的系统是如何配置的,然后我们运行make命令来编译程序。最后我们运行make install与make install-compat来安装基本文件与额外的兼容文件。我们也许需要root权限来运行安装步骤,而通常先使用-n选项来运行make命令是一个好主意(例如,make -n install),以便检测将会做些什么。

现在我们应拥有了ndbm的X/OPEN兼容版本,通常位于我们系统的/usr/local部分。我们的编译器设置在默认情况下也许不会搜索这些位置,在这种情况下我们需要在gcc的命令之后添加-I/usr/local/include选项来查找头文件以及-L/usr/local/lib选项来查找库文件:

$ gcc -I/usr/local/include program.c -L/usr/local/lib -o program -lgdbm

我们的系统已经拥有了ndbm.h文件,但是在/usr/include子目录中,例如/usr/include/gdbm,我们也许需要在编译行添加-I/usr/include/gdbm:

$ gcc -I/usr/include/gdbm program.c -o program -lgdbm

dbm例程

与我们在第六章所讨论的curses类似,dbm实用程序由程序在编译时必须链接的一个头文件与一个库文件所组成。库文件简单的称之为dbm,所以我们在编译命令行中添加-ldbm(或-lgdbm)来进行链接。头文件为ndbm.h。

在我们试着解释这些函数之前,理解dbm数据库所要实现在的目标是十分重要的。一旦我们理解这些,我们就会更好的理解如何使用dbm函数。

dbm数据库的基本元素是一个要存储的数据块,以及一个作为读取数据的键值的数据块。每一个dbm数据库对于要存储的每一个数据块必須具有唯一的键值。对于键值或是数据并没有严格的限制,对于使用过大的数据与键值也没有定义任何错误。规范允许实现限制键值/数据的大小为1023字节,但是通常而言并没有任何限制,因为实现要比他们所需要的更为灵活。键值的作用是所存储数据的索引。

要将这些块作为数据进行操作,ndbm.h包含文件定义了一个名为datum的新类型。这个类型的实际内容是与实现相关的,但是他至少要具有下列成员:

void *dptr;
size_t dsize

datum是一个由typedef定义的类型。同时在ndbm.h文件中还有一个类型定义dbm,他是一个用于访问数据的结构,与访问文件的FILE相类似。dbm类型的内部是与实现相关的,并且绝不应被使用。

当我们使用dbm库时要引用一个数据块,我们必须声明一个datum,设置dptr指向数据起始处,并且设置dsize包含其尺寸。要存储的数据以及访问数据所用的索引都可以通过datum类型来引用。

dbm类型与FILE类型的思想最为类似。当我们打开一个dbm数据库时,系统就会创建两个物理文件,一个以.pag为扩展名,另一个以.dir为扩展名。并且返回一个dbm指针,使用这个指针来访问这两个文件。这两个不要进行直接的读写操作,因为他们的本意是只通过dbm例程来访问的。

如果我们使用本地的gdbm库,那么这两个文件已经进行合并,从而只创建一个文件。

如果我们熟悉SQL数据,那么我们就会注意到在dbm数据中并没有相关联的表或列。这些结构不是必须的,因为dbm并不会在数据的每一个要存储的数据项目上强制固定的尺寸,也不会要求数据的内部结构。dbm库在非结构化的二进制数据上进行操作。

dbm访问函数

现在我们已经介绍了dbm库工作的基础,我们可以详细的了解一下dbm函数了。主要的dbm函数原型如下:

#include
DBM *dbm_open(const char *filename, int file_open_flags, mode_t file_mode);
int dbm_store(DBM *database_descriptor, datum key, datum content, int store_mode);
datum dbm_fetch(DBM *database_descriptor, datum key);
void dbm_close(DBM *database_descriptor);

dbm_open

这个函数可以用来打开一个已经存在的数据库,也可以用来创建一个新的数据库。filename参数是一个基础文件名,没有.dir或是.pag扩展名。

其余的参数与open函数的第二个和第三个函数相同,这我们已经在第三章进行了介绍。我们也可以使用相同的#define定义。第二个参数可以控制数据是否可以进行读,写或是读写操作。如果我们要创建一个新的数据,标记必须为O_READ与O_CREAT来允许创建文件。第三个参数指定了将要创建的文件的初始权限。

dbm_open返回一个指向DBM类型的指针。这个指针会用在接下来的所有数据库访问中。如果失败,就会返回(DBM *)0。

dbm_store

我们使用这个函数来向数据库中存入数据。正如我们在前面所提到的,所有的数据必须使用唯一的索引进行存储。要定义我们希望存储的数据以及用于引用的索引,我们必须设置两个datum类型:一个指向索引,而另一个指向实际的数据。最后一个参数,store_mode,控制当使用已经存在的索引存储数据时会出现什么情况。如果设置为dbm_insert,则会存储失败,并且dbm_store返回1。如果设置为dbm_replace,新数据就会覆盖已经存在的数据,并且dbm_store返回0。如果是其他错误,dbm_store就会返回一个负值。

dbm_fetch

dbm_fetch函数用于由数据库中读取数据。这个函数使用前面dbm_open调用返回的指针,以及一个必须设置指向索引的datum类型作为参数,并且会返回一个datum类型。如果与所用索引相关的数据在数据库中查找成功,返回的datum结构就会将dptr与dsize的值设置为指向返回的数据。如果索引没有查找成功,dptr就会被设置为null。

需要记住的一点,dbm_fetch只会返回一个指向数据的指针。实际的数据仍然会存储在位于dbm库的本地存储空间中,并且应在调用其他的dbm函数之前拷贝到程序变量中。

dbm_close

这个函数会关闭使用dbm_open打开的数据库,并且向其传递一个由前面的dbm_open调用所返回的dbm指针作为参数。

试验--一个简单的dbm数据库

现在我们已经了解了dbm数据库的基本函数了,现在我们已经了解了足够多的知识可以来编写我们的第一个dbm程序:dbm1.c。在这个程序中,我们将会使用一个名为test_data的结构。

1 首先,在程序的开头部分将会是#include,#define,main函数以及test_data结构的声明:

#include
#include
#include
#include
#include
#include
#define TEST_DB_FILE “/tmp/dbm1_test”
#define ITEMS_USED 3
struct test_data {
    char misc_chars[15];
    int any_integer;
    char more_chars[21];
};
int main()
{

2 在main函数中,我们设置items_to_store以及items_received结构,键值字符串,以及datum类型:

struct test_data items_to_store[ITEMS_USED];
struct test_data item_retrieved;
char key_to_use[20];
int i, result;
datum key_datum;
datum data_datum;
DBM *dbm_ptr;

3 在声明了一个指向dbm类型结构的指针之后,我们现在可以打开我们的测试数据库进行读写操作,如果需要则要创建这个数据库:

dbm_ptr = dbm_open(TEST_DB_FILE, O_RDWR | O_CREAT, 0666);
if (!dbm_ptr) {
    fprintf(stderr, “Failed to open database\n”);
    exit(EXIT_FAILURE);
}

4 现在我们向items_to_store结构添加一些数据:

memset(items_to_store, ‘\0’, sizeof(items_to_store));
strcpy(items_to_store[0].misc_chars, “First!”);
items_to_store[0].any_integer = 47;
strcpy(items_to_store[0].more_chars, “foo”);
strcpy(items_to_store[1].misc_chars, “bar”);
items_to_store[1].any_integer = 13;
strcpy(items_to_store[1].more_chars, “unlucky?”);
strcpy(items_to_store[2].misc_chars, “Third”);
items_to_store[2].any_integer = 3;
strcpy(items_to_store[2].more_chars, “baz”);
memset(items_to_store, ‘\0’, sizeof(items_to_store));
strcpy(items_to_store[0].misc_chars, “First!”);
items_to_store[0].any_integer = 47;
strcpy(items_to_store[0].more_chars, “foo”);
strcpy(items_to_store[1].misc_chars, “bar”);
items_to_store[1].any_integer = 13;
strcpy(items_to_store[1].more_chars, “unlucky?”);
strcpy(items_to_store[2].misc_chars, “Third”);
items_to_store[2].any_integer = 3;
strcpy(items_to_store[2].more_chars, “baz”);

5 对于每一个记录,我们需要为将来的引用构建一个键值。这是每一个字符串和整数的第一个字符。这个键值然后会使用key_datum进行榱,而data_datum指向items_to_store记录。然后我们在数据库中存储这些数据。

for (i = 0; i < ITEMS_USED; i++) {
    sprintf(key_to_use, “%c%c%d”,
           items_to_store[i].misc_chars[0],
           items_to_store[i].more_chars[0],
           items_to_store[i].any_integer);
    key_datum.dptr = (void *)key_to_use;
    key_datum.dsize = strlen(key_to_use);
    data_datum.dptr = (void *)&items_to_store[i];
    data_datum.dsize = sizeof(struct test_data);
    result = dbm_store(dbm_ptr, key_datum, data_datum, DBM_REPLACE);
    if (result != 0) {
        fprintf(stderr, “dbm_store failed on key %s\n”, key_to_use);
        exit(2);
    }
}

6 接下来我们要测试我们是否可以取得这些新数据,最后,我们必须关闭数据库:

sprintf(key_to_use, “bu%d”, 13);
key_datum.dptr = key_to_use;
key_datum.dsize = strlen(key_to_use);
  data_datum = dbm_fetch(dbm_ptr, key_datum);
  if (data_datum.dptr) {
      printf(“Data retrieved\n”);
      memcpy(&item_retrieved, data_datum.dptr, data_datum.dsize);
      printf(“Retrieved item - %s %d %s\n”,
             item_retrieved.misc_chars,
             item_retrieved.any_integer,
             item_retrieved.more_chars);
  }
  else {
      printf(“No data found for key %s\n”, key_to_use);
  }
  dbm_close(dbm_ptr);
  exit(EXIT_SUCCESS);
}

当我们编译并且运行这个程序时,我们可以得到下面的简单输出:

$ gcc -o dbm1 -I/usr/include/gdbm dbm1.c -lgdbm
$ ./dbm1
Data retrieved
Retrieved item - bar 13 unlucky?

如果我们的gdbm是以兼容模式安装的,我们就会得上面的输出结果。如果编译失败,我们就需要按照我们前面所描述的来安装GNU gdbm库兼容文件,并且/或是在我们编译时指定额外的目录:

$ gcc -I/usr/local/include -L/usr/local/lib -o dbm1 dbm1.c -ldbm

如果仍然编译失败,试着将-ldbm部分替换为-lgdbm:

$ gcc –I/usr/local/include –L/usr/local/lib -o dbm1 dbm1.c –lgdbm

工作原理

首先,我们打开数据库,如果需要,则要创建数据库。然后我们需要填充我们用作测试数据的items_to_store的三个成员。对于这三个成员的每一个,我们创建了一个索引键值。为了使其简单,我们使用两个字符串的第一个字符,加上存储的整数作为键值。

然后我们设置两个datum结构,一个用于键值,另一个用于要存储的数据。在数据库中存储这三个项目之后,我们组织一个新的键值,并且设置一个datum结构来指向他。然后我们使用这个键值由数据库中读取数据。我们通过检测返回的datum中的dptr不为null来确保成功。如果其不为空,我们就可以将所获取的数据(存储在dbm库中)拷贝到我们自己的结构中,在这里要小心使用dbm_fetch把返回的尺寸(如果我们没有这样做,并且正在使用变化尺寸的数据,我们就会试着拷贝本不存在的数据)。最后,我们打印出所取得的数据来显示我们正确的读取了数据。

阅读(534) | 评论(0) | 转发(0) |
0

上一篇:数据管理(五)

下一篇:数据管理(七)

给主人留下些什么吧!~~