前言

为什么要将 IPv4 转数值存储?

关于这个问题有几个一般的原因:像整数比字符串比起来存储占用空间更少。

但也只是强调一个主要的原因:IP地址实际上就是数字!

我们一般更喜欢IP地址字符串表示(如:10.0.0.12002::2)以使我们读或者记更加的轻松。IPs上的大多数操作都是严格的数字操作,如屏蔽(ip&mask)、检查IP范围。正如我们将数字字符串转整数类型("123"->123)一样,IP这样的字符串也应可以转换为整数形式。

不过,就大多数情况下这个问题更多的会在面试中被问到~

所以我们就来探讨一下这个问题!

思路

在此之前我们需要知道 IPv4 的地址范围:0.0.0.0 ~ 255.255.255.255,存储这样的 IPv4 地址,其实只需要 4 个字节,也就是32位。

所以,所有的 IPv4 实际上都可以使用32位的整型来进行表示,如下:

  1. 00000000 00000000 00000000 00000000

这样的二进制最左边的 8bit 我们称之为最高位,次之的 8bit 可以称之为中高位,之后的分别是中低位以及低位。

所以,IPv4 这样的地址实际上都可以对应 32 位整形数值中的,我们只需要将 IPv4 中的四个数字分别提取出来分别对应着 32位 二进制的高 ~ 低位即可。

这又牵扯到了位运算:左移、右移以及或操作。

现在来说下具体思路:

现在我们有一个 IPv4 地址:172.168.5.1 ,要求我们将该 IP 转换为整形存储。

所以,首先我们要做的就是分别将四个数字提取出来,然后分别得到 32位 的二进制(不足32就需要在其左边进行补码)。

如,172 的二进制数值为:10101100,因此我们就需要将该二进制进行32位补码,得到如下结果:

  1. 00000000 00000000 00000000 10101100

同理,我们需要得到其余3个数字的为禁止32位补码,最后四个数字的32位补码如下:

  1. 00000000 00000000 00000000 10101100 => 172
  2. 00000000 00000000 00000000 10101000 => 168
  3. 00000000 00000000 00000000 00000101 => 5
  4. 00000000 00000000 00000000 00000001 => 1

现在我们要做的就是将这四个数字进行位操作:

  • 将 172 放到最高位(左移24位):
  1. 10101100 00000000 00000000 00000000
  • 将 168 放到次高位(左移16位):
  1. 00000000 10101000 00000000 00000000
  • 将 5 放到次低位(左移8位):
  1. 00000000 00000000 00000101 00000000
  • 将 1 放到低位(低位不需要动):
  1. 00000000 00000000 00000000 00000001

所以,最后得到的四个结果如下:

  1. 10101100 00000000 00000000 00000000
  2. 00000000 10101000 00000000 00000000
  3. 00000000 00000000 00000101 00000000
  4. 00000000 00000000 00000000 00000001

现在我们要做的即使将这4个二进制的32位补码进行或(|)操作,得到最后的结果如下:

  1. 10101100 10101000 00000101 00000001

而这个数字就是我们最后得到的整型,转换为 10 进制值为:**2896692481**

所以,IPv4 172.168.5.1 对应的整型就是 **2896692481**

反之,要想还原为 IPv4 我们只需要将其反操作即可,如下:

  1. long ipv4 = 2896692481L;
  2. String first = (ipv4 >> 24) & 0xFF;
  3. String second = (ipv4 >> 16) & 0xFF;
  4. String third = (ipv4 >> 8) & 0xFF;
  5. String fourth = ipv4 & 0xFF;

现在就来看下具体的 Java 代码实现!

Java 实现

  1. import org.apache.commons.lang3.StringUtils;
  2. import java.math.BigInteger;
  3. import java.util.regex.Matcher;
  4. import java.util.regex.Pattern;
  5. public class IpUtils {
  6. private static final int MIN_IP_SEGMENT = 0;
  7. private static final int MAX_IP_SEGMENT = 255;
  8. /** 验证 IPv4 有消息 */
  9. 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)*$";
  10. public static BigInteger ipv4To32BitInt(String ipAddr) {
  11. if (!validateIpAddress(ipAddr)) {
  12. throw new IllegalArgumentException("Ip Address is not validated: " + ipAddr);
  13. }
  14. return BigInteger.valueOf(ipv4ToLong(ipAddr));
  15. }
  16. /**
  17. * 验证 IPv4 有效性
  18. *
  19. * @param ipAddr IPv4
  20. */
  21. private static boolean validateIpAddress(String ipAddr) {
  22. if (StringUtils.isBlank(ipAddr)) {
  23. return false;
  24. }
  25. Pattern pattern = Pattern.compile(IPV4_PATTERN);
  26. Matcher matcher = pattern.matcher(ipAddr);
  27. return matcher.matches();
  28. }
  29. /**
  30. * Ipv4 转整型 Long
  31. *
  32. * @param ipAddr IPv4
  33. */
  34. private static long ipv4ToLong(String ipAddr) {
  35. long result = 0;
  36. String[] ipSegmentArray = StringUtils.split(ipAddr, "\\.");
  37. for (int idx = 0; idx < ipSegmentArray.length; idx++) {
  38. result |= shiftedIpSegment(ipSegmentArray[idx], idx);
  39. }
  40. return result;
  41. }
  42. /**
  43. * IP段位运算
  44. *
  45. * @param ipSegment IP段
  46. * @param idx IP段下标
  47. */
  48. private static long shiftedIpSegment(String ipSegment, int idx) {
  49. long segment = formatIpSegment(ipSegment);
  50. switch (idx) {
  51. case 0:
  52. return (segment << 24) & 0xFF000000;
  53. case 1:
  54. return (segment << 16) & 0xFF0000;
  55. case 2:
  56. return (segment << 8) & 0xFF00;
  57. case 3:
  58. return segment & 0xFF;
  59. default:
  60. throw new ArrayIndexOutOfBoundsException("IPv4数组越界");
  61. }
  62. }
  63. /**
  64. * 验证IP段有效性
  65. *
  66. * @param ipSegment IP 段
  67. */
  68. private static long formatIpSegment(String ipSegment) {
  69. long formattedIpSegment = Long.parseLong(ipSegment.trim());
  70. if (formattedIpSegment < MIN_IP_SEGMENT || formattedIpSegment > MAX_IP_SEGMENT) {
  71. throw new IllegalArgumentException("Ip Address Segment Is Out Of Range: " + ipSegment);
  72. }
  73. return formattedIpSegment;
  74. }
  75. /**
  76. * Long 转 IPv4
  77. */
  78. private static String long2Ipv4(long ipAddr) {
  79. String ipv4 = ((ipAddr & 0xFF000000) >> 24) + "." +
  80. ((ipAddr & 0xFF0000) >> 16) + "." +
  81. ((ipAddr & 0xFF00) >> 8) + "." +
  82. (ipAddr & 0xFF);
  83. validateIpAddress(ipv4);
  84. return ipv4;
  85. }
  86. private static String bitInt2Ipv4(BigInteger ipAddr) {
  87. long ipv4 = ipAddr.longValue();
  88. return long2Ipv4(ipv4);
  89. }
  90. }
  • 测试类
    ```java import org.junit.Assert; import org.junit.Test;

import java.math.BigInteger;

public class Ipv4UtilsTest {

  1. @Test(expected = IllegalArgumentException.class)
  2. public void testIpv4AddrBlank() {
  3. IpUtils.ipv4To32BitInt("");
  4. }
  5. @Test(expected = IllegalArgumentException.class)
  6. public void testIpv4AddrNull() {
  7. IpUtils.ipv4To32BitInt(null);
  8. }
  9. @Test
  10. public void testIpv4ToInt() {
  11. // normal case
  12. Assert.assertEquals(BigInteger.valueOf(2896692481L), IpUtils.ipv4To32BitInt("172.168.5.1"));
  13. }
  14. @Test
  15. public void testIpv4AddrWithSpaceToInt() {
  16. Assert.assertEquals(BigInteger.valueOf(2896692481L), IpUtils.ipv4To32BitInt("172 . 168 .5 . 1"));
  17. Assert.assertEquals(BigInteger.valueOf(2896692481L), IpUtils.ipv4To32BitInt(" 172.168.5.1 "));
  18. Assert.assertEquals(BigInteger.valueOf(2896692481L), IpUtils.ipv4To32BitInt(" 172 .168 .5 . 1 "));
  19. Assert.assertEquals(BigInteger.valueOf(2896692481L), IpUtils.ipv4To32BitInt(" 172 . 168 .5 . 1 "));
  20. }
  21. @Test(expected = IllegalArgumentException.class)
  22. public void testIpv4AddrWithInvalidateSpace() {
  23. IpUtils.ipv4To32BitInt("17 2.168.5.1");
  24. }
  25. @Test(expected = IllegalArgumentException.class)
  26. public void testIpv4AddrInvalidate0() {
  27. IpUtils.ipv4To32BitInt("256.23.4.1");
  28. }
  29. @Test(expected = IllegalArgumentException.class)
  30. public void testIpv4AddrInvalidate1() {
  31. IpUtils.ipv4To32BitInt(".1.23.4.1");
  32. }
  33. @Test(expected = IllegalArgumentException.class)
  34. public void testIpv4AddInvalidate2() {
  35. IpUtils.ipv4To32BitInt(".23.4.1");
  36. }
  37. @Test(expected = IllegalArgumentException.class)
  38. public void testIpv4AddInvalidate3() {
  39. IpUtils.ipv4To32BitInt("a.b.c.d");
  40. }
  41. @Test(expected = IllegalArgumentException.class)
  42. public void testIpv4AddInvalidate4() {
  43. IpUtils.ipv4To32BitInt("a.b.1.2");
  44. }

} ```


参考资料:

Convert IPv4 string representation to a 32-bit number with SSE instructions
convert four 32 bits ints to IP address in java