Files
tiesheng-springboot/springboot-util/src/main/java/com/tiesheng/util/ip2region/Ip2Region.java
2023-01-11 11:21:01 +08:00

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();
}
}
}