java获取IP地址范围内的所有IP(附带解析)

一.实现代码

    /**
     * 验证IP地址范围是否有效
     *
     * @param ipRange 例IP范围:192.168.1.1-192.168.2.255
     * @return
     */
    public static boolean isValidIpRange(String ipRange) {
        String[] parts = ipRange.split("-");
        if (parts.length != 2) {
            return false;
        }
        String startIp = parts[0];
        String endIp = parts[1];
        // 验证起始和结束IP地址是否都有效
        if (!isValidIp(startIp) || !isValidIp(endIp)) {
            return false;
        }

        // 转换起始和结束IP地址为整数并比较
        int startIpInt = ipToInt(startIp);
        int endIpInt = ipToInt(endIp);

        // 结束IP地址必须大于或等于起始IP地址
        return endIpInt >= startIpInt;
    }

    /**
     * 根据IP范围获取范围内所有IP地址
     *
     * @param ipRange 例IP范围:192.168.1.1-192.168.2.255
     * @return
     */
    public static List<String> generateRangeIpList(String ipRange) {
        String[] parts = ipRange.split("-");
        String startIp = parts[0];
        String endIp = parts[1];
        int startIpAsInt = ipToInt(startIp);
        int endIpAsInt = ipToInt(endIp);
        List<String> ipList = new ArrayList<>();
        for (int i = startIpAsInt; i <= endIpAsInt; i++) {
            ipList.add(intToIp(i));
        }
        return ipList;
    }

    /**
     * 将IP地址转换为整数(网络字节序)
     *
     * @param ip 192.168.1.1
     * @return
     */
    private static int ipToInt(String ip) {
        String[] parts = ip.split("\\.");
        int result = 0;
        for (int i = 0; i < parts.length; i++) {
            result |= (Integer.parseInt(parts[i]) & 0xFF) << (24 - (i * 8));
        }
        return result;
    }

    /**
     * 将整数转换为IP地址
     *
     * @param ip 192.168.1.1
     * @return
     */
    private static String intToIp(int ip) {
        return (ip >>> 24) + "." +
                ((ip >> 16) & 0xFF) + "." +
                ((ip >> 8) & 0xFF) + "." +
                (ip & 0xFF);
    }

    /**
     * 使用正则表达式进行匹配 验证IPv4地址是否有效
     *
     * @param ip 例IP地址:192.168.1.1
     * @return
     */
    public static boolean isValidIp(String ip) {
        String pattern = "^((\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])\\.){3}(\\d{1,2}|1\\d{2}|2[0-4]\\d|25[0-5])$";
        return ip.matches(pattern);
    }

二.代码解析

2.1. ipToInt方法

这个方法将IP地址字符串转换为整数。

  1. 字符串分割String[] parts = ip.split("\\."); 使用点号(".")作为分隔符将IP地址字符串分割成四个部分(例如,"192"、"168"、"1"、"1")。

  2. 初始化结果int result = 0; 定义一个整数变量result,它将存储IP地址的整数表示。

  3. 循环处理每个部分:对于parts数组中的每个部分(即IP地址的每个数字),执行以下操作:

    • 解析整数Integer.parseInt(parts[i]) 将字符串转换为整数。
    • 掩码处理& 0xFF 是一个位掩码操作,确保整数在0到255之间(因为IP地址的每个部分都应该在0到255之间)。
    • 位移操作<< (24 - (i * 8)) 将解析并掩码处理后的整数左移适当的位数。这是因为IPv4地址是一个32位的数字,从左到右,每个部分(或称为八位字节)分别占据8位。所以,第一个部分(最左边的部分)不需要移位,第二个部分需要左移8位,依此类推。
  4. 返回结果return result; 返回存储了IP地址整数表示的result变量。

2.2. intToIp 方法

这个方法将整数转换回IP地址字符串。

  1. 无符号右移ip >>> 24 将整数右移24位,获取IP地址的第一个部分(最左边的部分)。注意,这里使用了无符号右移(>>>),这意味着在右侧填充0而不是符号位。
  2. 位掩码处理:对于IP地址的每个后续部分,使用位掩码& 0xFF来确保只获取8位。
  3. 位移和拼接((ip >> 16) & 0xFF)((ip >> 8) & 0xFF) 和 (ip & 0xFF) 分别获取IP地址的第二个、第三个和第四个部分。然后,使用点号(".")将这些部分拼接成一个字符串。
  4. 返回结果return 返回拼接后的IP地址字符串。

这两个方法共同实现了IPv4地址和32位无符号整数之间的转换,这在网络编程和IP地址处理中是非常常见的操作。

2.3. 位移操作解析

IPv4地址是由四个部分组成,每个部分是一个0到255之间的整数,这四个部分之间用点号(.)分隔。例如,IP地址 "192.168.1.1" 可以分解为四个部分:192, 168, 1, 1。

在二进制表示中,每个部分(或称为八位字节)占据8位。一个IPv4地址总共是32位。如果我们想将这四个部分组合成一个32位的整数,我们需要按照它们在IP地址中的顺序,将它们放在整数的不同位置。

现在,让我们用二进制和位移操作来演示这个过程。

假设我们有以下IP地址的四个部分(转换为二进制只是为了演示):

  1. 192: 11000000
  2. 168: 10101000
  3. 1: 00000001
  4. 1: 00000001

一个32位的整数有足够的空间来存储这四个部分,每个部分占据8位。为了将它们组合在一起,我们需要将它们分别放在整数的不同位置:

  • 第一个部分(192)放在最高位(最左边),不需要移位。
  • 第二个部分(168)需要左移8位,因为第一个部分已经占据了最低的8位。
  • 第三个部分(1)需要左移16位,因为前两个部分已经占据了最低的16位。
  • 第四个部分(1)需要左移24位,因为前三个部分已经占据了最低的24位。

用位移操作<<来表示这个过程:

  • 第一个部分:11000000(不变)
  • 第二个部分:10101000 << 8(变为 1010100000000000
  • 第三个部分:00000001 << 16(变为 000000010000000000000000
  • 第四个部分:00000001 << 24(变为 00000001000000000000000000000000

现在,我们将这四个部分组合起来,得到最终的32位整数表示:

11000000 10101000 00000001 00000001

ipToInt方法中,循环中的位移操作<< (24 - (i * 8))确保了每个部分都被放在正确的位置上。当i从0开始递增时,位移量从24逐渐减少到0,这样每个新的部分就会被放在之前部分的右边(即更低的位)。

这种方法允许我们轻松地将一个IP地址的四个部分转换为一个32位的整数,这个整数在网络编程中经常用于表示IP地址,因为它比字符串更加紧凑和高效。

2.4. 位移操作详细解析

首先,我们有一个初始结果result,它初始化为0(在二进制中,所有位都是0)。

然后,我们开始循环遍历IP地址的每个部分,并对每个部分执行以下操作:

  1. 将当前部分转换为整数(如果它已经是字符串表示的整数,则跳过这一步)。
  2. 使用位与操作& 0xFF来确保这个整数只有8位(即,确保它的值在0到255之间)。
  3. 将这个8位的整数左移适当的位数,以便它出现在最终32位整数中的正确位置。
  4. 使用位或操作|将这个左移后的值添加到result中。

这里的关键是位或操作|。这个操作会将两个操作数的对应位进行或运算。对于任何给定的位,只要两个操作数中至少有一个是1,结果就是那个位就是1。由于我们开始时将result初始化为0(所有位都是0),所以位或操作实际上只是将左移后的值“放置”在result的相应位置。

现在,让我们用实际的值来演示这个过程:

假设IP地址是"192.168.1.1",我们将其分解为四个部分:192, 168, 1, 1。

  1. 初始化result为0(在二进制中,32个0)。
  2. 处理第一个部分(192):
    • 转换为二进制:11000000
    • 左移0位(因为这是第一个部分,所以不需要移位):11000000
    • 使用位或操作添加到resultresult = 0 | 11000000,所以result现在是11000000
  3. 处理第二个部分(168):
    • 转换为二进制:10101000
    • 左移8位:1010100000000000
    • 使用位或操作添加到resultresult = 11000000 | 1010100000000000,所以result现在是110000001010100000000000
  4. 处理第三个部分(1):
    • 转换为二进制:00000001
    • 左移16位:000000010000000000000000
    • 使用位或操作添加到resultresult = 110000001010100000000000 | 000000010000000000000000,所以result现在是11000000101010000000000100000000
  5. 处理第四个部分(1):
    • 转换为二进制:00000001
    • 左移24位:00000001000000000000000000000000
    • 使用位或操作添加到resultresult = 11000000101010000000000100000000 | 00000001000000000000000000000000,所以result现在是11000000101010000000000100000001

这就是最终的32位整数表示,它对应于IP地址"192.168.1.1"。注意,这个过程不涉及右移,因为我们是在构建一个更大的数,而不是从一个大数中提取一个较小的部分。

2.5.问题分析

2.5.1 问:为什么通过运行代码IP地址"192.168.1.1返回的result是-1062731519呢?而不是11000000101010000000000100000001呢?

答:

运行ipToInt方法并传入IP地址"192.168.1.1"时,您实际上是在处理一个32位的无符号整数。然而,在Java中,int类型是一个有符号的32位整数,其值的范围是-2,147,483,648到2,147,483,647。

当您尝试将一个大于Integer.MAX_VALUE(即2,147,483,647)的无符号整数存储在一个int变量中时,会发生所谓的“整数溢出”。在这种情况下,Java会执行模2^32运算(因为int是32位的),这意味着它会取该数值除以2^32的余数。

对于IP地址"192.168.1.1",其对应的无符号32位整数是3232235777(即192 * 2^24 + 168 * 2^16 + 1 * 2^8 + 1)。但是,当您尝试将这个值存储在一个int变量中时,由于大于Integer.MAX_VALUE,所以会发生溢出。

具体来说,3232235777模2^32的结果是-1062731519。这是因为3232235777和-1062731519在二进制表示上只是符号位不同(一个的最高位是0,表示正数;另一个的最高位是1,表示负数,但在无符号整数上下文中,它只是一个更大的数)。

要正确处理这种情况,您可以将结果视为一个无符号整数,或者使用其他数据类型(如longBigInteger)来存储结果,以避免溢出。但是,请注意,即使使用long,您仍然需要小心处理无符号整数的概念,因为Java的基本数据类型都是有符号的。

在大多数情况下,当您在Java中处理IP地址的整数表示时,您只需要知道如何处理这种溢出情况,并理解它在二进制层面上的工作原理。在实际应用中,您通常不需要将IP地址的整数表示转换回其十进制表示(除非是为了显示或调试目的),因此这种溢出通常不会影响代码的正确性。

  • 31
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值