zSet 简单轻量的文件数组数据库

in 日常 with 0 comment

然后呢,凭借着“能白嫖就白嫖”的精神,我将收集来的博客图片挨个上传到了图床,收获了100个链接

链接列表

但是,如何随机调用图片呢?
简单的3个办法

  1. 放在一个文本文件里,通过file()解析
  2. sqlite3 SELECT
  3. mysql SELECT + RANDOM

但是我就是不喜欢,花了2小时,终于完成了强大的zSet
zSet相当于一个文件等宽数组,文件是这样的:

文件内容

而且API非常简洁,就那么几个:

函数列表

那么,我就简单地小时牛刀,创建了两个PHP文件,使用file()函数或使用zSet

<?php
    // 文件1:使用zSet
    include_once 'create.php';
    $set = new SetFile('db.zset',true);
    $total = $set->total;
    header('Location:'.$set->get(rand(0,$total)));
    // 文件2:简洁的file大法
    $data = file('img.list');
    header('Location:'+$data[rand(0,count($data))]);

结果

izS  # curl localhost/blog/way2.php -v
*   Trying 127.0.0.1:80...
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /blog/way2.php HTTP/1.1
> Host: localhost
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 302 Found
< Server: nginx
< Date: Sat, 02 Sep 2023 06:47:19 GMT
< Content-Type: text/html; charset=UTF-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< X-Powered-By: PHP/8.2.7
< Location: https://picss.sunbangyan.cn/2023/09/02/j37c5j.jpg
< Access-Control-Allow-Origin: *
< X-TimeUsed: 0.001
<
* Connection #0 to host localhost left intact

izS  # curl localhost/blog/random.php -v 
*   Trying 127.0.0.1:80...
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /blog/random.php HTTP/1.1
> Host: localhost
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 302 Found
< Server: nginx
< Date: Sat, 02 Sep 2023 06:47:46 GMT
< Content-Type: text/html; charset=UTF-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< X-Powered-By: PHP/8.2.7
< Location: https://picdm.sunbangyan.cn/2023/09/02/j3xlss.jpg
< Access-Control-Allow-Origin: *
< X-TimeUsed: 0.001
<
* Connection #0 to host localhost left intact

不明显,那么我们扩充容量继续

<?php
    include_once 'create.php';
    $set = new SetFile('db.zset',false);
    $file = fopen('img.list','w');
    for ($i = 0; $i < 20000; $i++) {
         $val = md5(rand($i,$i+10000000));
         $set->push($val);
         fwrite($file,$val);
    }
    $set->save();

超大容量!

2万条数据并发,我们看看结果:

izS  # curl localhost/blog/random.php -v  
*   Trying 127.0.0.1:80...
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /blog/random.php HTTP/1.1
> Host: localhost
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 302 Found
< Server: nginx
< Date: Sat, 02 Sep 2023 06:57:49 GMT
< Content-Type: text/html; charset=UTF-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< X-Powered-By: PHP/8.2.7
< Location: b431a69da5657ad54c2dda310abdf2d4
< Access-Control-Allow-Origin: *
< X-TimeUsed: 0.001
<
* Connection #0 to host localhost left intact

izS  # curl localhost/blog/way2.php -v
*   Trying 127.0.0.1:80...
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /blog/way2.php HTTP/1.1
> Host: localhost
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 302 Found
< Server: nginx
< Date: Sat, 02 Sep 2023 06:57:42 GMT
< Content-Type: text/html; charset=UTF-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< X-Powered-By: PHP/8.2.7
< Location: c861927528520dc2a3fafabfccbe8338
< Access-Control-Allow-Origin: *
< X-TimeUsed: 0.003
<
* Connection #0 to host localhost left intact

非常明显,如果数据量很大,zSet非常有优势!始终是1ms的高速

于是乎,zSet源码在此,需要的自取!

<?php
    class SetFile{
    private $file,$temp,$path,$length=0;
    protected $append = [],$isnf = false,
        $size = 0;
    public $total = 0;
    const HEAD = 'ZSET ',
        SEPARATE = "\033",
        NUMLEN = 5;
    
    function __construct(string $file,bool $readonly=false){
        if(!file_exists($file) and !touch($file)) throw new Exception('IO Error.');
        $this->path = $file;
        $this->file = fopen($file,'r');
        $this->length = filesize($file);
        if(!$readonly) $this->temp = fopen($file.'.lock','w');
        if(filesize($file) > (strlen(self::HEAD) + self::NUMLEN))
            $this->_init();
        else $this->isnf = true;
    }

    private function _init(){
        fseek($this->file,0);
        if(self::HEAD != fread($this->file,strlen(self::HEAD)))
            throw new Exception('Not a vaild setFile');
        fseek($this->file,strlen(self::HEAD));
        $this->size = (int)fread($this->file,self::NUMLEN);    // 单列大小
        if($this->size <= 0) throw new Exception('illegal meta.Wrong zset file!');
        $this->total = (filesize($this->path) - strlen(self::HEAD) - self::NUMLEN) / $this->size;    // 总数
    }
    
    function push(string $data){
        if($this->size <= strlen($data))
            $this->size = strlen($data) + 1;
        $this->append[] = $data;
        return $this;
    }
    
    function get(int $num){
        if($this->size <= 0 || $this->isnf) return null;
        $position = strlen(self::HEAD) + self::NUMLEN + $num * $this->size;
        if($position >= $this->length) return null;
        fseek($this->file,$position);
        return $this->_filter(fread($this->file,$this->size));
    }
    
    private function _filter(string $data){
        $end = strpos($data,self::SEPARATE,1);
        if($end === false) throw new Exception('illegal data found.bad set.');
        return substr($data,0,$end);
    }
    
    function getAll(){
        if($this->size <= 0 || $this->isnf) return [];
        $tmp = [];
        fseek($this->file,strlen(self::HEAD) + self::NUMLEN);
        while(!feof($this->file))
            $tmp[] = $this->_filter(fread($this->file,$this->size));
        return $tmp;
    }
    
    function save(){
        if(!$this->temp) throw new Exception('This file is Readonly.');
        fseek($this->temp,0);
        fwrite($this->temp,self::HEAD . str_pad($this->size,self::NUMLEN,'0',STR_PAD_LEFT));
        $dataset = array_merge($this->getAll(),$this->append);
        foreach ($dataset as $data){
            if($this->size <= strlen($data))
                throw new Exception('Broken file.illegal average length.');
            // 缺位补齐
            fwrite($this->temp,str_pad($data,$this->size,self::SEPARATE,STR_PAD_RIGHT));
            // 完成
            fdatasync($this->temp);
        }
    }

    function __destruct(){
        if($this->temp)
            rename($this->path . '.lock',$this->path) and fclose($this->temp);
        fclose($this->file);
    }
}
Responses