add #memorySearch interface for purge memory search

This commit is contained in:
lionsoul 2016-06-30 16:04:02 +08:00
parent 32ad842926
commit e436094563
3 changed files with 107 additions and 13 deletions

View File

@ -2,12 +2,12 @@
/** /**
* ip2region php seacher client class * ip2region php seacher client class
* *
* @author chenxin<chenxin619315@gmail.com> * @author chenxin<chenxin619315@gmail.com>
* @date 2015-10-29 * @date 2015-10-29
*/ */
defined('INDEX_BLOCK_LENGTH') or define('INDEX_BLOCK_LENGTH', 12); defined('INDEX_BLOCK_LENGTH') or define('INDEX_BLOCK_LENGTH', 12);
defined('TOTAL_HEADER_LENGTH') or define('TOTAL_HEADER_LENGTH', 4096); defined('TOTAL_HEADER_LENGTH') or define('TOTAL_HEADER_LENGTH', 4096);
class Ip2Region class Ip2Region
{ {
@ -29,6 +29,13 @@ class Ip2Region
private $firstIndexPtr = 0; private $firstIndexPtr = 0;
private $lastIndexPtr = 0; private $lastIndexPtr = 0;
private $totalBlocks = 0; private $totalBlocks = 0;
/**
* for memory mode only
* the original db binary string
*/
private $dbBinStr = NULL;
private $dbFile = NULL;
/** /**
* construct method * construct method
@ -37,7 +44,66 @@ class Ip2Region
*/ */
public function __construct( $ip2regionFile ) 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 //check and conver the ip address
if ( is_string($ip) ) $ip = ip2long($ip); if ( is_string($ip) ) $ip = ip2long($ip);
if ( $this->totalBlocks == 0 ) { 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); fseek($this->dbFileHandler, 0);
$superBlock = fread($this->dbFileHandler, 8); $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 * get the data block associated with the specifield ip with b-tree search algorithm
* @Note: not thread safe
* *
* @param ip * @param ip
* @return Mixed Array for NULL for any error * @return Mixed Array for NULL for any error
*/ */
public function btreeSearch( $ip ) public function btreeSearch( $ip )
{ {
@ -112,6 +187,14 @@ class Ip2Region
//check and load the header //check and load the header
if ( $this->HeaderSip == NULL ) { 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); fseek($this->dbFileHandler, 8);
$buffer = fread($this->dbFileHandler, TOTAL_HEADER_LENGTH); $buffer = fread($this->dbFileHandler, TOTAL_HEADER_LENGTH);
@ -240,7 +323,11 @@ class Ip2Region
*/ */
public function __destruct() public function __destruct()
{ {
if ( $this->dbFileHandler != NULL ) fclose($this->dbFileHandler); if ( $this->dbFileHandler != NULL ) {
fclose($this->dbFileHandler);
}
$this->dbBinStr = NULL;
$this->HeaderSip = NULL; $this->HeaderSip = NULL;
$this->HeaderPtr = NULL; $this->HeaderPtr = NULL;
} }

View File

@ -15,12 +15,19 @@ EOF;
array_shift($argv); array_shift($argv);
$dbFile = $argv[0]; $dbFile = $argv[0];
$method = 1; $method = 'btreeSearch';
$algorithm = 'B-tree'; $algorithm = 'B-tree';
if ( isset($argv[1]) if ( isset($argv[1]) ) {
&& strtolower($argv[1]) == 'binary' ) { switch ( strtolower($argv[1]) ) {
$method = 2; case 'binary':
$algorithm = 'Binary'; $algorithm = 'Binary';
$method = 'binarySearch';
break;
case 'memory':
$algorithm = 'Memory';
$method = 'memorySearch';
break;
}
} }
require dirname(__FILE__) . '/Ip2Region.class.php'; require dirname(__FILE__) . '/Ip2Region.class.php';
@ -47,7 +54,7 @@ while ( true ) {
} }
$s_time = getTime(); $s_time = getTime();
$data = $method==2 ? $ip2regionObj->binarySearch($line) : $ip2regionObj->btreeSearch($line); $data = $ip2regionObj->{$method}($line);
$c_time = getTime() - $s_time; $c_time = getTime() - $s_time;
printf("%s|%s in %.5f millseconds\n", $data['city_id'], $data['region'], $c_time); printf("%s|%s in %.5f millseconds\n", $data['city_id'], $data['region'], $c_time);
} }

Binary file not shown.