lua binding forward with binarySearch and btreeSearch interface implemented

This commit is contained in:
lionsoul 2018-10-03 13:12:05 +08:00
parent 04f093d9bd
commit 5f073d814e
3 changed files with 211 additions and 25 deletions

View File

@ -8,18 +8,28 @@ Ip2region lua binding
require("bit32");
local Ip2region = {
dbFile = "",
dbFileHandler = "",
dbBinStr = "",
HeaderSip = "",
HeaderPtr = "",
headerLen = "",
firstIndexPtr = "",
lastIndexPtr = "",
totalBlocks = "",
dbBinStr = "",
dbFile = ""
firstIndexPtr = 0,
lastIndexPtr = 0,
totalBlocks = 0
};
-- common constants
local INDEX_BLOCK_LENGTH = 12;
local TOTAL_HEADER_LENGTH = 8192;
function Ip2region:new(obj)
obj = obj or {};
setmetatable(obj, {__index = self});
return obj;
end
--[[
internal function to get a integer from a binary string
@ -90,18 +100,6 @@ function get_file_contents(file)
end
-- common constants
local INDEX_BLOCK_LENGTH = 12;
local TOTAL_HEADER_LENGTH = 8192;
function Ip2region:new(obj)
obj = obj or {};
setmetatable(obj, {__index = self});
return obj;
end
--[[
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
@ -171,6 +169,67 @@ or long ip numeric with binary search algorithm
@return table or nil for failed
]]--
function Ip2region:binarySearch(ip)
-- check and conver the ip address
if ( type(ip) == "string" ) then
ip = ip2long(ip);
end
if ( self.totalBlocks == 0 ) then
-- check and open the original db file
if ( self.dbFileHandler == "" ) then
self.dbFileHandler = io.open(self.dbFile, "r");
if ( not self.dbFileHandler ) then
return nil;
end
end
self.dbFileHandler:seek("set", 0);
local superBlock = self.dbFileHandler:read(8);
self.firstIndexPtr = getLong(superBlock, 1); -- 0 + 1
self.lastIndexPtr = getLong(superBlock, 5); -- 4 + 1
self.totalBlocks = (self.lastIndexPtr-self.firstIndexPtr)/INDEX_BLOCK_LENGTH + 1;
end
-- binary search to define the data
local l = 0;
local h = self.totalBlocks;
local dataPtr = 0;
while ( l <= h ) do
local m = math.floor((l + h) / 2);
local p = m * INDEX_BLOCK_LENGTH;
self.dbFileHandler:seek("set", self.firstIndexPtr + p);
local buffer = self.dbFileHandler:read(INDEX_BLOCK_LENGTH);
local sip = getLong(buffer, 1); -- 0 + 1
if ( ip < sip ) then
h = m - 1;
else
local eip = getLong(buffer, 5); -- 4 + 1
if ( ip > eip ) then
l = m + 1;
else
dataPtr = getLong(buffer, 9); -- 8 + 1
break;
end
end
end
-- not matched just stop it here
if ( dataPtr == 0 ) then return nil; end
-- get the data
local dataLen = bit32.band(bit32.rshift(dataPtr, 24), 0xFF);
dataPtr = bit32.band(dataPtr, 0x00FFFFFF);
self.dbFileHandler:seek("set", dataPtr);
local data = self.dbFileHandler:read(dataLen);
return {
city_id = getLong(data, 1), -- 0 + 1
region = string.sub(data, 5) -- 4 + 1
};
end
@ -181,6 +240,131 @@ get the data block associated with the specified ip with b-tree search algorithm
@return table or nil for failed
]]--
function Ip2region:btreeSearch(ip)
if ( type(ip) ) then
ip = ip2long(ip);
end
-- check and load the header
if ( self.HeaderSip == "" ) then
-- check and open the original db file
if ( self.dbFileHandler == "" ) then
self.dbFileHandler = io.open(self.dbFile, 'r');
if ( not self.dbFileHandler ) then
return nil;
end
end
self.dbFileHandler:seek("set", 8);
local buffer = self.dbFileHandler:read(TOTAL_HEADER_LENGTH);
-- fill the header
local i = 0;
local idx = 0;
self.HeaderSip = {};
self.HeaderPtr = {};
for i=0, TOTAL_HEADER_LENGTH, 8 do
local startIp = getLong(buffer, i + 1); -- 0 + 1
local dataPtr = getLong(buffer, i + 5); -- 4 + 1
if ( dataPtr == 0 ) then
break;
end
table.insert(self.HeaderSip, startIp);
table.insert(self.HeaderPtr, dataPtr);
idx = idx + 1;
end
self.headerLen = idx;
end
-- 1. define the index block with the binary search
local l = 0;
local h = self.headerLen;
local sptr = 0;
local eptr = 0;
while ( l <= h ) do
local m = math.floor((l + h) / 2);
-- perfetc matched, just return it
if ( ip == self.HeaderSip[m] ) then
if ( m > 0 ) then
sptr = self.HeaderPtr[m-1];
eptr = self.HeaderPtr[m ];
else
sptr = self.HeaderPtr[m ];
eptr = self.HeaderPtr[m+1];
end
break;
end
-- less then the middle value
if ( ip < self.HeaderSip[m] ) then
if ( m == 0 ) then
sptr = self.HeaderPtr[m ];
eptr = self.HeaderPtr[m+1];
break;
elseif ( ip > self.HeaderSip[m-1] ) then
sptr = self.HeaderPtr[m-1];
eptr = self.HeaderPtr[m ];
break;
end
h = m - 1;
else
if ( m == self.headerLen - 1 ) then
sptr = self.HeaderPtr[m-1];
eptr = self.HeaderPtr[m ];
break;
elseif ( ip <= self.HeaderSip[m+1] ) then
sptr = self.HeaderPtr[m ];
eptr = self.HeaderPtr[m+1];
break;
end
l = m + 1;
end
end
-- match nothing just stop it
if ( sptr == 0 ) then return nil; end
-- 2. search the index blocks to define the data
self.dbFileHandler:seek("set", sptr);
local blockLen = eptr - sptr;
local index = self.dbFileHandler:read(blockLen + INDEX_BLOCK_LENGTH);
local dataPtr = 0;
l = 0;
h = blockLen / INDEX_BLOCK_LENGTH;
while ( l <= h ) do
local m = math.floor((l + h) / 2);
local p = m * INDEX_BLOCK_LENGTH;
local sip = getLong(index, p + 1); -- 0 + 1
if ( ip < sip ) then
h = m - 1;
else
local eip = getLong(index, p + 5); -- 4 + 1
if ( ip > eip ) then
l = m + 1;
else
dataPtr = getLong(index, p + 9); -- 8 + 1
break;
end
end
end
-- not matched
if ( dataPtr == 0 ) then return nil; end
-- 3. get the data
local dataLen = bit32.band(bit32.rshift(dataPtr, 24), 0xFF);
dataPtr = bit32.band(dataPtr, 0x00FFFFFF);
self.dbFileHandler:seek("set", dataPtr);
local data = self.dbFileHandler:read(dataLen);
return {
city_id = getLong(data, 1), -- 0 + 1
region = string.sub(data, 5) -- 4 + 1
};
end

View File

@ -3,5 +3,7 @@ local cjson = require "cjson";
local Ip2region = require "Ip2region";
local searcher = Ip2region:new({dbFile = "../../data/ip2region.db"});
local data = searcher:memorySearch("120.79.17.142");
-- local data = searcher:memorySearch("120.79.17.142");
-- local data = searcher:binarySearch("120.79.17.142");
local data = searcher:btreeSearch("120.79.17.142");
print(cjson.encode(data));

View File

@ -266,7 +266,7 @@ class Ip2Region
fseek($this->dbFileHandler, $sptr);
$index = fread($this->dbFileHandler, $blockLen + INDEX_BLOCK_LENGTH);
$dataptr = 0;
$dataPtr = 0;
$l = 0; $h = $blockLen / INDEX_BLOCK_LENGTH;
while ( $l <= $h ) {
$m = (($l + $h) >> 1);
@ -279,18 +279,18 @@ class Ip2Region
if ( $ip > $eip ) {
$l = $m + 1;
} else {
$dataptr = self::getLong($index, $p + 8);
$dataPtr = self::getLong($index, $p + 8);
break;
}
}
}
//not matched
if ( $dataptr == 0 ) return NULL;
if ( $dataPtr == 0 ) return NULL;
//3. get the data
$dataLen = (($dataptr >> 24) & 0xFF);
$dataPtr = ($dataptr & 0x00FFFFFF);
$dataLen = (($dataPtr >> 24) & 0xFF);
$dataPtr = ($dataPtr & 0x00FFFFFF);
fseek($this->dbFileHandler, $dataPtr);
$data = fread($this->dbFileHandler, $dataLen);