前言
为什么要将 IPv4 转数值存储?
关于这个问题有几个一般的原因:像整数比字符串比起来存储占用空间更少。
但也只是强调一个主要的原因:IP地址实际上就是数字!
我们一般更喜欢IP地址字符串表示(如:10.0.0.1、2002::2)以使我们读或者记更加的轻松。IPs上的大多数操作都是严格的数字操作,如屏蔽(ip&mask)、检查IP范围。正如我们将数字字符串转整数类型("123"->123)一样,IP这样的字符串也应可以转换为整数形式。
不过,就大多数情况下这个问题更多的会在面试中被问到~
所以我们就来探讨一下这个问题!
思路
在此之前我们需要知道 IPv4 的地址范围:0.0.0.0 ~ 255.255.255.255,存储这样的 IPv4 地址,其实只需要 4 个字节,也就是32位。
所以,所有的 IPv4 实际上都可以使用32位的整型来进行表示,如下:
00000000 00000000 00000000 00000000
这样的二进制最左边的 8bit 我们称之为最高位,次之的 8bit 可以称之为中高位,之后的分别是中低位以及低位。
所以,IPv4 这样的地址实际上都可以对应 32 位整形数值中的,我们只需要将 IPv4 中的四个数字分别提取出来分别对应着 32位 二进制的高 ~ 低位即可。
这又牵扯到了位运算:左移、右移以及或操作。
现在来说下具体思路:
现在我们有一个 IPv4 地址:172.168.5.1 ,要求我们将该 IP 转换为整形存储。
所以,首先我们要做的就是分别将四个数字提取出来,然后分别得到 32位 的二进制(不足32就需要在其左边进行补码)。
如,172 的二进制数值为:10101100,因此我们就需要将该二进制进行32位补码,得到如下结果:
00000000 00000000 00000000 10101100
同理,我们需要得到其余3个数字的为禁止32位补码,最后四个数字的32位补码如下:
00000000 00000000 00000000 10101100 => 17200000000 00000000 00000000 10101000 => 16800000000 00000000 00000000 00000101 => 500000000 00000000 00000000 00000001 => 1
现在我们要做的就是将这四个数字进行位操作:
- 将 172 放到最高位(左移24位):
10101100 00000000 00000000 00000000
- 将 168 放到次高位(左移16位):
00000000 10101000 00000000 00000000
- 将 5 放到次低位(左移8位):
00000000 00000000 00000101 00000000
- 将 1 放到低位(低位不需要动):
00000000 00000000 00000000 00000001
所以,最后得到的四个结果如下:
10101100 00000000 00000000 0000000000000000 10101000 00000000 0000000000000000 00000000 00000101 0000000000000000 00000000 00000000 00000001
现在我们要做的即使将这4个二进制的32位补码进行或(|)操作,得到最后的结果如下:
10101100 10101000 00000101 00000001
而这个数字就是我们最后得到的整型,转换为 10 进制值为:**2896692481**。
所以,IPv4 172.168.5.1 对应的整型就是 **2896692481**。
反之,要想还原为 IPv4 我们只需要将其反操作即可,如下:
long ipv4 = 2896692481L;String first = (ipv4 >> 24) & 0xFF;String second = (ipv4 >> 16) & 0xFF;String third = (ipv4 >> 8) & 0xFF;String fourth = ipv4 & 0xFF;
现在就来看下具体的 Java 代码实现!
Java 实现
import org.apache.commons.lang3.StringUtils;import java.math.BigInteger;import java.util.regex.Matcher;import java.util.regex.Pattern;public class IpUtils {private static final int MIN_IP_SEGMENT = 0;private static final int MAX_IP_SEGMENT = 255;/** 验证 IPv4 有消息 */private static final String IPV4_PATTERN = "^(\\s)*(\\d){1,3}(\\s)*\\.(\\s)*(\\d){1,3}(\\s)*\\.(\\s)*(\\d){1,3}(\\s)*\\.(\\s)*(\\d){1,3}(\\s)*$";public static BigInteger ipv4To32BitInt(String ipAddr) {if (!validateIpAddress(ipAddr)) {throw new IllegalArgumentException("Ip Address is not validated: " + ipAddr);}return BigInteger.valueOf(ipv4ToLong(ipAddr));}/*** 验证 IPv4 有效性** @param ipAddr IPv4*/private static boolean validateIpAddress(String ipAddr) {if (StringUtils.isBlank(ipAddr)) {return false;}Pattern pattern = Pattern.compile(IPV4_PATTERN);Matcher matcher = pattern.matcher(ipAddr);return matcher.matches();}/*** Ipv4 转整型 Long** @param ipAddr IPv4*/private static long ipv4ToLong(String ipAddr) {long result = 0;String[] ipSegmentArray = StringUtils.split(ipAddr, "\\.");for (int idx = 0; idx < ipSegmentArray.length; idx++) {result |= shiftedIpSegment(ipSegmentArray[idx], idx);}return result;}/*** IP段位运算** @param ipSegment IP段* @param idx IP段下标*/private static long shiftedIpSegment(String ipSegment, int idx) {long segment = formatIpSegment(ipSegment);switch (idx) {case 0:return (segment << 24) & 0xFF000000;case 1:return (segment << 16) & 0xFF0000;case 2:return (segment << 8) & 0xFF00;case 3:return segment & 0xFF;default:throw new ArrayIndexOutOfBoundsException("IPv4数组越界");}}/*** 验证IP段有效性** @param ipSegment IP 段*/private static long formatIpSegment(String ipSegment) {long formattedIpSegment = Long.parseLong(ipSegment.trim());if (formattedIpSegment < MIN_IP_SEGMENT || formattedIpSegment > MAX_IP_SEGMENT) {throw new IllegalArgumentException("Ip Address Segment Is Out Of Range: " + ipSegment);}return formattedIpSegment;}/*** Long 转 IPv4*/private static String long2Ipv4(long ipAddr) {String ipv4 = ((ipAddr & 0xFF000000) >> 24) + "." +((ipAddr & 0xFF0000) >> 16) + "." +((ipAddr & 0xFF00) >> 8) + "." +(ipAddr & 0xFF);validateIpAddress(ipv4);return ipv4;}private static String bitInt2Ipv4(BigInteger ipAddr) {long ipv4 = ipAddr.longValue();return long2Ipv4(ipv4);}}
- 测试类
```java import org.junit.Assert; import org.junit.Test;
import java.math.BigInteger;
public class Ipv4UtilsTest {
@Test(expected = IllegalArgumentException.class)public void testIpv4AddrBlank() {IpUtils.ipv4To32BitInt("");}@Test(expected = IllegalArgumentException.class)public void testIpv4AddrNull() {IpUtils.ipv4To32BitInt(null);}@Testpublic void testIpv4ToInt() {// normal caseAssert.assertEquals(BigInteger.valueOf(2896692481L), IpUtils.ipv4To32BitInt("172.168.5.1"));}@Testpublic void testIpv4AddrWithSpaceToInt() {Assert.assertEquals(BigInteger.valueOf(2896692481L), IpUtils.ipv4To32BitInt("172 . 168 .5 . 1"));Assert.assertEquals(BigInteger.valueOf(2896692481L), IpUtils.ipv4To32BitInt(" 172.168.5.1 "));Assert.assertEquals(BigInteger.valueOf(2896692481L), IpUtils.ipv4To32BitInt(" 172 .168 .5 . 1 "));Assert.assertEquals(BigInteger.valueOf(2896692481L), IpUtils.ipv4To32BitInt(" 172 . 168 .5 . 1 "));}@Test(expected = IllegalArgumentException.class)public void testIpv4AddrWithInvalidateSpace() {IpUtils.ipv4To32BitInt("17 2.168.5.1");}@Test(expected = IllegalArgumentException.class)public void testIpv4AddrInvalidate0() {IpUtils.ipv4To32BitInt("256.23.4.1");}@Test(expected = IllegalArgumentException.class)public void testIpv4AddrInvalidate1() {IpUtils.ipv4To32BitInt(".1.23.4.1");}@Test(expected = IllegalArgumentException.class)public void testIpv4AddInvalidate2() {IpUtils.ipv4To32BitInt(".23.4.1");}@Test(expected = IllegalArgumentException.class)public void testIpv4AddInvalidate3() {IpUtils.ipv4To32BitInt("a.b.c.d");}@Test(expected = IllegalArgumentException.class)public void testIpv4AddInvalidate4() {IpUtils.ipv4To32BitInt("a.b.1.2");}
} ```
参考资料:
Convert IPv4 string representation to a 32-bit number with SSE instructions
convert four 32 bits ints to IP address in java
