算法应用【二分查找】100 万 IP 地址中快速定位
给你 100万IP 地址数据,如何快速定位指定的 IP 地址呢?
数据格式如下:
223.255.36.0 223.255.36.255 中国 北京 北京 156001000000
223.255.37.0 223.255.37.255 中国 吉林 长春 156021000001
223.255.38.0 223.255.41.255 中国 北京 北京 156001000000
223.255.42.0 223.255.43.255 中国 湖北 武汉 156013000001
223.255.44.0 223.255.127.255 中国 北京 北京 156001000000
223.255.128.0 223.255.191.255 中国 香港 * 344033000000
223.255.192.0 223.255.202.255 韩国 韩国 * 410000000000
223.255.203.0 223.255.203.255 日本 日本 * 392000000000
223.255.204.0 223.255.205.255 韩国 韩国 * 410000000000
223.255.206.0 223.255.206.255 日本 日本 * 392000000000
数据分为三段:
IPv4编码:一共两段,分别为起始 IP 和终止 IP。
地理位置:一共三个字段,同 citycode.txt 的地理位置。
位置编码:一个字段,12 位数字代码,同 citycode.txt 里的位置编码。
字段数据之间均使用制表符分隔,请在文本编辑器里设置制表符可见即可。
IP地址库下载地址:IP地址库(100w条数据,含国内外)
思路解析:
如果IP区间和归属地的对应关系不经常更新,那么我们可以预先处理这100万数据,让其按照从小到大进行。将IP地址转化为Long长整型即可。IP库里面的数据是根据ip地址从高到低排序,为了严谨还是对数据进行了排序。现在我们知道所有地区 IP 地址的最大值和最小值,运用二分查找处理100万数据只需耗时1ms。
- 读取ipdb.txt文件,把数据存到List中
- 对List内数据排序后把List转化成数组
- 根据ip地址用二分查找所属的ip地址段
代码实现
@Data
@NoArgsConstructor
@AllArgsConstructor
public class IPBean {
//起始 IP
private long begin;
//终止 IP
private long end;
//国家
private String country;
//省份
private String region;
//城市
private String city;
//位置编码
private String code;
}
public class IPUtil {
/**
* 根据ip地址判断属于哪个ip地址段,返回这个段的ip地址对象
* 使用二分查找算法来实现
* @param ip
* @return
*/
public static IPBean getIPByHalf(String ip) {
IPBean[] ipBeans = IpAnalyse.ipBeans;
if (ipBeans == null || ipBeans.length == 0) {
return null;
}
long iplong = IPUtil.ipToLong(ip);
if (iplong < ipBeans[0].getBegin() || iplong > ipBeans[ipBeans.length - 1].getEnd()) {
return null;
}
int low = 0;
int high = ipBeans.length - 1;
//不能使用 int mid = (left + right) / 2,如果 low 和 high 比较大的话,两者之和就有可能会溢出
int mid = low + (( high - low ) >>> 1);
//当两个指针同时指向同一个数据时,需用 = 判断
while (low <= high) {
if (iplong < ipBeans[mid].getBegin()) {
high = mid - 1;
} else if (iplong > ipBeans[mid].getBegin()) {
low = mid + 1;
}
if (iplong >= ipBeans[mid].getBegin() && iplong <= ipBeans[mid].getEnd()) {
return ipBeans[mid];
}
mid = (low + high) / 2;
}
return null;
}
/**
* ip地址转成long型数字
* 将IP地址转化成整数的方法如下:
* 1、通过String的split方法按.分隔得到4个长度的数组
* 2、通过左移位操作(<<)给每一段的数字加权,第一段的权为2的24次方,第二段的权为2的16次方,第三段的权为2的8次方,最后一段的权为1
* @param strIp
* @return
*/
public static long ipToLong(String strIp) {
String[] ip = strIp.split("\\.");
return (Long.parseLong(ip[0]) << 24) + (Long.parseLong(ip[1]) << 16) + (Long.parseLong(ip[2]) << 8) + Long.parseLong(ip[3]);
}
/**
* 将十进制整数形式转换成127.0.0.1形式的ip地址
* 将整数形式的IP地址转化成字符串的方法如下:
* 1、将整数值进行右移位操作(>>>),右移24位,右移时高位补0,得到的数字即为第一段IP。
* 2、通过与操作符(&)将整数值的高8位设为0,再右移16位,得到的数字即为第二段IP。
* 3、通过与操作符吧整数值的高16位设为0,再右移8位,得到的数字即为第三段IP。
* 4、通过与操作符吧整数值的高24位设为0,得到的数字即为第四段IP。
* @param longIp
* @return
*/
public static String longToIP(long longIp) {
StringBuffer sb = new StringBuffer();
// 直接右移24位
sb.append((longIp >>> 24));
sb.append(".");
// 将高8位置0,然后右移16位
sb.append(((longIp & 0x00FFFFFF) >>> 16));
sb.append(".");
// 将高16位置0,然后右移8位
sb.append(((longIp & 0x0000FFFF) >>> 8));
sb.append(".");
// 将高24位置0
sb.append((longIp & 0x000000FF));
return sb.toString();
}
}
public class IpAnalyse {
private static Logger logger = Logger.getLogger(IpAnalyse.class);
public static IPBean[] ipBeans;
/**
* 读取ipdb.txt文件,按行读取,并转成IPBean对象数组
* @param filePath
* @throws IOException
*/
public static void load(String filePath) throws IOException {
logger.info("load ip db,path:" + filePath);
InputStreamReader inputStreamReader = null;
BufferedReader reader = null;
try {
File file = new File(filePath);
//判断文件是否存在
if(file.isFile() && file.exists()) {
inputStreamReader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8);
reader = new BufferedReader(inputStreamReader);
String line;
LinkedList<IPBean> ipBeanList = new LinkedList<>();
while ((line = reader.readLine()) != null) {
String[] tmp = line.split("\t");
IPBean ipBean = new IPBean(IPUtil.ipToLong(tmp[0]), IPUtil.ipToLong(tmp[1]), tmp[2], tmp[3], tmp[4], tmp[5]);
ipBeanList.add(ipBean);
}
Collections.sort(ipBeanList, Comparator.comparingLong(IPBean::getBegin));
ipBeans = ipBeanList.toArray(new IPBean[]{});
} else {
logger.info("找不到指定的文件");
}
} catch (Exception ex) {
logger.info("读取文件内容出错");
ex.printStackTrace();
} finally {
inputStreamReader.close();
reader.close();
}
}
public static IPBean find(String ip) {
return IPUtil.getIPByHalf(ip);
}
}
测试
public static void main(String[] args) {
try {
IpAnalyse.load("D:\\ipdb.txt");
System.out.println("查询前时间" + System.currentTimeMillis());
String ip = "223.255.204.100";
IPBean ipBean = find(ip);
System.out.println(ipBean);
System.out.println("查询后时间" + System.currentTimeMillis());
} catch (Exception e){
e.printStackTrace();
}
}
浙公网安备 33010602011771号