面试题:我有一批IPv6地址,你帮我想个办法来存储?
前语:微信改版后,大量读者留言说,找不到我们的公众号,在此建议大家“置顶”本公众号。如文章写得好,望大家阅读后在右下边“在看”处点个赞,以示鼓励!
本文由群里的绪扬同学投稿,在此,也欢迎更多的同学来投稿。
之前写了一篇《面试题:请用代码实现ip地址与int之间互换?》,有读者评论问到 IPv6 的转换方法,于是抽时间也自己实现了一下。
面试官:我有一批IPv6地址,你帮我想个办法来存储?
我:啊。。。
面试官:……嗯。好的。回去等通知吧。
# 什么是IPv6?
IPv6是英文“Internet Protocol Version 6”(互联网协议第6版)的缩写,是互联网工程任务组(IETF)设计的用于替代IPv4的下一代IP协议,其地址数量号称可以为全世界的每一粒沙子编上一个地址。IPv6的地址长度为128位,它有3种表示方法,分别是冒分十六进制表示法、0位压缩表示法、内嵌IPv4地址表示法。
# 思考
首先,IPv6 的地址长度为 128 位,而 Java 中没有 128 位的原生数字,int 为 32 位,long 是 64 位,因此若要将 IPv6 地址直接转为 long, 则会丢掉一半的信息,这肯定是不能接受的。
因此,解决方式有两种思路。第一,使用 BigInteger;第二,将 IPv6 地址的 128 位拆分为两个 64 位的地址,即可存到两个 long 整数组成的数组中。本文采用后者,即将 IPv6 地址转换为 long 数组。
# 实现篇
另外,为简便起见,我们只考虑冒分十六进制表示法的情况,即完整的ip地址,如 0:0:0:0:0:0:0:0,0位压缩表示法和内嵌 IPv4 地址表示法暂不考虑。
将IPv6地址转为long数组,代码如下。
/**
* 将 IPv6 地址转为 long 数组,只支持冒分十六进制表示法
*/
public static long[] ip2Longs(String ipString) {
if (ipString == null || ipString.isEmpty()) {
throw new IllegalArgumentException("ipString cannot be null.");
}
String[] ipSlices = ipString.split(":");
if (ipSlices.length != 8) {
throw new IllegalArgumentException(ipString + " is not an ipv6 address.");
}
long[] ipv6 = new long[2];
for (int i = 0; i < 8; i++) {
String slice = ipSlices[i];
// 以 16 进制解析
long num = Long.parseLong(slice, 16);
// 每组 16 位
long right = num << (16 * i);
// 每个 long 保存四组,i >> 2 = i / 4
ipv6[i >> 2] |= right;
}
return ipv6;
}
将long数组转为IPv6地址,代码如下。
/**
* 将 long 数组转为冒分十六进制表示法的 IPv6 地址
*/
public static String longs2Ip(long[] numbers) {
if (numbers == null || numbers.length != 2) {
throw new IllegalArgumentException(Arrays.toString(numbers) + " is not an IPv6 address.");
}
StringBuilder sb = new StringBuilder(32);
for (long numSlice : numbers) {
// 每个 long 保存四组
for (int j = 0; j < 4; j++) {
// 取最后 16 位
long current = numSlice & 0xFFFF;
sb.append(Long.toString(current, 16)).append(":");
// 右移 16 位,即去除掉已经处理过的 16 位
numSlice >>= 16;
}
}
// 去掉最后的 :
return sb.substring(0, sb.length() - 1);
}
小试牛刀。
public static void main(String[] args) {
String[] ips4Test = new String[]{"FFFF:FFFF:7654:FEDA:1245:BA98:3210:4562",
"FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", "7654:0:FFFF:7654:562:222:7622:0", "0:0:0:0:0:0:0:0"};
for (String testCase : ips4Test) {
test(testCase);
}
}
private static void test(String testCase) {
long[] ipv6 = ip2Longs(testCase);
String ipString = longs2Ip(ipv6);
boolean eq = testCase.equalsIgnoreCase(ipString);
System.out.println("本次测试 ipv6 地址: " + testCase + ", 转为 long 数组: " + Arrays.toString(ipv6)
+ ", 再转回 ipv6 字符串: " + ipString + ", 是否与原字符串相等: " + eq);
if (!eq) {
throw new IllegalStateException("本次测试未通过!testCase: " + testCase);
}
}
输出结果如下所示。
本次测试 ipv6 地址: FFFF:FFFF:7654:FEDA:1245:BA98:3210:4562, 转为 long 数组: [-82623535708635137, 4999613583766065733], 再转回 ipv6 字符串: ffff:ffff:7654:feda:1245:ba98:3210:4562, 是否与原字符串相等: true
本次测试 ipv6 地址: FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF, 转为 long 数组: [-1, -1], 再转回 ipv6 字符串: ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff, 是否与原字符串相等: true
本次测试 ipv6 地址: 7654:0:FFFF:7654:562:222:7622:0, 转为 long 数组: [8526721465200965204, 129888436749666], 再转回 ipv6 字符串: 7654:0:ffff:7654:562:222:7622:0, 是否与原字符串相等: true
本次测试 ipv6 地址: 0:0:0:0:0:0:0:0, 转为 long 数组: [0, 0], 再转回 ipv6 字符串: 0:0:0:0:0:0:0:0, 是否与原字符串相等: true
好了,我在这里抛砖引玉了,实现了IPv6的转换,相信聪明的你一定知道接下来该怎么存储这个long数组了。
其实,现在很多数据库,都内置了专门的函数来转换IP地址。比如从mysql5.6开始,可以直接使用inet6_aton()函数来转换,见下图。
总之,直接保存字符串,虽然可读性最好,但浪费了不少的存储空间;转换后再存储,虽然节约了存储空间,但可读性较差。该如何取舍,还是根据具体的应用场景来决定。
如果你有更好的方案,欢迎在留言区一起探讨。
如果你觉得写得不错,建议给原作者赞赏一下,以示鼓励。
---END---
热文推荐