查看原文
其他

面试题:我有一批IPv6地址,你帮我想个办法来存储?

dadiyang Java面试那些事儿 2019-12-19
前语:微信改版后,大量读者留言说,找不到我们的公众号,在此建议大家“置顶”本公众号。如文章写得好,望大家阅读后在右下边“在看”处点个赞,以示鼓励!


本文由群里的绪扬同学投稿,在此,也欢迎更多的同学来投稿。


之前写了一篇《面试题:请用代码实现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---



热文推荐

面试题:请用代码实现ip地址与int之间互换?

面试题:方法重载的底层原理?

面试题:jdk那些类的底层实现使用过位运算,并且给你印象最深?

推荐:群里同学分享的Java面试资料。


给个在看

人气满满

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存