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