前言
为什么要将 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 => 172
00000000 00000000 00000000 10101000 => 168
00000000 00000000 00000000 00000101 => 5
00000000 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 00000000
00000000 10101000 00000000 00000000
00000000 00000000 00000101 00000000
00000000 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);
}
@Test
public void testIpv4ToInt() {
// normal case
Assert.assertEquals(BigInteger.valueOf(2896692481L), IpUtils.ipv4To32BitInt("172.168.5.1"));
}
@Test
public 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