无数据库php文章,PHP文本数据库类及其基础上的文章系统

三 文本数据库的精细设计与算法

上面简单地分析了文本数据库的实现方法。下面根据我的实例来介绍文本数据库的精细设计与具体算法。

首先,我们简地介绍上面提到的三个文件的数据结构。

对于数据文件dbf,记录是不定长而且是无序存放(为什么是无序,下面会有介绍)的,不需要太多的说明。

索引文件indx以定长并且是顺序的方式存放记录,也就说每个记录的长度一致为RcdLength,而且第N个记录存放在文件中的N*RcdLength处.每个记录的格式如下:

记录编号(ID)|偏移位置(LOC)|数据长度(Length)

其中,ID是数据库每个出现过的记录的唯一编号,不同的记录以此来唯一标识自己,相当于数据库的健值。ID编号由系统递增分配;LOC记录实际数据在数据文件dbf中存入的首位置,length则表示此数据在dbf中所占用的空间。值得提出的是,ID编号N的记录并不一定是数据库中的第N个记录,ID用以标识记录的身分,而第N个记录的N则是指记录在数据库中的物理(对应于indx)与逻辑位置(对应于dbf);还有,length记录的是该数据所占用的空间大小而并一定等同于数据的实际长度(为什么?).

ID,LOC,Length均为无符号整数,所以indx中每个记录的占用4*3=12个字节的空间,这很利于记录的顺序存放和删除。

我们还约定,indx文件中第一个记录并不是用于记录数据的定位信息,而用于记录整个数据库的相关信息,其中ID用于存入已被分

配的最大ID编号,LOC用于记录数据库中的实际记录个数,length用于记录数据文件dbf的末端位置。可以看出,ID值是递增的,在没有

进行任何删除操作之前,ID值与LOC永远相等,如删除操作,LOC必然小于ID值。另外,length值在很多情况下并不与数据文件bdf的大

小相等,如我们在dbf的末端为一个新记录分配了100个字节的空间而写入的实际数据只有90,那个length值要比dbf的大小大10.

left文件的记录结构与组织方式和indx类似却更为简单,它只有两个字段:

偏移位置(LOC)|空间长度(Length)

其中,LOC表示闲置空间在dbf中的起始位置,Length表示此闲置空间的长度。同样,left文件中的第一个记录不用来记录闲置空间

信息,而是记录自己的相关信息,第一个记录的LOC值或Length用于记录left文件中的闲置空间条数也就是本文件和实际记录个数。

数据结构已经陈述完备,下面我们根据代码来谈谈实际算法:

class TxtDB //文本数据库类

{

var $name=''; //文本数据库名

var $path=''; //数据库路径

var $isError; //出错代码

var $dbh; //数据文件dbf指针

var $indxh; //索引文件indx指针

var $lfth; //闲置空间文件left指针

var $lckh;

var $rcdCnt=0;//数据库的记录个数

var $maxID=0; //数据库已分配的最大ID编号

var $leftCnt=0;//闲置空间个数

var $DBend=0; //DBF文件末端指针

/*初始化函数*/

function TxtDB($name,$path='dbm')

{

$this->name=$name;

$this->path=$path.'/'.$name;

$this->isError=0;

$path=$this->path;

if ($name!='')

{

@mkdir($this->path,0777);//创建数据库目录

//创建或打开数据库文件

if (!file_exists($path.'/'.$name.'.tdb')) $this->dbh=fopen($this->path.'/'.$name.'.tdb','w+');

else $this->dbh=fopen($path.'/'.$name.'.tdb','r+');

if (!file_exists($path.'/'.$name.'.indx')) $this->indxh=fopen($this->path.'/'.$name.'.indx','w+');

else $this->indxh=fopen($path.'/'.$name.'.indx','r+');

if (!file_exists($path.'/'.$name.'.lft')) $this->lfth=fopen($this->path.'/'.$name.'.lft','w+');

else $this->lfth=fopen($this->path.'/'.$name.'.lft','r+');

//为保证数据库操作的原子性,对数据进行加锁保护

$this->lckh=fopen($this->path.'/'.$name.'.lck','w');

flock($this->lckh,2);

fwrite($this->lckh,'lck');//阻塞其它并发进程对数据库的并行操作

//获取数据库的相关信息

$rcd=$this->getRcd(0);//从indx文件中读取首个记录

$this->rcdCnt=$rcd[id];

$this->maxID=$rcd[loc];

$this->DBend=$rcd[len];

$rcd=$this->getLeft(0);//从left文件中读取首个记录

$this->leftCnt=$rcd[loc];

}

else $this->isError=1;

}

/*设置indx的定位信息*/

function setRcd($rid,$id,$loc,$len)

{

fseek($this->indxh,$rid*12);

//移动文件指针至记录处

$str=pack('III',$id,$loc,$len);

//将整数压缩到字符串中

fwrite($this->indxh,$str,12);

//将定定位信息 ID|LOC|Len 写入indx的第rid个记录

}

/*获取定位信息*/

function getRcd($rid)

{

fseek($this->indxh,$rid*12);

//移至记录处

$str=fread($this->indxh,12);

//记取记录

$rcd=array();

//将压缩的字符串还原为整数

$rcd[id]=str2int($str);

$rcd[loc]=str2int(substr($str,4,4));

$rcd[len]=str2int(substr($str,8,4));

return $rcd;//返回第rid个记录的定位信息

}

/*设置闲置空间记录*/

function setLeft($lid,$loc,$len)

{

fseek($this->lfth,$lid*8);

$str=pack('II',$loc,$len);

fwrite($this->lfth,$str,8);

}

/*记取第lid个闲置空间信息*/

function getLeft($lid)

{

fseek($this->lfth,$lid*8);

$str=fread($this->lfth,8);

$rcd[loc]=str2int($str);

$rcd[len]=str2int(substr($str,4,4));

return $rcd;

}

/*结束数据库操作并释放数据加锁*/

function close()

{

@fclose($this->dbh);

@fclose($this->indxh);

@fclose($this->lfth);

@fclose($this->lckh);

}

/*从闲置空间中寻找一个大小最少为len的空间

使用最佳适用法 */

function seekSpace($len)

{

$res=array('loc'=>0,'len'=>0);

if ($this->leftCnt<1) return $res;

//没有闲置空间

$find=0;

$min=1000000;

//遍历所有闲置空间信息

for ($i=$this->leftCnt;$i>0;$i--)

{

$res=$this->getLeft($i);

//找寻到大小刚好合适的空间

if ($res[len]==$len) {$find=$i;break;}

//找到可用的闲置空间

else if($res[len]>$len)

{

//力图找到一个最合适的空间

if ($res[len]-$len {

$min=$res[len]-$len;

$find=$i;

}

}

}

if ($find)

{

//找到了合适的闲置空间

//读取闲置空间信息

$res=$this->getLeft($find);

//用left文件删除此闲置空间的记录信息

fseek($this->lfth,($find+1)*8);

$str=fread($this->lfth,($this->leftCnt-$find)*8);

fseek($this->lfth,$find*8);

fwrite($this->lfth,$str);

//更新闲置空间记录数

$this->leftCnt--;

$this->setLeft(0,$this->leftCnt,0);

//返回获得的闲置空间结果

return $res;

}

else //失败返回

{

$res[len]=0;

return $res;

}

}

/*插入记录至数据库content为记录内容,len限定记录的长度*/

function insert($content,$len=0)

{

$res=array('loc'=>0);

//记录长度没有指定则根据数据实际长度指定

if (!$len) $len=strlen($content);

//试图从闲置空间中获取一块可用的空间

if ($this->leftCnt) $res=$this->seekSpace($len);

if (!$res[len])

{

//没有找到可用的闲置空间则从数据文件末端分配空间

$res[loc]=$this->DBend;

$res[len]=$len;

}

//更新数据文件末端指针

if ($res[loc]+$res[len]>$this->DBend) $this->DBend=$res[loc]+$res[len];

$this->maxID++;//更新最大ID编号

$this->rcdCnt++;//更新数据库记录个数

//将更新永久写入数据库

$this->setRcd(0,$this->rcdCnt,$this->maxID,$this->DBend);

$this->setRcd($this->rcdCnt,$this->maxID,$res[loc],$res[len]);

//将实际数据写入从dbf分配的空间处

fseek($this->dbh,$res[loc]);

fwrite($this->dbh,$content,$len);

//成功返回新记录的编号

return $this->maxID;

}

/*寻找编号为ID的记录在数据库中的位置编号N*/

/*因为ID编号在indx中升序排列可使用二分查找大大提高查询速度*/

function findByID($id)

{

//数据库中没有记录或者编号超过当前最大ID编号

if ($id<1 or $id>$this->maxID or $this->rcdCnt<1) return 0;

$left=1;

$right=$this->rcdCnt;

while($left {

$mid=(int)(($left+$right)/2);

if ($mid==$left or $mid==$right) break;

$rcd=$this->getRcd($mid);

if ($rcd[id]==$id) return $mid;

else if($id else $left=$mid;

}

$rcd=$this->getRcd($left);

if ($rcd[id]==$id) return $left;

$rcd=$this->getRcd($right);

if ($rcd[id]==$id) return $right;

//查找成功返回位置编号N

return 0;//失败返回0

}

/*从数据库中删除编号为ID的记录*/

function delete($id)

{

//查找此记录在数据库中的位置编号

$rid=$this->findByID($id);

if (!$rid) return;//不存在ID号为id的记录

$res=$this->getRcd($rid);//获取此记录的定位信息

//从索引文件中删除此记录的定位信息

fseek($this->indxh,($rid+1)*12);

$str=fread($this->indxh,($this->rcdCnt-$i)*12);

fseek($this->indxh,$rid*12);

fwrite($this->indxh,$str);

//更新数据库记录个数并永久写入数据库

$this->rcdCnt--;

$this->setRcd(0,$this->rcdCnt,$this->maxID,$this->DBend);

//将此记录在dbf所占用的空间登记到闲置空间队列

$this->leftCnt++;

$this->setLeft(0,$this->leftCnt,0);

$this->setLeft($this->leftCnt,$res[loc],$res[len]);

}

/*更新ID编号为id的记录内容*/

/*len用于重新限定记录的内容*/

function update($id,$newcontent,$len=0)

{

//将ID编号转化为位置编号N

$rid=$this->findByID($id);

if (!$rid) return;//不存的ID编号

if (!$len) $len=strlen($newcontent);

//获取此记录定位信息

$rcd=$this->getRcd($rid);

//更新的内容长度超出记录原来分配的空间

if ($rcd[len] {

//放弃原空间并将此空间录入闲置空间队列

$this->leftCnt++;

$this->setLeft(0,$this->leftCnt,0);

$this->setLeft($this->leftCnt,$rcd[loc],$rcd[len]);

//在dbf末端为此记录重新分配空间

$rcd[loc]=$this->DBend;

$rcd[len]=$len;

$this->DBend+=$len;

//更新数据库信息

$this->setRcd(0,$this->rcdCnt,$this->maxID,$this->DBend);

$this->setRcd($rid,$rcd[id],$rcd[loc],$rcd[len]);

}

//写入新数据

fseek($this->dbh,$rcd[loc]);

fwrite($this->dbh,$newcontent,$len);

}

/*根据位置编号获取记录内容*/

function selectByRid($rid)

{

//数据以ID编号与实际数据content二元组返回

$res=array('id'=>0,'content'=>'');

//错误的位置编号

if ($rid<1 or $rid>$this->rcdCnt) return $res;

//读取定位信息

else $rcd=$this->getRcd($rid);

$res[id]=$rcd[id];

$res[len]=$rcd[len];

//根据定位信息从dbf中读取实际数据

fseek($this->dbh,$rcd[loc]);

$res[content]=fread($this->dbh,$rcd[len]);

return $res;

}

/*根据ID编号获取记录内容*/

function select($id)

{

//将ID编号转换成位置编号再调用上面的函数

return $this->selectByRid($this->findByID($id));

}

/*数据库备份*/

function backup()

{

copy($this->path.'/'.$this->name.'.tdb',$this->path.'/'.$this->name.'.tdb.bck');

copy($this->path.'/'.$this->name.'.indx',$this->path.'/'.$this->name.'.indx.bck');

copy($this->path.'/'.$this->name.'.lft',$this->path.'/'.$this->name.'.lft.bck');

}

/*从备份中恢复*/

function recover()

{

copy($this->path.'/'.$this->name.'.tdb.bck',$this->path.'/'.$this->name.'.tdb');

copy($this->path.'/'.$this->name.'.indx.bck',$this->path.'/'.$this->name.'.indx');

copy($this->path.'/'.$this->name.'.lft.bck',$this->path.'/'.$this->name.'.lft');

}

/*清除数据库*/

function drop()

{

@unlink($this->path.'/'.$this->name.'.tdb');

@unlink($this->path.'/'.$this->name.'.indx');

@unlink($this->path.'/'.$this->name.'.lft');

}

/*清空数据库记录*/

function reset()

{

setRcd(0,0,0);

setLeft(0,0);

}

}

?>

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值