加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 站长学院 > PHP教程 > 正文

PHP的实现一个高效的数据库

发布时间:2020-12-13 05:16:00 所属栏目:PHP教程 来源:网络整理
导读:用文件的方式读写,一个文件是索引文件,另一个文件是真实的数据文件。 索引文件分为2部分,第一部分是所有的指针,记录第二部分的位置;第二部分是索引记录。所有的索引指针:是记录所有相同Hash值的key的指针,它是一个链表结构,记录在数据文件的位置和同

用文件的方式读写,一个文件是索引文件,另一个文件是真实的数据文件。


索引文件分为2部分,第一部分是所有的指针,记录第二部分的位置;第二部分是索引记录。所有的索引指针:是记录所有相同Hash值的key的指针,它是一个链表结构,记录在数据文件的位置和同key的下一个值。


索引记录中:每条记录有四部分,第一部分4个字节,是下一条索引的偏移量;第二部分是该记录的key,128字节;第三部分是数据偏移量,4个字节;第四部分是数据记录长度,4个字节。


我们设定文件的存储上限为262144个。

查找流程如下:

1、根据key算出hash值,获取该hash值的链表在索引文件的第一部分(所有指针区)的位置。


2、根据步骤一的位置,获取值,时间复杂度O(1);


2、根据步骤一中的值,找到索引文件中第二部分(索引记录)的位置,也就是和key相同hash值的所有指针的链表。顺着链表查找该key,获取该key在链表中存放的数据,数据只包含该key在索引文件中的位置,时间复杂度为O(n);


3、根据步骤二所获取的key在索引文件位置,得到索引文件中存放该key的信息。信息包含在真实数据文件中存放真实数据的位置。


4、根据步骤三所获取的位置,在真实数据文件中获取数据,并返回给应用程序。

测试结果:插入10000条耗时:793ms。查找10000条耗时:149ms。虽然这效率只有Redis的十分之一。。。但是请不要在意这些细节。。。

代码做了注释,上述文字有些乱。代码只实现三个方法,一个插入(如果存在则跳过),一个是查找,一个是删除。

//Hash表中的元素指针个数,每个指针都是int,存储hash链表的文件偏移量

define('DB_BUCKET_SIZE',262144);

//每条记录的key的长度

define('DB_KEY_SIZE',128);

//一条索引记录的长度

define('DB_INDEX_SIZE',DB_KEY_SIZE + 12);

//成功-返回码

define('DB_SUCCESS',1);

//失败-返回码

define('DB_FAILURE',-1);

//key重复-返回码

define('DB_KEY_EXISTS',-2);

class DB{

private $idx_fp;

private $dat_fp;

private $closed;

/**

* Description: 打开数据库

* @param $pathName 数据文件的存放路径

* @return mixed

*/

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){

//把0x00000000转换成无符号长整型的二进制

$elem = pack('L',0x00000000);

for($i=0; $i< DB_BUCKET_SIZE; $i++){

fwrite($this->idx_fp,$elem,4);

}

}

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

if(!$this->dat_fp){

return DB_FAILURE;

}

return DB_SUCCESS;

}

/**

* Description: Times33 Hash算法

* @param $key

* @return int

*/

private function times33Hash($key){

$len = 8;

$key = substr(md5($key),$len);

$hash = 0;

for($i=0; $i<$len; $i++){

$hash += 33 * $hash + ord($key[$i]);

}

//0x7FFFFFFF:一个十六进制的数是4bit,8个就是32位,就是4字节,和一个int一样大。而F是1111,7是0111,那么这个十六进制的数就是头为0,其余为1的,首位是符号位,也就是说7fffffff是最大的整数。

//& 0x7fffffff 可以保证返回的数是正整数

return $hash & 0x7FFFFFFF;

}

/**

* Description: 插入记录

* @param $key

* @param $value

*/

public function add($key,$value){

$offset = ($this->times33Hash($key) % DB_BUCKET_SIZE) * 4;

$idxoff = fstat($this->idx_fp);

$idxoff = intval($idxoff['size']);

$datoff = fstat($this->dat_fp);

$datoff = intval($datoff['size']);

$keylen = strlen($key);

$vallen = strlen($value);

if($keylen > DB_KEY_SIZE){

return DB_FAILURE;

}

//0表示这是最后一个记录,该链再无其他记录。

$block = pack('L',0x00000000);

//键值

$block .= $key;

//如果键值的长度没有达到最大长度,则用0填充

$space = DB_KEY_SIZE - $keylen;

for($i=0; $i<$space; $i++){

$block .= pack('C',0x00);

}

//数据所在文件的偏移量

$block .= pack('L',$datoff);

//数据记录的长度

$block .= pack('L',$vallen);

//尽管SEEK_SET是默认值,但是显式声明了就不怕以后官方会改变了-.-

fseek($this->idx_fp,$offset,SEEK_SET);

//检测该key所对应的hash值是否存在了

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

$pos = $pos[1];

//如果key不存在

if($pos == 0){

fseek($this->idx_fp,SEEK_SET);

fwrite($this->idx_fp,pack('L',$idxoff),4);

fseek($this->idx_fp,SEEK_END);

fwrite($this->idx_fp,$block,DB_INDEX_SIZE);

fseek($this->dat_fp,SEEK_END);

fwrite($this->dat_fp,$value,$vallen);

return DB_SUCCESS;

}

//如果key存在

$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);

//$cpkey==$key时返回0,小于返回负数,大于返回正数

if(!strncmp($cpkey,$key,$keylen)){

$dataoff = unpack('L',substr($tmp_block,DB_KEY_SIZE + 4,4));

$dataoff = $dataoff[1];

$datalen = unpack('L',DB_KEY_SIZE + 8,4));

$datalen = $datalen[1];

$found = true;

break;

}

$prev = $pos;

$pos = @unpack('L',4));

$pos = $pos[1];

}

if($found){

return DB_KEY_EXISTS;

}

fseek($this->idx_fp,$prev,$vallen);

return DB_SUCCESS;

}

/**

* Description: 查询一条记录

* @param $key

*/

public function get($key){

//计算偏移量,key的hash值对索引文件的大小求模,再乘4。因为每个链表指针大小为4

$offset = ($this->times33Hash($key) % DB_BUCKET_SIZE) * 4;

//SEEK_SET是默认的

fseek($this->idx_fp,SEEK_SET);

$pos = unpack('L',4));

$pos = $pos[1];

$found = false;

while($pos){

fseek($this->idx_fp,SEEK_SET);

$block = fread($this->idx_fp,DB_INDEX_SIZE);

$cpkey = substr($block,DB_KEY_SIZE);

if(!strncmp($key,$cpkey,strlen($key))){

$dataoff = unpack('L',substr($block,4));

$datalen = $datalen[1];

$found = true;

break;

}

$pos = unpack('L',4));

$pos = $pos[1];

}

if(!$found){

return null;

}

fseek($this->dat_fp,$dataoff,SEEK_SET);

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

return $data;

}

/**

* Description: 删除

* @param $key

*/

public function delete($key){

$offset = ($this->times33Hash($key) % DB_BUCKET_SIZE) * 4;

fseek($this->idx_fp,SEEK_SET);

$head = unpack('L',4));

$head = $head[1];

$curr = $head;

$prev = 0;

$found = false;

while($curr){

fseek($this->idx_fp,$curr,DB_INDEX_SIZE);

$next = unpack('L',4));

$next = $next[1];

$cpkey = substr($block,strlen($key))){

$found = true;

break;

}

$prev = $curr;

$curr = $next;

}

if(!$found){

return DB_FAILURE;

}

//删除索引文件。

if($prev == 0){

fseek($this->idx_fp,$next),4);

}else{

fseek($this->idx_fp,4);

}

return DB_SUCCESS;

}

public function close(){

if(!$this->closed){

fclose($this->idx_fp);

fclose($this->dat_fp);

$this->closed = true;

}

}

}

?>

测试,测试添加一万条和查找一万条:

//先include上面的类。。如果在同一个文件中就不用了。

//测试

$db = new DB();

$db->open('/var/www/data/');

$startTime = microtime(true);

//插入测试...插入10000条:成功,耗时: 793.48206520081ms

//for($i=0; $i<10000; $i++){

// $db->add('key'.$i,'value'.$i);

//}

//查找测试...查找10000条:成功,耗时: 149.08313751221ms

for($i=0; $i<10000; $i++){

$db->get('key'.$i);

}

$endTime = microtime(true);

echo '成功,耗时: ' . (($endTime - $startTime)*1000) . 'ms';

$db->close();

?>

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读