PHP實現文本快速查找 - 二分查找

PHP實現文本快速查找 - 二分查找法

原由

先說說事情的原由,最近在分析數據時常常遇到一種場景,代碼須要頻繁的讀某一張數據庫的表,好比根據地區ID獲取地區名稱、根據網站分類ID獲取分類名稱、根據關鍵詞ID獲取關鍵詞等。雖然以上需求均可以在原始建表時,經過冗餘數據來解決。但仍有部分業務存的只是關聯表的ID,數據分析時須要頻繁的查表。php

所讀的表存在共同的特色

  • 數據幾乎不會變動
  • 數據量適中,從一萬到100多萬,若是全加載到內存也不太合適。

糾結的地方

在作數據分析時,須要十分頻繁的讀這些表,每秒有可能須要讀上萬次。其實內部的數據庫集羣徹底能夠勝任,但會對線上業務稍有影響。(你懂得,小公司不可能爲離線分析作一套完整的數據存儲服務。大部分數據分析還要藉助線上的數據集羣)mysql

優化方案的思考

有沒有一種方式能夠不增長線上的壓力,同時提供更高效的查詢方式?想過redis,但最終選擇用文本存儲。由於數據分析是一個獨立的需求,不但願與現有的redis集羣或者其它存儲服務有交集。還有一個緣由是每次分析的中間結果,對下一次分析並無很大的實質做用,並不須要把結果持久存儲,並且佔的內存也會較多。最終使用文本存儲,而後用二分來查找。特色,1,存儲很是快,雖然redis等nosql服務雖然已經很是快,但仍沒法與文本存儲相提並論;2,查找的時候使用二分查找,百萬條記錄查詢也可在0.1ms內完成(使用線上的普通硬盤,若是是ssd盤會更快)。git

實現步驟

  • 將數據庫中須要的字段導出到文本github

    方法:使用mysql的phpmyadmin工具,執行sql語句查出主建id和相應字段
    如以上的關鍵詞表: select kid, keyword from keyword
    而後使用phpmyadmin的導出工具,能夠快速把結果導出到文本中
    操做截圖:

    image


image

  • 將導出的文本(已經按id進行過排序)轉換格式從新存儲
  • 程序讀取轉換後的格式

文本存儲格式

說明 :需求中,文本每行有兩列,第一列是主建ID(數字),第二列爲文本。整個文本已經按第一列有序排列,兩列之間用tab鍵分隔。 以前有看過ip.dat的存儲,本次仿照其存儲格式:將文本中的內容每行轉換爲固定長度後,存儲到新的文件。搜索時,使用文件操做函數fopen,fseek,fgets等函數按字節讀取內容,並以二分查找法快速定位須要的內容。redis

代碼實現部分

  • 通用類,相似需求只須要提供符合標準的文本(每行兩列,第一列爲查找的ID,第二列爲文本。同時文本已經按第一列有序排序)
  • 生成以上所提到的存儲格式
  • 提供根據id查詢接口

代碼片段

  • 從新生成新的存儲格式sql

    //讀源文件,寫入到新的索引文件
    $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代碼 ,相關連接

運行截圖

image

以上拿100萬的關鍵詞進行測試,根據關鍵詞id快速查找關鍵詞,平均速度能夠達到0.1毫秒。nosql

相關文章
相關標籤/搜索