最近有需求须要解析ipv6地址,因为数据量有点大,因此不可能去请求先用的http查询接口,这样子确定严重影响使用,因此在网上搜索的时候找到了zxinc
这个网站,提供离线文件进行查询,而后就下载了最新的ipv6
离线数据,里边也有一些编程语言(python,php,c++)的读取实现,却没有我要的JAVA的实现,因而参考python的实现,本身实现了一下读取数据的java版本。在这里分享出来供你们使用,若是代码有什么问题或者能够优化的地方,还请你们指正。php
首先咱们先介绍一下ipv6地址的组成:IPv6 地址是128位的,就是说ipv6地址总共由128个0或1组成,分红8块(block),每块16位。每块能够表示成四个16进制数,用冒号“:”隔开。
ipv6地址的书写有两个规则,以FF01:0000:3EE8:DFE1:0063:0000:0000:F001
为例,咱们说明这两个规则:html
FF01:0000:3EE8:DFE1:63:0000:0000:F001
FF01:0:3EE8:DFE1:63::F001
在实现代码之前要先熟悉一下BigInteger这个类,如字面意思大整形
,在java中,int类型占4个字节,long类型占8个字节,每一个字节8位,因此int所能表达的最大值为2^(48-1) - 1=2147483647,而long类型所能表达的最大值为2^(88-1) - 1 = 9223372036854775807,而咱们在进行ipv6解析的时候,常常要对long类型进行左移运算
,这个时候就有可能致使位移后的数超出了long类型所能表达的范围,这时候结果会以负数形式展现,这个结果天然就不是咱们想要的了,因此在ipv6解析的时候咱们要使用BigInteger这个类来进行位运算操做,下边列举一下BigInteger的位运算相关方法,具体的BigInteger请参考这篇文章:Java 大数高精度函数(BigInteger)java
BigInteger n = new BigInteger( String ); BigInteger m = new BigInteger( String );
方法 | 等同操做 | 说明 |
---|---|---|
n.shiftLeft(k) | n << k | 左移 |
n.shiftRight(k) | n >> k | 右移 |
n.and(m) | n & m | 且 |
n.or(m) | n l m | 或 |
ipv6转long类型,直接参考zxinc
中的python代码编写,替换了其中可能出现运算溢出的状况python
public BigInteger ipv6ToNum(String ipStr) { // String ipStr = "2400:3200::1"; // 最多被冒号分隔为8段 int ipCount = 8; List<String> parts = new ArrayList<>(Arrays.asList(ipStr.split(":"))); // 最少也至少为三段,如:`::1` if (parts.size() < 3) { System.out.println("error ip address"); } String last = parts.get(parts.size() - 1); if (last.contains(".")) { long l = ipv4ToNum(last); parts.remove(parts.size() - 1); // (l >> 16) & 0xFFFF; parts.add(new BigInteger(((l >> 16) & 0xFFFF) + "").toString(16)); parts.add(new BigInteger((l & 0xFFFF) + "").toString(16)); } int emptyIndex = -1; for (int i = 0; i < parts.size(); i++) { if (StringUtils.isEmpty(parts.get(i))) { emptyIndex = i; } } int parts_hi, parts_lo, parts_skipped; if (emptyIndex > -1) { parts_hi = emptyIndex; parts_lo = parts.size() - parts_hi - 1; if (StringUtils.isEmpty(parts.get(0))) { parts_hi -= 1 ; if (parts_hi > 0) { System.out.println("error ip address"); } } if (StringUtils.isEmpty(parts.get(parts.size() - 1))) { parts_lo -= 1; if (parts_lo > 0) { System.out.println("error ip address"); } } parts_skipped = ipCount - parts_hi - parts_lo; if (parts_skipped < 1) { System.out.println("error ip address"); } } else { // 彻底地址 if (parts.size() != ipCount) { System.out.println("error ip address"); } parts_hi = parts.size(); parts_lo = 0; parts_skipped = 0; } BigInteger ipNum = new BigInteger("0"); if (parts_hi > 0) { for (int i = 0; i < parts_hi; i++) { ipNum = ipNum.shiftLeft(16); String part = parts.get(i); if (part.length() > 4) { System.out.println("error ip address"); } BigInteger bigInteger = new BigInteger(part, 16); int i1 = bigInteger.intValue(); if (i1 > 0xFFFF) { System.out.println("error ip address"); } ipNum = ipNum.or(bigInteger); } } ipNum = ipNum.shiftLeft(16 * parts_skipped); for (int i = -parts_lo; i < 0; i++) { // ipNum <<= 16; ipNum = ipNum.shiftLeft(16); String part = parts.get(parts.size() + i); if (part.length() > 4) { System.out.println("error ip address"); } BigInteger bigInteger = new BigInteger(part, 16); int i1 = bigInteger.intValue(); if (i1 > 0xFFFF) { System.out.println("error ip address"); } // ipNum |= i1; ipNum = ipNum.or(bigInteger); } System.out.println(ipNum); return ipNum; }
在处理ipv4的时候,咱们一般把byte[]转为long返回,可是在处理ipv6的时候则不能处理为long类型,由于可能存在溢出的状况,因此使用咱们前边介绍的BigInteger进行处理。c++
private BigInteger byteArrayToBigInteger(byte[] b) { BigInteger ret = new BigInteger("0"); // 循环读取每一个字节经过移位运算完成long的8个字节拼装 for(int i = 0; i < b.length; i++){ // value |=((long)0xff << shift) & ((long)b[i] << shift); int shift = i << 3; BigInteger shiftY = new BigInteger("ff", 16); BigInteger data = new BigInteger(b[i] + ""); ret = ret.or(shiftY.shiftLeft(shift).and(data.shiftLeft(shift))); } return ret; }
就是这里注释的语句把0xff
转成long类型,致使数溢出,在这里坑了我很久。最好所有都用BigInteger进行处理。
这里的byte数组转BigInteger的操做参考:java:bytes[]转long的三种方式文章中的第一种方法。
这里的byteArrayToBigInteger
等效与下边的代码,写法上简单一点,而下边这个方法用long去做为最后的结果,就会有溢出的风险:spring
//这是将二进制转化成long类型的方法 public static long getLongAt(byte[] buffer,int offset) { long value = 0; //第一个value和上面的long类型转化成二进制对应起来, //先将第一个取出来的左移64位与FF000000相与就是这八位,再相或就是原来的前八位 value |= buffer[offset + 0] << 56 & 0xFF00000000000000L; value |= buffer[offset + 1] << 48 & 0x00FF000000000000L; value |= buffer[offset + 2] << 40 & 0x0000FF0000000000L; value |= buffer[offset + 3] << 32 & 0x000000FF00000000L; value |= buffer[offset + 4] << 24 & 0x00000000FF000000L; value |= buffer[offset + 5] << 16 & 0x0000000000FF0000L; value |= buffer[offset + 6] << 8 & 0x0000000000000FF0L; value |= buffer[offset + 7] & 0x00000000000000FFL; return value; }
大概须要注意的就这些地方,下边贴上完整代码:数据库
import java.io.IOException; import java.io.RandomAccessFile; import java.io.UnsupportedEncodingException; import java.math.BigInteger; /** * ip库来源:http://ip.zxinc.org/ * 如下全部数据类型(short/int/int64/IP地址/偏移地址等)均为小端序 * * 文件头 * 0~3 字符串 "IPDB" * 4-5 short 版本号,如今是2。版本号0x01到0xFF之间保证互相兼容。 * 6 byte 偏移地址长度(2~8) * 7 byte IP地址长度(4或8或12或16, 如今只支持4(ipv4)和8(ipv6)) * 8~15 int64 记录数 * 16-23 int64 索引区第一条记录的偏移 * 24 byte 地址字段数(1~255)[版本咕咕咕新增,现阶段默认为2] * 25-31 reserve 保留,用00填充 * 32~39 int64 数据库版本字符串的偏移[版本2新增,版本1没有] * * 记录区 * array 字符串[地址字段数] * 与qqwry.dat大体相同,可是没有结束IP地址 * 01开头的废弃不用 * 02+偏移地址[偏移长度]表示重定向 * 20~FF开头的为正常的字符串,采用UTF-8编码,以NULL结尾 * * 索引区 * struct{ * IP[IP地址长度] 开始IP地址 * 偏移[偏移长度] 记录偏移地址 * }索引[记录数]; * */ public class Ipv6Service { private String file = "D:\\project\\idea\\spring-boot-demo\\ipv6wry.db"; //单一模式实例 private static Ipv6Service instance = new Ipv6Service(); // private RandomAccessFile randomAccessFile = null; private byte[] v6Data; // 偏移地址长度 private int offsetLen; // 索引区第一条记录的偏移 private long firstIndex; // 总记录数 private long indexCount; public long getIndexCount() { return indexCount; } private Ipv6Service() { try(RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) { v6Data = new byte[(int) randomAccessFile.length()]; randomAccessFile.readFully(v6Data, 0, v6Data.length); } catch (IOException e) { System.out.println("读取文件失败!"); } // 获取偏移地址长度 byte[] bytes = readBytes(6, 1); offsetLen = bytes[0]; // 索引区第一条记录的偏移 bytes = readBytes(16, 8); BigInteger firstIndexBig = byteArrayToBigInteger(bytes); firstIndex = firstIndexBig.longValue(); // 570063 System.out.println("索引区第一条记录的偏移:" + firstIndex); // 总记录数 bytes = readBytes(8, 8); BigInteger indexCountBig = byteArrayToBigInteger(bytes); indexCount = indexCountBig.longValue(); } /** * @return 单一实例 */ public static Ipv6Service getInstance() { return instance; } private byte[] readBytes(long offset, int num) { byte[] ret = new byte[num]; for(int i=0; i < num; i++) { ret[i] = v6Data[(int) (offset + i)]; } return ret; } /** * 对little-endian字节序进行了转换 * byte[]转换为long * @param b * @return ret */ private BigInteger byteArrayToBigInteger(byte[] b) { BigInteger ret = new BigInteger("0"); // 循环读取每一个字节经过移位运算完成long的8个字节拼装 for(int i = 0; i < b.length; i++){ // value |=((long)0xff << shift) & ((long)b[i] << shift); int shift = i << 3; BigInteger shiftY = new BigInteger("ff", 16); BigInteger data = new BigInteger(b[i] + ""); ret = ret.or(shiftY.shiftLeft(shift).and(data.shiftLeft(shift))); } return ret; } /** * 二分查找 * @param ip * @param l * @param r * @return */ public long find (BigInteger ip, long l, long r){ if (r - l <= 1) return l; long m = (l + r) >>> 1; long o = firstIndex + m * (8 + offsetLen); byte[] bytes = readBytes(o, 8); BigInteger new_ip = byteArrayToBigInteger(bytes); if (ip.compareTo(new_ip) == -1) { return find(ip, l, m); } else { return find(ip, m, r); } } public static long ipv4ToNum(String ipStr) { long result = 0; String[] split = ipStr.split("\\."); if (split.length != 4) { System.out.println("error ip address"); } for (int i = 0; i < split.length; i++) { int s = Integer.valueOf(split[i]); if (s > 255) { System.out.println("error ip address"); } result = (result << 8) | s; } return result; } // ipv6ToNum方法在文章上边已贴出代码,这里为了减小代码,去掉 public BigInteger ipv6ToNum(String ipStr) { BigInteger ipNum = new BigInteger("0"); return ipNum; } public long getIpOff(long findIp) { return firstIndex + findIp * (8 + offsetLen); } public long getIpRecOff(long ip_off) { byte[] bytes = readBytes(ip_off + 8, offsetLen); BigInteger ip_rec_off_big = byteArrayToBigInteger(bytes); return ip_rec_off_big.longValue(); } public String getAddr(long offset) { byte[] bytes = readBytes(offset, 1); int num = bytes[0]; if (num == 1) { // 重定向模式1 // [IP][0x01][国家和地区信息的绝对偏移地址] // 使用接下来的3字节做为偏移量调用字节取得信息 bytes = readBytes(offset + 1, offsetLen); BigInteger l = byteArrayToBigInteger(bytes); return getAddr(l.longValue()); } else { // 重定向模式2 + 正常模式 // [IP][0x02][信息的绝对偏移][...] String cArea = getAreaAddr(offset); if (num == 2) { offset += 1 + offsetLen; } else { offset = findEnd(offset) + 1; } String aArea = getAreaAddr(offset); return cArea + "|" + aArea; } } private String getAreaAddr(long offset){ // 经过给出偏移值,取得区域信息字符串 byte[] bytes = readBytes(offset, 1); int num = bytes[0]; if (num == 1 || num == 2) { bytes = readBytes(offset + 1, offsetLen); BigInteger p = byteArrayToBigInteger(bytes); return getAreaAddr(p.longValue()); } else { return getString(offset); } } private String getString(long offset) { long o2 = findEnd(offset); // 有可能只有国家信息没有地区信息, byte[] bytes = readBytes(offset, Long.valueOf(o2 - offset).intValue()); try { return new String(bytes, "utf8"); } catch (UnsupportedEncodingException e) { return "未知数据"; } } private long findEnd(long offset) { int i = Long.valueOf(offset).intValue(); for (; i < v6Data.length; i++) { byte[] bytes = readBytes(i, 1); if ("\0".equals(new String(bytes))) { break; } } return i; } public static void main(String[] args) { Ipv6Service ipv6Service = Ipv6Service.getInstance(); BigInteger ipNum = ipv6Service.ipv6ToNum("2409:8754:2:1::d24c:4b55"); BigInteger ip = ipNum.shiftRight(64).and(new BigInteger("FFFFFFFFFFFFFFFF", 16)); // 查找ip的索引偏移 long findIp = ipv6Service.find(ip, 0, ipv6Service.getIndexCount()); // 获得索引记录 long ip_off = ipv6Service.getIpOff(findIp); long ip_rec_off = ipv6Service.getIpRecOff(ip_off); String addr = ipv6Service.getAddr(ip_rec_off); System.out.println(addr); } }
上边是完整的代码,整个代码参考zxinc网站给出的python示例代码编写。编程
因为对二进制的位运算不熟悉,写这个java代码也算花费了很多心血。了解ipv6地址的组成形式;熟悉二进制的位运算;而后到掉进long类型溢出的坑里,一步一步走过来,最终也算是实现了查找功能,收获也颇丰。代码中若是有什么不对的地方欢迎你们指出,共同交流,或者其中的方法有什么能够优化的,也能够提出,谢谢你们。数组