diff --git a/binding/php/Ip2Region.class.php b/binding/php/Ip2Region.class.php index ceb019a..14a1fc2 100644 --- a/binding/php/Ip2Region.class.php +++ b/binding/php/Ip2Region.class.php @@ -2,12 +2,12 @@ /** * ip2region php seacher client class * - * @author chenxin + * @author chenxin * @date 2015-10-29 */ -defined('INDEX_BLOCK_LENGTH') or define('INDEX_BLOCK_LENGTH', 12); -defined('TOTAL_HEADER_LENGTH') or define('TOTAL_HEADER_LENGTH', 4096); +defined('INDEX_BLOCK_LENGTH') or define('INDEX_BLOCK_LENGTH', 12); +defined('TOTAL_HEADER_LENGTH') or define('TOTAL_HEADER_LENGTH', 4096); class Ip2Region { @@ -29,6 +29,13 @@ class Ip2Region private $firstIndexPtr = 0; private $lastIndexPtr = 0; private $totalBlocks = 0; + + /** + * for memory mode only + * the original db binary string + */ + private $dbBinStr = NULL; + private $dbFile = NULL; /** * construct method @@ -37,7 +44,66 @@ class Ip2Region */ public function __construct( $ip2regionFile ) { - $this->dbFileHandler = fopen($ip2regionFile, 'r'); + $this->dbFile = $ip2regionFile; + } + + /** + * all the db binary string will be loaded into memory + * then search the memory only and this will a lot faster than disk base search + * @Note: + * invoke it once before put it to public invoke could make it thread safe + * + * @param $ip + */ + public function memorySearch($ip) + { + //check and load the binary string for the first time + if ( $this->dbBinStr == NULL ) { + $this->dbBinStr = file_get_contents($this->dbFile); + if ( $this->dbBinStr == false ) { + throw new Exception("Fail to open the db file {$this->dbFile}"); + } + + $this->firstIndexPtr = self::getLong($this->dbBinStr, 0); + $this->lastIndexPtr = self::getLong($this->dbBinStr, 4); + $this->totalBlocks = ($this->lastIndexPtr-$this->firstIndexPtr)/INDEX_BLOCK_LENGTH + 1; + echo "length: ", strlen($this->dbBinStr); + } + + if ( is_string($ip) ) $ip = ip2long($ip); + + //binary search to define the data + $l = 0; + $h = $this->totalBlocks; + $dataPtr = 0; + while ( $l <= $h ) { + $m = (($l + $h) >> 1); + $p = $this->firstIndexPtr + $m * INDEX_BLOCK_LENGTH; + $sip = self::getLong($this->dbBinStr, $p); + if ( $ip < $sip ) { + $h = $m - 1; + } else { + $eip = self::getLong($this->dbBinStr, $p + 4); + if ( $ip > $eip ) { + $l = $m + 1; + } else { + $dataPtr = self::getLong($this->dbBinStr, $p + 8); + break; + } + } + } + + //not matched just stop it here + if ( $dataPtr == 0 ) return NULL; + + //get the data + $dataLen = (($dataPtr >> 24) & 0xFF); + $dataPtr = ($dataPtr & 0x00FFFFFF); + + return array( + 'city_id' => self::getLong($this->dbBinStr, $dataPtr), + 'region' => substr($this->dbBinStr, $dataPtr + 4, $dataLen - 4) + ); } /** @@ -51,6 +117,14 @@ class Ip2Region //check and conver the ip address if ( is_string($ip) ) $ip = ip2long($ip); if ( $this->totalBlocks == 0 ) { + //check and open the original db file + if ( $this->dbFileHandler == NULL ) { + $this->dbFileHandler = fopen($this->dbFile, 'r'); + if ( $this->dbFileHandler == false ) { + throw new Exception("Fail to open the db file {$this->dbFile}"); + } + } + fseek($this->dbFileHandler, 0); $superBlock = fread($this->dbFileHandler, 8); @@ -102,9 +176,10 @@ class Ip2Region /** * get the data block associated with the specifield ip with b-tree search algorithm + * @Note: not thread safe * - * @param ip - * @return Mixed Array for NULL for any error + * @param ip + * @return Mixed Array for NULL for any error */ public function btreeSearch( $ip ) { @@ -112,6 +187,14 @@ class Ip2Region //check and load the header if ( $this->HeaderSip == NULL ) { + //check and open the original db file + if ( $this->dbFileHandler == NULL ) { + $this->dbFileHandler = fopen($this->dbFile, 'r'); + if ( $this->dbFileHandler == false ) { + throw new Exception("Fail to open the db file {$this->dbFile}"); + } + } + fseek($this->dbFileHandler, 8); $buffer = fread($this->dbFileHandler, TOTAL_HEADER_LENGTH); @@ -240,7 +323,11 @@ class Ip2Region */ public function __destruct() { - if ( $this->dbFileHandler != NULL ) fclose($this->dbFileHandler); + if ( $this->dbFileHandler != NULL ) { + fclose($this->dbFileHandler); + } + + $this->dbBinStr = NULL; $this->HeaderSip = NULL; $this->HeaderPtr = NULL; } diff --git a/binding/php/testSeacher.php b/binding/php/testSeacher.php index 5f2d8aa..6547f4f 100644 --- a/binding/php/testSeacher.php +++ b/binding/php/testSeacher.php @@ -15,12 +15,19 @@ EOF; array_shift($argv); $dbFile = $argv[0]; -$method = 1; +$method = 'btreeSearch'; $algorithm = 'B-tree'; -if ( isset($argv[1]) - && strtolower($argv[1]) == 'binary' ) { - $method = 2; - $algorithm = 'Binary'; +if ( isset($argv[1]) ) { + switch ( strtolower($argv[1]) ) { + case 'binary': + $algorithm = 'Binary'; + $method = 'binarySearch'; + break; + case 'memory': + $algorithm = 'Memory'; + $method = 'memorySearch'; + break; + } } require dirname(__FILE__) . '/Ip2Region.class.php'; @@ -47,7 +54,7 @@ while ( true ) { } $s_time = getTime(); - $data = $method==2 ? $ip2regionObj->binarySearch($line) : $ip2regionObj->btreeSearch($line); + $data = $ip2regionObj->{$method}($line); $c_time = getTime() - $s_time; printf("%s|%s in %.5f millseconds\n", $data['city_id'], $data['region'], $c_time); } diff --git a/data/ip2region.db b/data/ip2region.db index 27a218c..eb2eef5 100644 Binary files a/data/ip2region.db and b/data/ip2region.db differ