PHP实现文本快速查找:二分查找法

编程之家收集整理的这篇文章主要介绍了PHP实现文本快速查找:二分查找法编程之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

起因

先说说事情的起因,最近在分析数据时经常遇到一种场景,代码需要频繁的读某一张数据库的表,比如根据地区ID获取地区名称、根据网站分类ID获取分类名称、根据关键词ID获取关键词等。虽然以上需求都可以在原始建表时,通过冗余数据来解决。但仍有部分业务存的只是关联表的ID,数据分析时需要频繁的查表。

所读的表存在共同的特点

  • 数据几乎不会变更

  • 数据量适中,从一万到100多万,如果全加载到内存也不太合适。

纠结的地方

在做数据分析时,需要十分频繁的读这些表,每秒有可能需要读上万次。其实内部的数据库集群完全可以胜任,但会对线上业务稍有影响。(你懂得,小公司不可能为离线分析做一套完整的数据存储服务。大部分数据分析还要借助线上的数据集群)

优化方案的思考

有没有一种方式可以不增加线上的压力,同时提供更高效的查询方式?想过redis,但最终选择用文本存储。因为数据分析是一个独立的需求,不希望与现有的redis集群或者其它存储服务有交集。还有一个原因是每次分析的中间结果,对下一次分析并没有很大的实质作用,并不需要把结果持久存储,而且占的内存也会较多。最终使用文本存储,然后用二分来查找。特点,1,存储非常快,虽然redis等nosql服务虽然已经非常快,但仍无法与文本存储相提并论;2,查找的时候使用二分查找,百万条记录查询也可在0.1ms内完成(使用线上的普通硬盘,如果是ssd盘会更快)。

实现步骤

数据库中需要的字段导出到文本        方法:使用MysqLPHPmyadmin工具,执行sql语句查出主建id和相应字段  如以上的关键词表: select kid, keyword from keyword  然后使用PHPmyadmin的导出工具,可以快速把结果导出到文本中  操作截图:

导出数据流程1


导出数据流程2

  • 将导出的文本(已经按id进行过排序)转换格式重新存储

  • 程序读取转换后的格式

文本存储格式

说明 :需求中,文本每行有两列,第一列是主建ID(数字),第二列为文本。整个文本已经按第一列有序排列,两列之间用tab键分隔。
之前有看过ip.dat的存储,本次仿照其存储格式:将文本中的内容每行转换为固定长度后,存储到新的文件搜索时,使用文件操作函数fopen,fseek,fgets等函数按字节读取内容,并以二分查找法快速定位需要的内容

代码实现部分

  • 通用类,类似需求只需要提供符合标准的文本(每行两列,第一列为查找的ID,第二列为文本。同时文本已经按第一列有序排序)

  • 生成以上所提到的存储格式

  • 提供根据id查询接口

代码片断

  • 重新生成新的存储格式

     //读源文件,写入到新的索引文件
      $readfd = fopen($this->filename, 'rb');
      $writefd = fopen($this->formatFile.'_tmp', 'wb+');
      if ($readfd === false || $writefd === false) {
          return false;
      }
      echo "\n start reformat file $this->filename ..";
      while (!feof($readfd)) {
          $line = fgets($readfd, 8192);
          fwrite($writefd, pack("a".$this->maxLength, $line));
      }
      echo "\n reformat ok\n";
      fclose($readfd);
      fclose($writefd);
      rename($this->formatFile.'_tmp', $this->formatFile);
  • 二分查找的代码片断

     /**
      * 在索引文件中进行二分查找
      * @param  int $id    进行二分查找的id
      * @return [type]     [description]
      */
      public function search($key)
      {
          $filesize = filesize($this->formatFile);
          $fd = fopen($this->formatFile, "rb");
          $left = 0; //行号
          $right = ($filesize / $this->maxLength) - 1; 
          while ($left <= $right) {
              $middle = intval(($right + $left)/2);
              fseek($fd, ($middle) * $this->maxLength);
              $info = unpack("a*", fread($fd, $this->maxLength))['1'];
              $lineinfo = explode("\t", $info, 2);
              if ($lineinfo['0'] > $key) {
                  $right = $middle - 1;
              } elseif ($lineinfo['0'] < $key) {
                  $left = $middle + 1;
              } else {
                  return $lineinfo['1'];
              }
          }
          return false;
      }

    整个类库代码一共91行,具体可查看github的demo代码

运行截图

运行截图

以上拿100万的关键词进行测试,根据关键词id快速查找关键词,平均速度可以达到0.1毫秒。

总结

以上是编程之家为你收集整理的PHP实现文本快速查找:二分查找法全部内容,希望文章能够帮你解决PHP实现文本快速查找:二分查找法所遇到的程序开发问题。

如果觉得编程之家网站内容还不错,欢迎将编程之家网站推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。

小编个人微信号 jb51ccc
喜欢与人分享编程技术与工作经验,欢迎加入编程之家官方交流群!

相关文章

猜你在找的PHP相关文章

PHP使用百度地图api 获取指定地址的经纬度
try{ throw exception(&#39;抛出异常&#39;,403) // 尝试执行,如果有异常 就抛出异常 echo &#39;aaa&#39; //此处不会执行 }catch (exc
1.防止sql注入-预准备 mysqli: $qSelect = $DBH->prepare("SELECT * FROM users WHERE username = ?"
strlen(); strlen 和 mb_strlen 都是用于获取字符串长度的, 其中 strlen 只针对单字节编码字符,也就是说它计算的是 字符串的总字节数,如果是多字节编码,如 gbk 和
今天实现上传文件和指定路径下文件进行对比时,用到了这个array_map方法,传入的回调函数里面执行 array_splice()方法时,却报错第一个参数不是一个数组,给定的null。 起初在arra
什么是时间轮算法?把任务放到它需要被执行的时刻,然后等待时针转到这个时刻,取出该时刻的任务,执行并将任务从该时刻删除(消费)。解决了什么问题?以商品为例,如何实现商品的过保质期自动失效?1:我们可以每分
为了简单快速的接入,在申请支付宝商户后,我们可通过沙箱应用进行测试准备证书首先登录支付宝开放平台沙箱环境:开放平台-沙箱环境在 RSA2(SHA256)密钥 设置中的加签模式选择 公钥证书,然后访问&nb
这篇文章在很早以前就看到了,由于最近要自己做一些主题方面的东西,代码需要更加规范,用这些反面的例子来约束自己,告诉自己代码不应该这样写,虽然它也能实现功能,但那样做并不明智,也不美观。现在将这些小花絮