磁盘持久化
实现了在内存中储存硬编码的数据表之后,我们需要更近一步,将内存中的数据保存在文件中,即实现数据持久化。
为了使这一点变得容易,我们将创建一个称为页面缓存的抽象概念。我们向页面缓存询问页码x,它返回了一块内存。它首先在其缓存中查找。在发生高速缓存未命中时,它将数据从磁盘复制到内存中(通过读取数据库文件)。
页面缓存(Pager)
页面缓存器结构
首先我们来明确一下页面缓存器的结构:
typedef struct {
int file_descriptor;//文件描述
uint32_t file_length;//文件长度
void* pages[TABLE_MAX_PAGES];//所在页
} Pager;
数据表结构
其次,由于确定页面缓存器结构,我们的数据表结构也要发生改变,使其能够包含页面缓存器结构。
typedef struct {
- void* pages[TABLE_MAX_PAGES];
+ Pager* pager;
uint32_t num_rows;
} Table;
new_table()
原本我们的数据表结构中含有pages字段,然而pages字段的存在只是单纯的为了给数据表数据按页分配内存,现在我们引入页面缓存器,它的作用是将文件数据复制到内存中。
由于数据表结构体的变化,我们创建新数据表的函数new_table()也会发生相应的变化:
-Table* new_table() {
+Table* db_open(const char* filename) {
+ Pager* pager = pager_open(filename);
+ uint32_t num_rows = pager->file_length / ROW_SIZE;
+ Table* table = malloc(sizeof(Table));
- table->num_rows = 0;
+ table->pager = pager;
+ table->num_rows = num_rows;
return table;
}
get_page()
我们定义该函数需要实现如下几个功能:
- 打开数据库文件
- 初始化页面缓存器数据结构
- 初始化表结构
按照顺序先来讲一下打开数据库文件的open()函数。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int oflag, ... /* mode_t mode */);
open函数用来打开或创建一个文件,若成功返回文件描述符,打开失败返回-1。pathname是要打开或创建文件的名字。
oflag参数是下列一个或多个常量执行按位或运算的结果
- O_RDONLY 只读打开
- O_WRONLY 只写打开
- O_RDWR 读写打开
上面三个常量必须指定一个并且只能指定一个,下面一些常量则是可选的,配合使用时使用|链接:
- O_APPEND 将写入追加到文件的尾端
- O_CREAT 若文件不存在,则创建它。使用该选项时,需要第三个参数mode,用来指定新文件的访问权限位
- O_EXCL 如果同时指定了O_CREAT,而文件已经存在,则会出错
- O_TRUNC 如果此文件存在,而且为只写或读写模式成功打开,则将其长度截短为0
- O_NOCTTY 如果pathname指的是终端设备,则不将该设备分配作为此进程的控制终端
- O_NONBLOCK 如果pathname指的是一个FIFO文件、块设备文件或字符设备文件,则此选项将文件的本次打开操作和后续的I/O操作设置为非阻塞模式
第二个我们需要知道的函数是lseek():
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fildes, off_t offset, int whence);
每一个已打开的文件都有一个读写位置, 当打开文件时通常其读写位置是指向文件开头, 若是以附加的方式打开文件(如O_APPEND), 则读写位置会指向文件尾. 当read()或write()时, 读写位置会随之增加,lseek()便是用来控制该文件的读写位置. 参数fildes 为已打开的文件描述词, 参数offset 为根据参数whence来移动读写位置的位移数.
参数 whence 为下列其中一种:
SEEK_SET 参数offset 即为新的读写位置.
SEEK_CUR 以目前的读写位置往后增加offset 个位移量.
SEEK_END 将读写位置指向文件尾后再增加offset 个位移量. 当whence 值为SEEK_CUR 或
SEEK_END 时, 参数offet 允许负值的出现.
下列是教特别的使用方式:
1) 欲将读写位置移到文件开头时:lseek(int fildes, 0, SEEK_SET);
2) 欲将读写位置移到文件尾时:lseek(int fildes, 0, SEEK_END);
3) 想要取得目前文件位置时:lseek(int fildes, 0, SEEK_CUR);
返回值:当调用成功时则返回目前的读写位置, 也就是距离文件开头多少个字节. 若有错误则返回-1, errno 会存放错误代码.
+Pager* pager_open(const char* filename) {
+ int fd = open(filename,
+ O_RDWR | // Read/Write mode
+ O_CREAT, // Create file if it does not exist
+ S_IWUSR | // User write permission
+ S_IRUSR // User read permission
+ );
+
+ if (fd == -