本地缓存数据库设计原理
1、分两个文件一个索引文件 .idx,一个数据文件 .dat
2、索引文件结构 : [索引指针 | 索引指针| …|索引指针|索引记录|…|索引记录]
索引指针:用来记录索引记录的偏移量(当时的文件size)
索引记录:hash链表 结构=》 [下一个借点指针 | key| 数据偏移量(当时的文件size)|数据长度]
代码如下:
具体说明:
0、初始化的时候创建索引和数据文件
(索引文件组成:索引(存放hash偏移量)+ 索引块(4字节的下一个hash的偏移量+key(key 128位长度不足补0)+dataSize+dataLenth))
(说明:索引(用key的time33算法后得到的二进制数据去读取,长度4字节)(存储的是当前文件的size值)
索引记录块(用索引记录的值去读取长度,128字节)
)
//获取数据
1、输入key,用time33算法计算出key的偏移量
2、通过key的偏移量,获取hash记录块的偏移量
3、通过hash记录块偏移量获取hash记录块数据。判断key是否相等,不等就读取下一个记录块,直到找到,
4、通过data偏移量(当时的文件size)和data的长度,获取data的数据
//数据插入
1、fstat()获取索引文件的文件size,数据文件的文件size
2、构造hash索引记录块(4字节的下一个hash的偏移量+key(key 128位长度不足补0)+dataSize+dataLenth)
3、读取key索引偏移量(当时文件的size)去获取hash记录块,如果没有就直接插入记录块,如果有就第4步
4、读取已经有的hash记录块,判断key值是否相等,如果相等,就报存在。如果不等统插入索引块,数据。
//数据删除
找到数据,如果是头节点,就修改索引里存放的偏移量为下一个索引块的偏移量,如果不是就交换节点。
亲测可用
不足:1、没时间做并发控制。2、删除索引以后数据文件没有改变。
针对以上两个问题,有知道比较好做法的小伙伴留言哈。
<?php
define('DB_INSERT','1');
define('DB_REPALCE','2');
define('DB_STORE','3');
define('DB_BUCKET_SIZE','262144');
define('DB_KEY_SIZE','128');
define('DB_INDEX_SIZE',DB_KEY_SIZE+12);
define('DB_KEY_EXISTS','1');
define('DB_FAILURE','-1');
define('DB_SUCCESS','0');
/**
*
*/
class DB {
private $idx_fp;//索引文件句柄
private $dat_fp;//数据文件句柄
private $closed='false';//关闭标识
public function __construct($pathname)
{
if($pathname){
$this->open($pathname);
}
}
public function __destruct()
{
$this->close();
}
/**打开索引文件和数组文件
* @since 2018/12/14
* @author weihua.zhang
* @param $pathname
* @return string
*/
public function open($pathname){
$idx_path = $pathname.'.idx';
$dat_path = $pathname.'.dat';
if(!file_exists($idx_path)){
$init = true;
$mode = 'w+b';
}else{
$init = false;
$mode = 'r+b';
}
//打开文件,如果没有就创建
$this->idx_fp = fopen($idx_path, $mode);
if(!$this->idx_fp){
return DB_FAILURE;
}
//初始化索引块
if($init){
$elem = pack('L',0x00000000);//128字节
for($i=0;$i<DB_BUCKET_SIZE;$i++){
fwrite($this->idx_fp, $elem,4);
}
}
//打开文件,如果没有就创建
$this->dat_fp = fopen($dat_path, $mode);
if(!$this->idx_fp){
return DB_FAILURE;
}
return DB_SUCCESS;
}
/**key偏移量计算
* @since 2018/12/14
* @author weihua.zhang
* @param $key
* @return float|int
*/
private function _offset($key){
return $this->_hash($key)%DB_BUCKET_SIZE*4;
}
/**times33算法
* @since 2018/12/14
* @author weihua.zhang
* @param $string
* @return int
*/
private function _hash($string){
$string = substr(md5($string), 0,8);
$hash = 0;
for ($i=0; $i <8 ; $i++) {
$hash += 33*$hash + ord($string{$i});
}
return $hash&0X7FFFFFFF;
}
/**读文件
* @since 2018/12/14
* @author weihua.zhang
* @param $handle 文件句柄
* @param $offset 偏移量
* @param $lenth 长度
* @return bool|string
*/
private function _fileRead($handle,$offset,$lenth){
fseek($handle, $offset,SEEK_SET);//设置文件光标位置
return fread($handle, $lenth);
}
/**写文件
* @since 2018/12/14
* @author weihua.zhang
* @param $handle 文件句柄
* @param $offset 偏移量
* @param $data 数据
* @param $lenth 长度
*/
private function _fileWrite($handle,$offset,$data,$lenth){
fseek($handle, $offset,SEEK_SET);
fwrite($handle, $data,$lenth);
}
/**获取数据操作
* @since 2018/12/14
* @author weihua.zhang
* @param $key
* @return bool|string
*/
public function fetch($key){
$offset = $this->_offset($key);
$pos = unpack('L', $this->_fileRead($this->idx_fp,$offset,4));//获取hash链表的偏移量
$pos = $pos[1];
//查找数据
$found = false;
while ($pos){
$block = $this->_fileRead($this->idx_fp,$pos,DB_INDEX_SIZE);//获取索引记录
$cpKey = substr($block, 4,DB_KEY_SIZE);
//找到数据
if(!strncmp($key, $cpKey, strlen($key))){
$dataoff = unpack('L', substr($block, DB_KEY_SIZE+4,4));
$dataoff = $dataoff[1];//数据偏移量
$datalen = unpack('L', substr($block, DB_KEY_SIZE+8,4));
$datalen = $datalen[1];//数据长度
$found = true;
break;
}
//没找到数据,就查找下一个索引记录
$pos = unpack('L', substr($block, 0,4));//获取hash链表的偏移量
$pos = $pos[1];
}
if(!$found){
return DB_FAILURE;
}
$data = $this->_fileRead($this->dat_fp,$dataoff,$datalen);
return $data;
}
/**插入数据
* @since 2018/12/14
* @author weihua.zhang
* @param $block 索引记录
* @param $data 存储的数据
* @param $offset 索引偏移量
* @param $idxoff 新增索引记录偏移量
* @param $datoff 数据偏移量
*/
private function _insert_data($block,$data,$offset,$idxoff,$datoff){
$this->_fileWrite($this->idx_fp,$offset,pack('L',$idxoff),4);//新增索引
$this->_fileWrite($this->idx_fp,$idxoff,$block,DB_INDEX_SIZE);//添加索引记录
$this->_fileWrite($this->dat_fp,$datoff,$data,strlen($data));//添加数据
}
/**写入操作
* @since 2018/12/14
* @author weihua.zhang
* @param $key
* @param $data
* @return string
*/
public function insert($key,$data){
$offset = $this->_offset($key);
$idxoff = fstat($this->idx_fp);//获取索引表的统计信息,计算索引记录的偏移量
$idxoff = $idxoff['size'];
$datoff = fstat($this->dat_fp);//获取数据表的统计信息,计算数据记录的偏移量
$datoff = $datoff['size'];
$keylen = strlen($key);
if($keylen>DB_KEY_SIZE){
return DB_FAILURE;
}
//构造索引记录块
$block = pack('L',0000);
$block.= $key;
$space = DB_KEY_SIZE-$keylen;
for ($i=0; $i < $space; $i++) {
$block.= pack('C',0);
}
$block .= pack('L',$datoff);
$block .= pack('L',strlen($data));
//判断key是否存在,如果没有就写入索引记录的偏移量
$pos = unpack('L', $this->_fileRead($this->idx_fp,$offset,4));
$pos = $pos[1];
if($pos==0){
$this->_insert_data($block,$data,$offset,$idxoff,$datoff);
return DB_SUCCESS;
}
$found = false;
while ($pos) {
//获取索引记录块
$temp_block = $this->_fileRead($this->idx_fp,$pos,DB_INDEX_SIZE);
$cpKey = substr($temp_block, 4,DB_KEY_SIZE);
if(!strncmp($key, $cpKey, strlen($key))){
$found = true;
break;
}
$prev = $pos;//记录本次的偏移量,因为下次索引记录可能是空的
$pos = unpack('L',substr($temp_block, 0,4));
$pos = $pos[1];
}
if($found){
return DB_KEY_EXISTS;
}
$this->_insert_data($block,$data,$prev,$idxoff,$datoff);
return DB_SUCCESS;
}
/**删除操作
* @since 2018/12/14
* @author weihua.zhang
* @param $key
* @return string
*/
public function delete($key){
$offset = $this->_offset($key);
$head = unpack('L',$this->_fileRead($this->idx_fp,$offset,4));
$curr = $head = $head[1];
$prev = 0;
$next = 0;
$datoff = 0;
$datlen = 0;
//获取索引记录
while ($curr){
$block = $this->_fileRead($this->idx_fp,$curr,DB_INDEX_SIZE);
$next = unpack('L',substr($block,0,4))[1];
$cpkey = substr($block,4,DB_KEY_SIZE);
if(!strncmp($key,$cpkey,strlen($key))){
$datoff = unpack('L',substr($block,DB_KEY_SIZE+4,4))[1];
$datlen = unpack('L',substr($block,DB_KEY_SIZE+8,4))[1];
$found = true;
break;
}
$prev = $curr;
$curr = $next;
}
if(!$found){
return DB_FAILURE;
}
if($prev==0){
//如果是头节点,修改索引为下一个hash链表的借点
$this->_fileWrite($this->idx_fp,$offset,pack('L',$next),4);
}else{
//如果不是头节点,交换借点
$this->_fileWrite($this->idx_fp,$prev,pack('L',$next),4);
}
return DB_SUCCESS;
}
/**关闭句柄
* @since 2018/12/14
* @author weihua.zhang
*/
public function close(){
if(!$this->closed){
fclose($this->idx_fp);
fclose($this->dat_fp);
$this->closed = true;
}
}
}