406 lines
12 KiB
Java
406 lines
12 KiB
Java
package org.lionsoul.ip2region;
|
|
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.io.RandomAccessFile;
|
|
|
|
/**
|
|
* ip db searcher class (Not thread safe)
|
|
*
|
|
* @author chenxin<chenxin619315@gmail.com>
|
|
*/
|
|
public class DbSearcher
|
|
{
|
|
public static final int BTREE_ALGORITHM = 1;
|
|
public static final int BINARY_ALGORITHM = 2;
|
|
public static final int MEMORY_ALGORITYM = 3;
|
|
|
|
/**
|
|
* db config
|
|
*/
|
|
private DbConfig dbConfig = null;
|
|
|
|
/**
|
|
* db file access handler
|
|
*/
|
|
private RandomAccessFile raf = null;
|
|
|
|
/**
|
|
* header blocks buffer
|
|
*/
|
|
private long[] HeaderSip = null;
|
|
private int[] HeaderPtr = null;
|
|
private int headerLength;
|
|
|
|
/**
|
|
* super blocks info
|
|
*/
|
|
private long firstIndexPtr = 0;
|
|
private long lastIndexPtr = 0;
|
|
private int totalIndexBlocks = 0;
|
|
|
|
/**
|
|
* for memory mode
|
|
* the original db binary string
|
|
*/
|
|
private byte[] dbBinStr = null;
|
|
|
|
/**
|
|
* construct class
|
|
*
|
|
* @param bdConfig
|
|
* @param dbFile
|
|
* @throws FileNotFoundException
|
|
*/
|
|
public DbSearcher( DbConfig dbConfig, String dbFile ) throws FileNotFoundException
|
|
{
|
|
this.dbConfig = dbConfig;
|
|
raf = new RandomAccessFile(dbFile, "r");
|
|
}
|
|
|
|
/**
|
|
* construct method with self-define std ip2region bianry string support
|
|
* Thanks to the issue from Wendal at https://gitee.com/lionsoul/ip2region/issues/IILFL
|
|
*
|
|
* @param dbConfig
|
|
* @param dbBinStr
|
|
*/
|
|
public DbSearcher(DbConfig dbConfig, byte[] dbBinStr)
|
|
{
|
|
this.dbConfig = dbConfig;
|
|
this.dbBinStr = dbBinStr;
|
|
|
|
firstIndexPtr = Util.getIntLong(dbBinStr, 0);
|
|
lastIndexPtr = Util.getIntLong(dbBinStr, 4);
|
|
totalIndexBlocks = (int)((lastIndexPtr - firstIndexPtr)/IndexBlock.getIndexBlockLength()) + 1;
|
|
}
|
|
|
|
/**
|
|
* get the region with a int ip address with memory binary search algorithm
|
|
*
|
|
* @param ip
|
|
* @throws IOException
|
|
*/
|
|
public DataBlock memorySearch(long ip) throws IOException
|
|
{
|
|
int blen = IndexBlock.getIndexBlockLength();
|
|
if ( dbBinStr == null ) {
|
|
dbBinStr = new byte[(int)raf.length()];
|
|
raf.seek(0L);
|
|
raf.readFully(dbBinStr, 0, dbBinStr.length);
|
|
|
|
//initialize the global vars
|
|
firstIndexPtr = Util.getIntLong(dbBinStr, 0);
|
|
lastIndexPtr = Util.getIntLong(dbBinStr, 4);
|
|
totalIndexBlocks = (int)((lastIndexPtr - firstIndexPtr)/blen) + 1;
|
|
}
|
|
|
|
//search the index blocks to define the data
|
|
int l = 0, h = totalIndexBlocks;
|
|
long sip, eip, dataptr = 0;
|
|
while ( l <= h ) {
|
|
int m = (l + h) >> 1;
|
|
int p = (int)(firstIndexPtr + m * blen);
|
|
|
|
sip = Util.getIntLong(dbBinStr, p);
|
|
if ( ip < sip ) {
|
|
h = m - 1;
|
|
} else {
|
|
eip = Util.getIntLong(dbBinStr, p + 4);
|
|
if ( ip > eip ) {
|
|
l = m + 1;
|
|
} else {
|
|
dataptr = Util.getIntLong(dbBinStr, p + 8);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//not matched
|
|
if ( dataptr == 0 ) return null;
|
|
|
|
//get the data
|
|
int dataLen = (int)((dataptr >> 24) & 0xFF);
|
|
int dataPtr = (int)((dataptr & 0x00FFFFFF));
|
|
int city_id = (int)Util.getIntLong(dbBinStr, dataPtr);
|
|
String region = new String(dbBinStr, dataPtr + 4, dataLen - 4, "UTF-8");
|
|
|
|
return new DataBlock(city_id, region, dataPtr);
|
|
}
|
|
|
|
/**
|
|
* get the region throught the ip address with memory binary search algorithm
|
|
*
|
|
* @param ip
|
|
* @return DataBlock
|
|
* @throws IOException
|
|
*/
|
|
public DataBlock memorySearch( String ip ) throws IOException
|
|
{
|
|
return memorySearch(Util.ip2long(ip));
|
|
}
|
|
|
|
|
|
/**
|
|
* get by index ptr
|
|
*
|
|
* @param indexPtr
|
|
* @throws IOException
|
|
*/
|
|
public DataBlock getByIndexPtr( long ptr ) throws IOException
|
|
{
|
|
raf.seek(ptr);
|
|
byte[] buffer = new byte[12];
|
|
raf.readFully(buffer, 0, buffer.length);
|
|
//long startIp = Util.getIntLong(buffer, 0);
|
|
//long endIp = Util.getIntLong(buffer, 4);
|
|
long extra = Util.getIntLong(buffer, 8);
|
|
|
|
int dataLen = (int)((extra >> 24) & 0xFF);
|
|
int dataPtr = (int)((extra & 0x00FFFFFF));
|
|
|
|
raf.seek(dataPtr);
|
|
byte[] data = new byte[dataLen];
|
|
raf.readFully(data, 0, data.length);
|
|
|
|
int city_id = (int)Util.getIntLong(data, 0);
|
|
String region = new String(data, 4, data.length - 4, "UTF-8");
|
|
|
|
return new DataBlock(city_id, region, dataPtr);
|
|
}
|
|
|
|
/**
|
|
* get the region with a int ip address with b-tree algorithm
|
|
*
|
|
* @param ip
|
|
* @throws IOException
|
|
*/
|
|
public DataBlock btreeSearch( long ip ) throws IOException
|
|
{
|
|
//check and load the header
|
|
if ( HeaderSip == null ) {
|
|
raf.seek(8L); //pass the super block
|
|
byte[] b = new byte[dbConfig.getTotalHeaderSize()];
|
|
// byte[] b = new byte[4096];
|
|
raf.readFully(b, 0, b.length);
|
|
|
|
//fill the header
|
|
int len = b.length >> 3, idx = 0; //b.lenght / 8
|
|
HeaderSip = new long[len];
|
|
HeaderPtr = new int [len];
|
|
long startIp, dataPtr;
|
|
for ( int i = 0; i < b.length; i += 8 ) {
|
|
startIp = Util.getIntLong(b, i);
|
|
dataPtr = Util.getIntLong(b, i + 4);
|
|
if ( dataPtr == 0 ) break;
|
|
|
|
HeaderSip[idx] = startIp;
|
|
HeaderPtr[idx] = (int)dataPtr;
|
|
idx++;
|
|
}
|
|
|
|
headerLength = idx;
|
|
}
|
|
|
|
//1. define the index block with the binary search
|
|
if ( ip == HeaderSip[0] ) {
|
|
return getByIndexPtr(HeaderPtr[0]);
|
|
} else if ( ip == HeaderSip[headerLength-1] ) {
|
|
return getByIndexPtr(HeaderPtr[headerLength-1]);
|
|
}
|
|
|
|
int l = 0, h = headerLength, sptr = 0, eptr = 0;
|
|
while ( l <= h ) {
|
|
int m = (l + h) >> 1;
|
|
|
|
//perfetc matched, just return it
|
|
if ( ip == HeaderSip[m] ) {
|
|
if ( m > 0 ) {
|
|
sptr = HeaderPtr[m-1];
|
|
eptr = HeaderPtr[m ];
|
|
} else {
|
|
sptr = HeaderPtr[m ];
|
|
eptr = HeaderPtr[m+1];
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
//less then the middle value
|
|
if ( ip < HeaderSip[m] ) {
|
|
if ( m == 0 ) {
|
|
sptr = HeaderPtr[m ];
|
|
eptr = HeaderPtr[m+1];
|
|
break;
|
|
} else if ( ip > HeaderSip[m-1] ) {
|
|
sptr = HeaderPtr[m-1];
|
|
eptr = HeaderPtr[m ];
|
|
break;
|
|
}
|
|
h = m - 1;
|
|
} else {
|
|
if ( m == headerLength - 1 ) {
|
|
sptr = HeaderPtr[m-1];
|
|
eptr = HeaderPtr[m ];
|
|
break;
|
|
} else if ( ip <= HeaderSip[m+1] ) {
|
|
sptr = HeaderPtr[m ];
|
|
eptr = HeaderPtr[m+1];
|
|
break;
|
|
}
|
|
l = m + 1;
|
|
}
|
|
}
|
|
|
|
//match nothing just stop it
|
|
if ( sptr == 0 ) return null;
|
|
|
|
//2. search the index blocks to define the data
|
|
int blockLen = eptr - sptr, blen = IndexBlock.getIndexBlockLength();
|
|
byte[] iBuffer = new byte[blockLen + blen]; //include the right border block
|
|
raf.seek(sptr);
|
|
raf.readFully(iBuffer, 0, iBuffer.length);
|
|
|
|
l = 0; h = blockLen / blen;
|
|
long sip, eip, dataptr = 0;
|
|
while ( l <= h ) {
|
|
int m = (l + h) >> 1;
|
|
int p = m * blen;
|
|
sip = Util.getIntLong(iBuffer, p);
|
|
if ( ip < sip ) {
|
|
h = m - 1;
|
|
} else {
|
|
eip = Util.getIntLong(iBuffer, p + 4);
|
|
if ( ip > eip ) {
|
|
l = m + 1;
|
|
} else {
|
|
dataptr = Util.getIntLong(iBuffer, p + 8);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//not matched
|
|
if ( dataptr == 0 ) return null;
|
|
|
|
//3. get the data
|
|
int dataLen = (int)((dataptr >> 24) & 0xFF);
|
|
int dataPtr = (int)((dataptr & 0x00FFFFFF));
|
|
|
|
raf.seek(dataPtr);
|
|
byte[] data = new byte[dataLen];
|
|
raf.readFully(data, 0, data.length);
|
|
|
|
int city_id = (int)Util.getIntLong(data, 0);
|
|
String region = new String(data, 4, data.length - 4, "UTF-8");
|
|
|
|
return new DataBlock(city_id, region, dataPtr);
|
|
}
|
|
|
|
/**
|
|
* get the region throught the ip address with b-tree search algorithm
|
|
*
|
|
* @param ip
|
|
* @return DataBlock
|
|
* @throws IOException
|
|
*/
|
|
public DataBlock btreeSearch( String ip ) throws IOException
|
|
{
|
|
return btreeSearch(Util.ip2long(ip));
|
|
}
|
|
|
|
/**
|
|
* get the region with a int ip address with binary search algorithm
|
|
*
|
|
* @param ip
|
|
* @throws IOException
|
|
*/
|
|
public DataBlock binarySearch( long ip ) throws IOException
|
|
{
|
|
int blen = IndexBlock.getIndexBlockLength();
|
|
if ( totalIndexBlocks == 0 ) {
|
|
raf.seek(0L);
|
|
byte[] superBytes = new byte[8];
|
|
raf.readFully(superBytes, 0, superBytes.length);
|
|
//initialize the global vars
|
|
firstIndexPtr = Util.getIntLong(superBytes, 0);
|
|
lastIndexPtr = Util.getIntLong(superBytes, 4);
|
|
totalIndexBlocks = (int)((lastIndexPtr - firstIndexPtr)/blen) + 1;
|
|
}
|
|
|
|
//search the index blocks to define the data
|
|
int l = 0, h = totalIndexBlocks;
|
|
byte[] buffer = new byte[blen];
|
|
long sip, eip, dataptr = 0;
|
|
while ( l <= h ) {
|
|
int m = (l + h) >> 1;
|
|
raf.seek(firstIndexPtr + m * blen); //set the file pointer
|
|
raf.readFully(buffer, 0, buffer.length);
|
|
sip = Util.getIntLong(buffer, 0);
|
|
if ( ip < sip ) {
|
|
h = m - 1;
|
|
} else {
|
|
eip = Util.getIntLong(buffer, 4);
|
|
if ( ip > eip ) {
|
|
l = m + 1;
|
|
} else {
|
|
dataptr = Util.getIntLong(buffer, 8);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//not matched
|
|
if ( dataptr == 0 ) return null;
|
|
|
|
//get the data
|
|
int dataLen = (int)((dataptr >> 24) & 0xFF);
|
|
int dataPtr = (int)((dataptr & 0x00FFFFFF));
|
|
|
|
raf.seek(dataPtr);
|
|
byte[] data = new byte[dataLen];
|
|
raf.readFully(data, 0, data.length);
|
|
|
|
int city_id = (int)Util.getIntLong(data, 0);
|
|
String region = new String(data, 4, data.length - 4, "UTF-8");
|
|
|
|
return new DataBlock(city_id, region, dataPtr);
|
|
}
|
|
|
|
/**
|
|
* get the region throught the ip address with binary search algorithm
|
|
*
|
|
* @param ip
|
|
* @return DataBlock
|
|
* @throws IOException
|
|
*/
|
|
public DataBlock binarySearch( String ip ) throws IOException
|
|
{
|
|
return binarySearch(Util.ip2long(ip));
|
|
}
|
|
|
|
/**
|
|
* get the db config
|
|
*
|
|
* @return DbConfig
|
|
*/
|
|
public DbConfig getDbConfig()
|
|
{
|
|
return dbConfig;
|
|
}
|
|
|
|
/**
|
|
* close the db
|
|
*
|
|
* @throws IOException
|
|
*/
|
|
public void close() throws IOException
|
|
{
|
|
HeaderSip = null; //let gc do its work
|
|
HeaderPtr = null;
|
|
dbBinStr = null;
|
|
raf.close();
|
|
}
|
|
|
|
}
|