IP 地址存储方案:IPv4 与 IPv6 的高效处理方式
在开发中,IP 地址(尤其是用户访问日志、设备管理场景)需要高效存储和查询。直接存储字符串形式的 IP 不仅占用空间大,还不利于范围查询(如判断某个 IP 是否在指定网段内)。本文将详细讲解 IPv4 和 IPv6 地址的数字化存储方案,包括转换原理、代码实现及应用场景。
IPv4 地址的存储:使用 long 类型
IPv4 地址由 4 个 0-255 的整数组成(如 192.168.1.1),总长度为 32 位(4×8 位),刚好可以用一个 64 位的 long 类型存储。
转换原理
将 IPv4 地址的 4 个分段视为 32 位二进制数的 4 个字节,按从左到右的顺序拼接后转换为十进制整数。例如:
- IP 地址
192.168.1.1 可拆分为 192、168、1、1;
- 转换为二进制:
11000000.10101000.00000001.00000001;
- 拼接后为 32 位二进制数,转换为十进制即
3232235777。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public class IPv4Converter {
public static long ipToLong(String ipString) { String[] segments = ipString.split("\\."); if (segments.length != 4) { return -1; }
long result = 0; try { result += Long.parseLong(segments[0]) << 24; result += Long.parseLong(segments[1]) << 16; result += Long.parseLong(segments[2]) << 8; result += Long.parseLong(segments[3]); return result; } catch (NumberFormatException e) { return -1; } }
public static String longToIp(long ipLong) { StringBuilder ip = new StringBuilder(); ip.append((ipLong >>> 24)).append("."); ip.append((ipLong >>> 16) & 0xFF).append("."); ip.append((ipLong >>> 8) & 0xFF).append("."); ip.append(ipLong & 0xFF); return ip.toString(); }
public static void main(String[] args) { String ip = "255.255.255.255"; long ipLong = ipToLong(ip); System.out.println(ipLong); System.out.println(longToIp(ipLong)); } }
|
优势与应用
- 节省空间:
long 类型仅占 8 字节,比字符串形式(如 "192.168.1.1" 占 11 字节)更节省存储;
- 高效查询:支持范围查询(如判断 IP 是否在
192.168.1.0/24 网段内,可通过比较 long 值范围实现);
- 数据库优化:在 MySQL 中可直接用
BIGINT 类型存储,支持索引加速查询。
IPv6 地址的存储:使用 BigInteger
IPv6 地址长度为 128 位(16 个字节),远超 long 类型的 64 位,因此需要用 BigInteger 存储(支持任意长度整数)。
IPv6 地址的特点
- 格式:由 8 个 16 进制分段组成,中间用冒号分隔(如
2001:0db8:85a3:0000:0000:8a2e:0370:7334);
- 压缩规则:连续的零分段可省略为
::(如 2001:db8::8a2e:370:7334);
- 长度:128 位,可表示的地址数量约为 3.4×10³⁸,远超 IPv4 的 43 亿。
转换原理
将 IPv6 地址的 8 个 16 进制分段转换为 128 位二进制数,再转换为 BigInteger。例如:
- 分段
2001:0db8:85a3:0000:0000:8a2e:0370:7334 转换为 16 进制字符串 20010db885a3000000008a2e03707334;
- 将 16 进制字符串转换为
BigInteger。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| import java.math.BigInteger; import java.net.InetAddress; import java.net.UnknownHostException; import org.slf4j.Logger; import org.slf4j.LoggerFactory;
public class IPv6Converter { private static final Logger LOGGER = LoggerFactory.getLogger(IPv6Converter.class);
public static BigInteger ip6ToNumber(String ipv6Address) { try { InetAddress inetAddress = InetAddress.getByName(ipv6Address); byte[] bytes = inetAddress.getAddress();
return new BigInteger(1, bytes); } catch (UnknownHostException e) { LOGGER.error("IPv6 转换失败:{}", ipv6Address, e); return BigInteger.ZERO; } }
public static String numberToIp6(BigInteger number) { try { byte[] bytes = number.toByteArray(); byte[] ipv6Bytes = new byte[16];
int copyLength = Math.min(bytes.length, 16); System.arraycopy( bytes, bytes.length - copyLength, ipv6Bytes, 16 - copyLength, copyLength );
return InetAddress.getByAddress(ipv6Bytes).getHostAddress(); } catch (UnknownHostException e) { LOGGER.error("BigInteger 转换为 IPv6 失败:{}", number, e); return ""; } }
public static void main(String[] args) { String ipv6 = "2001:db8::1"; BigInteger number = ip6ToNumber(ipv6); System.out.println(number); System.out.println(numberToIp6(number)); } }
|
关键细节
- 处理压缩格式:
InetAddress.getByName() 会自动解析 :: 压缩格式,无需手动处理;
- 字节数组转换:
BigInteger 的 toByteArray() 可能包含符号位(首位为 0 时),需通过 new BigInteger(1, bytes) 确保正数;
- 性能考量:
BigInteger 操作比 long 稍慢,但对于 IPv6 场景是唯一可行的方案。
存储方案对比与建议
| 类型 |
地址长度 |
存储类型 |
占用空间 |
适用场景 |
| IPv4 |
32 位 |
long |
8 字节 |
传统网络设备、多数服务器 |
| IPv6 |
128 位 |
BigInteger |
16-20 字节 |
物联网、新网络协议场景 |
最佳实践
ipv4
ipv4使用long类型进行存储即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public static long ipToLong(String ipString) throws NumberFormatException { long result = 0; String[] terms = ipString.split("\\."); if (terms.length != 4) { return -1; } result += Long.parseLong(terms[0]) << 24; result += Long.parseLong(terms[1]) << 16; result += Long.parseLong(terms[2]) << 8; result += Long.parseLong(terms[3]); return result; }
public static String longToIp(long ipLong) { return (ipLong >>> 24) + "." + ((ipLong >>> 16) & 0xFF) + "." + ((ipLong >>> 8) & 0xFF) + "." + (ipLong & 0xFF); }
public static void main(String[] args) { long result = ipToLong("255.255.255.255"); System.out.println(result); System.out.println(longToIp(result)); }
|
ipv6
ipv6使用long类型是存储不了的,需要使用BigInteger
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public static BigInteger ip6ToNumber(String ipv6Address) throws NumberFormatException { try{ InetAddress inetAddress = InetAddress.getByName(ipv6Address); String hostAddress = inetAddress.getHostAddress();
StringBuilder decimalString = new StringBuilder(); String[] split = hostAddress.split(":"); for(String s : split){ int i = Integer.parseInt(s, 16); decimalString.append(String.format("%05d",i)); }
return new BigInteger(decimalString.toString()); } catch (UnknownHostException exception){ LOGGER.error("ipv6转换失败 {}", ipv6Address, exception); return BigInteger.ZERO; }
}
|