417 lines
12 KiB
Java
417 lines
12 KiB
Java
package com.tiesheng.util.ip2region;
|
|
|
|
import cn.hutool.extra.spring.SpringUtil;
|
|
import com.tiesheng.util.config.Ip2regionConfig;
|
|
|
|
import java.io.IOException;
|
|
import java.io.RandomAccessFile;
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
/**
|
|
* ip db searcher class (Not thread safe)
|
|
*
|
|
* @author chenxin<chenxin619315 @ gmail.com>
|
|
*/
|
|
public class Ip2Region {
|
|
|
|
|
|
private static Ip2Region ip2Region;
|
|
/**
|
|
* db config
|
|
*/
|
|
private final DbConfig dbConfig;
|
|
/**
|
|
* 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
|
|
*/
|
|
public Ip2Region() {
|
|
this.dbConfig = new DbConfig();
|
|
try {
|
|
Ip2regionConfig ip2regionConfig = SpringUtil.getBean(Ip2regionConfig.class);
|
|
raf = ip2regionConfig.getDbAccessFile();
|
|
} catch (Exception ignored) {
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取单例
|
|
*
|
|
* @return
|
|
*/
|
|
public static Ip2Region getInstance() {
|
|
if (ip2Region == null) {
|
|
synchronized (Ip2Region.class) {
|
|
if (ip2Region == null) {
|
|
ip2Region = new Ip2Region();
|
|
}
|
|
}
|
|
}
|
|
return ip2Region;
|
|
}
|
|
|
|
/**
|
|
* get the region with an 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 = IpUtil.getIntLong(dbBinStr, 0);
|
|
lastIndexPtr = IpUtil.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 = IpUtil.getIntLong(dbBinStr, p);
|
|
if (ip < sip) {
|
|
h = m - 1;
|
|
} else {
|
|
eip = IpUtil.getIntLong(dbBinStr, p + 4);
|
|
if (ip > eip) {
|
|
l = m + 1;
|
|
} else {
|
|
dataptr = IpUtil.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 cityId = (int) IpUtil.getIntLong(dbBinStr, dataPtr);
|
|
String region = new String(dbBinStr, dataPtr + 4, dataLen - 4, StandardCharsets.UTF_8);
|
|
|
|
return new DataBlock(cityId, region, dataPtr);
|
|
}
|
|
|
|
/**
|
|
* get the region throught the ip address with memory binary search algorithm
|
|
*
|
|
* @return DataBlock
|
|
* @throws IOException
|
|
*/
|
|
public DataBlock memorySearch() throws IOException {
|
|
return memorySearch(null);
|
|
}
|
|
|
|
/**
|
|
* 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(IpUtil.ip2long(ip));
|
|
}
|
|
|
|
|
|
/**
|
|
* get by index ptr
|
|
*
|
|
* @param ptr
|
|
* @throws IOException
|
|
*/
|
|
public DataBlock getByIndexPtr(long ptr) throws IOException {
|
|
raf.seek(ptr);
|
|
byte[] buffer = new byte[12];
|
|
raf.readFully(buffer, 0, buffer.length);
|
|
long extra = IpUtil.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 cityId = (int) IpUtil.getIntLong(data, 0);
|
|
String region = new String(data, 4, data.length - 4, StandardCharsets.UTF_8);
|
|
|
|
return new DataBlock(cityId, region, dataPtr);
|
|
}
|
|
|
|
/**
|
|
* get the region with an 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);
|
|
byte[] b = new byte[dbConfig.getTotalHeaderSize()];
|
|
raf.readFully(b, 0, b.length);
|
|
|
|
//fill the header
|
|
int len = b.length >> 3, idx = 0;
|
|
headerSip = new long[len];
|
|
headerPtr = new int[len];
|
|
long startIp, dataPtr;
|
|
for (int i = 0; i < b.length; i += 8) {
|
|
startIp = IpUtil.getIntLong(b, i);
|
|
dataPtr = IpUtil.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();
|
|
//include the right border block
|
|
byte[] iBuffer = new byte[blockLen + blen];
|
|
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 = IpUtil.getIntLong(iBuffer, p);
|
|
if (ip < sip) {
|
|
h = m - 1;
|
|
} else {
|
|
eip = IpUtil.getIntLong(iBuffer, p + 4);
|
|
if (ip > eip) {
|
|
l = m + 1;
|
|
} else {
|
|
dataptr = IpUtil.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 cityId = (int) IpUtil.getIntLong(data, 0);
|
|
String region = new String(data, 4, data.length - 4, StandardCharsets.UTF_8);
|
|
|
|
return new DataBlock(cityId, 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) {
|
|
try {
|
|
return btreeSearch(IpUtil.ip2long(ip));
|
|
} catch (IOException ignored) {
|
|
}
|
|
return new DataBlock(0, "未知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 = IpUtil.getIntLong(superBytes, 0);
|
|
lastIndexPtr = IpUtil.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);
|
|
raf.readFully(buffer, 0, buffer.length);
|
|
sip = IpUtil.getIntLong(buffer, 0);
|
|
if (ip < sip) {
|
|
h = m - 1;
|
|
} else {
|
|
eip = IpUtil.getIntLong(buffer, 4);
|
|
if (ip > eip) {
|
|
l = m + 1;
|
|
} else {
|
|
dataptr = IpUtil.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 cityId = (int) IpUtil.getIntLong(data, 0);
|
|
String region = new String(data, 4, data.length - 4, StandardCharsets.UTF_8);
|
|
|
|
return new DataBlock(cityId, 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(IpUtil.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;
|
|
headerPtr = null;
|
|
dbBinStr = null;
|
|
if (raf != null) {
|
|
raf.close();
|
|
}
|
|
}
|
|
|
|
}
|