php实现简单key-value hash数据库


类库文件:

<?php



//insert 函数插入标记
define('DB_INSERT',1);
define('DB_STROE',2); //不管有没有key 直接存储


//hash 索引大小 262144
define('DB_BUCKETS_SIZE',262144);
//key 的长度
define('DB_KEY_SIZE',128);
//一条索引记录的长度
define('DB_INDEX_SIZE',DB_KEY_SIZE+12);


define('DB_KEY_EXISTS','key is exists!');
define('DB_KEY_SIZE_IS_BIG','key size is too big');
define('DB_FAILURE',-1);
define('DB_SUCCESS', 0);


/**
 * 自己写的key-value数据库类,可以存储数据,提取数据,删除数据
 * 
 * 不足之处:
 * key 的大小不能超过128
 * 删除对应key值时数据时库并没有真正的从文件删除对应的数据
 * 随着时间增长文件会越来越大
 * @author prg
 * @date 2014-2-25
 */
class MYDB
{
/**
* 索引文件句柄
* @var source
*/
private $idx_fp;

/**
* 数据文件句柄
* @var source
*/
private $dat_fp;

/**
*数据库是否已经关闭 
* @var bool
*/
private $closed;

/**
*文件名字
*/
private $pathname;



/**
* 打开数据库
* @param string $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); //无符号二进制进行打包
for($i=0;$i<DB_BUCKETS_SIZE;$i++)
{
fwrite($this->idx_fp, $elem,4);
}
}

$this->dat_fp = fopen($dat_path, $mode);

if(!$this->dat_fp)
{
return DB_FAILURE;
}

$this->pathname=$pathname;
$this->closed = false;

return DB_SUCCESS;
}

/**
* Time33 hash 函数
* @param string $string
* @return boolean
*/
private function _hash($string)
{
$string = substr(md5($string), 0,8);
$hash   = 0;
for($i=0;$i<8;$i++)
{
$hash+=($hash<<5)+ord($string{$i});
}
return $hash & 0x7FFFFFFF;
}

/**
* 这里使用的是尾插法
* @param string $key
* @param string $data
* @return string
*/
public function insert($key,$data,$mode=DB_INSERT)
{
$offset = ($this->_hash($key)%DB_BUCKETS_SIZE)*4;

//索引文件下一个空闲区域偏移量
$idxoff = fstat($this->idx_fp);
$idxoff = intval($idxoff['size']);

//数据文件下一个空闲区域偏移量
$datoff = fstat($this->dat_fp);
$datoff = intval($datoff['size']);

$keylen = strlen($key);
if($keylen>DB_KEY_SIZE)
{
return DB_KEY_SIZE_IS_BIG;
}

//开始构造一条索引记录()链表指针4个字节,键128个字节,数据偏移量4个字节,数据长度4个字节
$block = pack('L',0x00000000); //将链表指针,指向吓一跳索引记录的指针,为空
$block.= $key;
$space = DB_KEY_SIZE-$keylen; //这里key长度不够用0填充
for($i=0;$i<$space;$i++)
{
$block.=pack('C',0x00);
}
$block.=pack('L',$datoff);
$block.=pack('L',strlen($data));
fseek($this->idx_fp, $offset,SEEK_SET); //定位到对应的hash上 


$pos = unpack('L', fread($this->idx_fp, 4));
$pos = $pos[1];

if($pos==0) //如果该hash链上还没有元素
{
fseek($this->idx_fp,$offset,SEEK_SET);
//var_dump($this->idx_fp);
fwrite($this->idx_fp, pack('L',$idxoff),4); //索引空闲偏移量

fseek($this->idx_fp, 0,SEEK_END); //到索引文件结尾
fwrite($this->idx_fp,$block,DB_INDEX_SIZE);//写入一个索引块

fseek($this->dat_fp,0,SEEK_END);
fwrite($this->dat_fp, $data,strlen($data));

return DB_SUCCESS;
}

$found = false;

while($pos)
{
fseek($this->idx_fp,$pos,SEEK_SET);
$tmp_block  = fread($this->idx_fp,DB_INDEX_SIZE);
$cpkey      = substr($tmp_block,4,DB_KEY_SIZE);


if(!strncmp($cpkey, $key, strlen($key))) //比较前strlen($key)是否相似,已经存在key
{
//$dataoff = unpack('L',substr($tmp_block,DB_KEY_SIZE+4,4));
//$dataoff = $dataoff[1]; //数据偏移量
//数据长度
//$datalen = unpack('L',substr($tmp_block, DB_KEY_SIZE+8,4));
//$datalen = $datalen[1];
$found   = true;
break;
}

//这里开始遍历链表
$prev = $pos;
$pos  = unpack('L', substr($tmp_block, 0,4));
$pos  = $pos[1];
}

if($found&&$mode==DB_INSERT)
{
//这里key存在 ,并且只能是插入
return DB_KEY_EXISTS;
}
else if($found&&$mode!=DB_INSERT)
{
//这里替换原来的 实际上就是修改数据偏移量和数据长度
fseek($this->idx_fp, $pos+4+DB_KEY_SIZE,SEEK_SET); //定位到数据偏移量位置
fwrite($this->idx_fp,pack('L',$datoff),4);

//修改数据长度值
fseek($this->idx_fp, $pos+8+DB_KEY_SIZE,SEEK_SET); //定位到数据长度
fwrite($this->idx_fp,pack('L',strlen($data)),4);

//记录数据
fseek($this->dat_fp, 0,SEEK_END);
fwrite($this->dat_fp, $data,strlen($data));

return DB_SUCCESS;
}

fseek($this->idx_fp,$prev,SEEK_SET);//定位到链表最后一条索引记录,此时这条索引记录里的链表指针必定是空
fwrite($this->idx_fp,pack('L',$idxoff),4); //修改这个指针使它指向下一条索引记录

//记录索引块
fseek($this->idx_fp,0,SEEK_END);
fwrite($this->idx_fp, $block,DB_INDEX_SIZE);

//记录数据
fseek($this->dat_fp, 0,SEEK_END);
fwrite($this->dat_fp, $data,strlen($data));

return DB_SUCCESS;
}


public function fetch($key)
{
$offset = ($this->_hash($key)%DB_BUCKETS_SIZE)*4;
fseek($this->idx_fp, $offset,SEEK_SET);

$pos   = unpack('L',fread($this->idx_fp, 4));
$pos   = $pos[1];



$found =false;
while($pos)
{
fseek($this->idx_fp,$pos,SEEK_SET);
$block = fread($this->idx_fp,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));
$pos = $pos[1];
}
if(!$found) //这里没有找到对应的数据
{
return NULL;
}

fseek($this->dat_fp, $dataoff,SEEK_SET);
$data = fread($this->dat_fp, $datalen);

return $data;


/**
* 这里只是改变了链表的结构,并没有真正的吧数据重文件中删除,如果要删除的话开销回非常大,要改变整个hash结构
* @param string $key
* @return string
*/
function delete($key)
{
$offset = ($this->_hash($key)%DB_BUCKETS_SIZE)*4;
fseek($this->idx_fp,$offset,SEEK_SET);

$head = unpack('L', fread($this->idx_fp,4));
$head = $head[1];
$curr = $head;
$prev = 0;
$found= false;
//开始遍历链表
while($curr)
{
fseek($this->idx_fp,$curr,SEEK_SET);
$block = fread($this->idx_fp,DB_INDEX_SIZE);

//指向下个索引块的指针
$next  = unpack('L', substr($block,0,4));
$next  = $next[1];


$cpkey = substr($block, 4,DB_KEY_SIZE);
if(!strncmp($key, $cpkey, strlen($key)))
{
//这里找到对应的key
//数据偏移量
$datoff = unpack('L',substr($block, DB_KEY_SIZE+4,4));
$datoff = $datoff[1];

$datlen = unpack('L',substr($block, DB_KEY_SIZE+8,4));
$datlen = $datlen[1];

$found  = true;
break;
}

$prev = $curr;
$curr = $next;
}

if(!$found)
{
return DB_FAILURE;
}

if($prev==0)
{
//如果是头结点
fseek($this->idx_fp, $offset,SEEK_SET);
fwrite($this->idx_fp, pack('L',$next),4);
}
else
{
fseek($this->idx_fp,$prev,SEEK_SET);
fwrite($this->idx_fp,pack('L',$next),4);
}
return DB_SUCCESS;
}

/**
* 关闭数据库
*/
public function close()
{
if(!$this->closed)
{
fclose($this->idx_fp);
fclose($this->dat_fp);
$this->closed = true;
}
}

/**
* 清空所有数据
* @return string
*/
public function flush_all()
{
if(!$this->closed)
{
fclose($this->idx_fp);
fclose($this->dat_fp);
}
$mode  = 'w+b';

$this->idx_fp = fopen($this->pathname.'.idx', $mode);
$this->dat_fp = fopen($this->pathname.'.dat', $mode);

if(!$this->idx_fp)
{
return DB_FAILURE;
}

$elem = pack('L',0x00000000); //无符号二进制进行打包
for($i=0;$i<DB_BUCKETS_SIZE;$i++)
{
fwrite($this->idx_fp, $elem,4);
}
return DB_SUCCESS;
}
}

?>

调用文件:

<?php
header("Content-type:text/html;charset=utf-8");
require_once 'mydb.class.php';
$mydb = new MYDB();
$mydb->open('test');
$mydb->insert('key', 'value2',DB_STROE);
$mydb->fetch('key');
?>


以上大部分代码来自:PHP核心技术与最佳实践


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值