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

起因

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

所读的表存在共同的特点

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

纠结的地方

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

优化方案的思考

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

实现步骤

  • 将数据库中需要的字段导出到文本

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


  • 将导出的文本(已经按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实现文本快速查找 - 二分查找的更多相关文章

  1. 【转】Java实现折半查找(二分查找)的递归和非递归算法

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://wintys.blog.51cto.com/425414/94051 Java二分 ...

  2. JAVA查找--[二分查找]

    package com.array; public class BinaryFind { /* * 项目名称:二分查找 ; * 项目要求:用JAVA对数组进行查找,并运用快速查找算法; * 作者:Se ...

  3. leetcode旋转数组查找 二分查找的变形

    http://blog.csdn.net/pickless/article/details/9191075 Suppose a sorted array is rotated at some pivo ...

  4. 查找算法(I) 顺序查找 二分查找 索引查找

    查找 本文为查找算法的第一部分内容,包括了基本概念,顺序查找.二分查找和索引查找.关于散列表和B树查找的内容,待有空更新吧. 基本概念 查找(search)又称检索,在计算机上对数据表进行查找,就是根 ...

  5. 数据结构基础(2) --顺序查找 & 二分查找

    顺序查找 适用范围: 没有进行排序的数据序列 缺点: 速度非常慢, 效率为O(N) //实现 template <typename Type> Type *sequenceSearch(T ...

  6. [javaSE] 数组(查找-二分查找)

    前提数组必须是有序的 定义最小,最大,中间的角标索引 int min,max,mid; min=0; max=arr.length-1; mid=(min+max)/2; 上面的索引需要变化,使用循环 ...

  7. 顺序查找&二分查找&索引查找

    1.查找技术的分类.如下图: 2.什么是顺序查找呢?(无序表) 顺序查找的原理很简单,就是遍历整个列表,逐个进行记录的关键字与给定值比较,若某个记录的关键字和给定值相等,则查找成功,找到所查的记录.如 ...

  8. Java基础知识强化60:经典查找之二分查找

    1. 二分查找       二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好:其缺点是要求待查表为有序表,且插入删除困难.因此,折半查找方法适用于不经常变动而查找频繁的有序列表. 比较 ...

  9. 【leetcode边做边学】二分查找应用

    很多其它请关注我的HEXO博客:http://jasonding1354.github.io/ 简书主页:http://www.jianshu.com/users/2bd9b48f6ea8/lates ...

随机推荐

  1. CentOS_7.2安装Nginx_1.9

    一.安装依赖包和开发工具: yum install vim vim-enhanced wget zip unzip telnet ntsysv compat* apr* nasm* gcc gcc* ...

  2. SQL server同时删除多个表

    ) while exists (select name from sys.tables where name like 'se_%') begin @varname = name from sys.t ...

  3. js对特殊字符转义、时间格式化、获取URL参数

    /*特殊字符转义*/ function replace_html(str) { var str = str.toString().replace(/&/g, "&" ...

  4. 自定义BadgeView

    -(instancetype)initWithFrame:(CGRect)frame{    if (self=[super initWithFrame:frame]) {        self.u ...

  5. usb端口号绑定

    由于ubuntu USB设备号为从零开始依次累加,所以多个设备每次开机后设备号不固定,机器人每次开机都要蛋疼的按顺序插, 在网上找到一种方法:udev的规则 udev的规则说明,可以参考博客说明:ht ...

  6. 检索 COM 类工厂中 CLSID 为 {820280E0-8ADA-4582-A1D9-960A83CE8BB5} 的组件失败,原因是出现以下错误: 80040154 没有注册类 (异常来自 HRESULT:0x80040154 (REGDB_E_CLASSNOTREG))。

    检索 COM 类工厂中 CLSID 为 {820280E0-8ADA-4582-A1D9-960A83CE8BB5} 的组件失败,原因是出现以下错误: 80040154 没有注册类 (异常来自 HRE ...

  7. xcode6.0以上创建一个Empty Application

    运行Xcode 6,创建一个Single View Application工程.   创建好后,把工程目录下的Main.storyboard和LaunchScreen.xib删除,扔进废纸篓.   打 ...

  8. http get/post解决乱码问题

    <form method="默认为get"-> <s:form mothod="默认为post"-> ================= ...

  9. Three.Js学习第一天

    因为工作需求,最近接触到了ThreeJs库,国内学习文档的确少,所以在这里写下bolgs记录学习史,并且给后面学习的人尽一份微博之力. 3D场景依靠WebGL技术.目前支持比较好的浏览器,谷歌.火狐. ...

  10. MFC-01-Chapter01:Hello,MFC---1.2 MFC简介

    1.2 MFC简介 MFC是Microsoft提供的放置Windows API的面向对象的包装的C++类库.MFC大约封装了好几百个类,其中有一些可以直接调用,有些类可以作为用户自己的类的基类.一些M ...