杰瑞科技汇

java ip地址的正则表达式

IPv4 地址正则表达式

IPv4 地址由四个 0-255 之间的十进制数组成,用点号()分隔,168.1.1

推荐的正则表达式

这是一个既精确又相对易读的版本,能够严格匹配 0-255 的范围。

^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$

正则表达式解析

让我们把这个复杂的表达式拆解开来:

  • ^ 和 :分别表示字符串的开始和结束,这确保了整个输入字符串都必须是一个合法的 IP 地址,而不是包含其他字符。
  • 这是一个非捕获分组,它将括号内的内容组合成一个整体,但不会像 那样创建一个反向引用,这更高效。
  • (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?):这是匹配 0-255 这个范围的核心逻辑,它由三个部分通过 (或) 连接:
    1. 25[0-5]:匹配 250255 的数字。
    2. 2[0-4][0-9]:匹配 200249 的数字。
    3. [01]?[0-9][0-9]?:匹配 0199 的数字。
      • [01]?:匹配 0 个或 1 个 01
      • [0-9]:匹配一个 09 的数字。
      • [0-9]?:匹配 0 个或 1 个 09 的数字。
      • 组合起来可以匹配 099 ([01]?[0-9]?), 100199 (1[0-9][0-9]), 以及单个数字如 5 ([0-9])。
  • \.:匹配一个字面量点号 ,需要反斜杠 \ 来转义,因为在正则表达式中 是一个特殊字符(匹配任意字符)。
  • {3}:表示前面的分组(即 xxx.)必须出现恰好 3 次。

所以整个表达式的意思是:从字符串开头开始,匹配一个 0-255 的数字,后跟一个点号,重复三次;最后再匹配一个 0-255 的数字,直到字符串结束。

Java 代码示例

import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class Ipv4Validator {
    // IPv4 正则表达式
    private static final String IPV4_REGEX =
            "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
    private static final Pattern IPV4_PATTERN = Pattern.compile(IPV4_REGEX);
    public static boolean isValidIPv4(String ip) {
        if (ip == null) {
            return false;
        }
        Matcher matcher = IPV4_PATTERN.matcher(ip);
        return matcher.matches();
    }
    public static void main(String[] args) {
        String[] validIps = {"192.168.1.1", "10.0.0.1", "255.255.255.255", "0.0.0.0", "127.0.0.1"};
        String[] invalidIps = {"256.1.2.3", "192.168.01.1", "192.168.1", "192.168.1.1.1", "a.b.c.d"};
        System.out.println("--- Valid IPv4 Addresses ---");
        for (String ip : validIps) {
            System.out.println(ip + ": " + isValidIPv4(ip));
        }
        System.out.println("\n--- Invalid IPv4 Addresses ---");
        for (String ip : invalidIps) {
            System.out.println(ip + ": " + isValidIPv4(ip));
        }
    }
}

IPv6 地址正则表达式

IPv6 地址比 IPv4 复杂得多,它由 8 组 4 位的十六进制数组成,组之间用冒号()分隔。2001:0db8:85a3:0000:0000:8a2e:0370:7334

为了简化,IPv6 允许一些压缩规则:

  1. 可以省略每组前导的零,2001:db8:85a3:0:0:8a2e:370:7334
  2. 可以使用 来表示一组或多组连续的零,但 在一个地址中只能出现一次,2001:db8:85a3::8a2e:370:7334

推荐的正则表达式

这个正则表达式考虑了上述所有规则,包括 的压缩。

^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^(([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})?::(([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})?$

正则表达式解析

这个表达式由两个主要部分通过 (或) 连接,分别处理标准格式和带 的压缩格式。

第一部分:标准格式 ^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$

  • [0-9a-fA-F]{1,4}:匹配 1 到 4 位十六进制字符(数字 0-9,以及字母 a-f,不区分大小写)。
  • ([0-9a-fA-F]{1,4}:){7}:匹配由 "1-4位十六进制字符 + 冒号" 构成的组合,共 7 次。
  • 最后再跟一个 [0-9a-fA-F]{1,4}
  • 这正好匹配了 8 组十六进制数,前 7 组后面带冒号。

第二部分:压缩格式(含 ) ^(([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})?::(([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})?$ 这部分比较复杂,它允许 出现在地址的开头、中间或结尾。

  • (([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})?:这是 左边的部分。
    • 表示整个分组是可选的(可以匹配 0 次或 1 次)。
    • ([0-9a-fA-F]{1,4}:){0,6}:匹配 0 到 6 次 "1-4位十六进制字符 + 冒号"。
    • [0-9a-fA-F]{1,4}:最后再跟一个 1-4 位十六进制字符。
    • 所以这个分组可以匹配像 2001:db8: 这样的前缀。
  • 匹配字面量的双冒号。
  • (([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})?:这是 右边的部分,与左边部分的结构完全相同,可以匹配像 85a3:0000 这样的后缀。

组合起来, 可以代表:

  • 开头的零::1 (相当于 0000:0000:0000:0000:0000:0000:0000:0001
  • 中间的零:2001:db8::1 (相当于 2001:0db8:0000:0000:0000:0000:0000:0001
  • 结尾的零:2001:db8:0:0:0:0:0:: (相当于 2001:0db8:0000:0000:0000:0000:0000:0000

Java 代码示例

import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class Ipv6Validator {
    // IPv6 正则表达式
    private static final String IPV6_REGEX =
            "^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^(([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})?::(([0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4})?$";
    private static final Pattern IPV6_PATTERN = Pattern.compile(IPV6_REGEX);
    public static boolean isValidIPv6(String ip) {
        if (ip == null) {
            return false;
        }
        Matcher matcher = IPV6_PATTERN.matcher(ip);
        return matcher.matches();
    }
    public static void main(String[] args) {
        String[] validIps = {
            "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
            "2001:db8:85a3:0:0:8a2e:370:7334",
            "2001:db8:85a3::8a2e:370:7334",
            "2001:db8::1",
            "::1",
            "fe80::1%eth0" // 注意:这个包含作用域ID的正则不匹配,需要扩展
        };
        String[] invalidIps = {
            "2001:0db8:85a3:0000:0000:8a2e:0370:7334:1234", // 组数太多
            "2001:0db8:85a3:::8a2e:370:7334", // 多个::
            "2001:0db8:85a3::8a2e::370:7334", // 多个::
            "2001:0db8:85a3:0:0:8a2e:370:733g", // 包含非法字符
            "192.168.1.1" // IPv4地址
        };
        System.out.println("--- Valid IPv6 Addresses ---");
        for (String ip : validIps) {
            System.out.println(ip + ": " + isValidIPv6(ip));
        }
        System.out.println("\n--- Invalid IPv6 Addresses ---");
        for (String ip : invalidIps) {
            System.out.println(ip + ": " + isValidIPv6(ip));
        }
    }
}

重要提示与替代方案

性能考虑

复杂的正则表达式(尤其是 IPv6 的)在处理大量数据或高频调用时可能会有性能开销,如果只是做简单的校验,并且性能是关键因素,可以考虑将字符串按分隔符( 或 )拆分,然后逐部分进行逻辑判断,这种方式通常比正则表达式更快。

更健壮的 IPv6 正则

上面的 IPv6 正则表达式是常见且实用的,但它无法匹配包含“作用域ID”的 IPv6 地址,fe80::1%eth0,一个更全面的正则表达式会非常复杂,对于大多数场景,上述版本已经足够。

最佳实践:使用 Java 内置 API

对于 IP 地址的验证,最推荐、最健壮、最简单的方式是使用 Java 标准库中的 InetAddress 类,它已经内置了所有复杂的解析逻辑,并且能正确处理各种边界情况。

import java.net.InetAddress;
import java.net.UnknownHostException;
public class InetAddressValidator {
    public static boolean isValidIP(String ip) {
        try {
            // 尝试将字符串解析为 InetAddress
            InetAddress.getByName(ip);
            // 如果没有抛出异常,说明 IP 地址格式正确
            return true;
        } catch (UnknownHostException e) {
            // 如果抛出 UnknownHostException,说明 IP 地址格式无效
            return false;
        }
    }
    public static void main(String[] args) {
        String testIp = "2001:0db8:85a3::8a2e:0370:7334";
        System.out.println(testIp + " is valid: " + isValidIP(testIp)); // true
        String testIp2 = "256.1.2.3";
        System.out.println(testIp2 + " is valid: " + isValidIP(testIp2)); // false
        String testIp3 = "localhost";
        System.out.println(testIp3 + " is valid: " + isValidIP(testIp3)); // true (getByName可以解析主机名)
        // 如果只想验证 IP 格式,不解析主机名,需要额外处理
        // 检查字符串中是否包含字母
    }
}

InetAddress.getByName() 的优点:

  • 准确性高:由 Java 官方实现,符合所有 RFC 标准。
  • 代码简洁:无需编写和维护复杂的正则表达式。
  • 功能强大:不仅能验证 IPv4 和 IPv6,还能处理主机名。

缺点:

  • 它会尝试进行网络解析(如果传入的是主机名),可能会有轻微的性能开销(虽然通常可以忽略不计)。
  • 它会将 localhost0.0.1 视为有效地址,如果你只想验证纯 IP 格式,需要自己加上判断(检查字符串中是否包含字母)。
方法 优点 缺点 适用场景
正则表达式 纯字符串匹配,无网络开销。
逻辑完全在代码控制中。
正则表达式复杂,难以编写和维护。
可能有性能问题。
难以覆盖所有边缘情况(如 IPv6 作用域 ID)。
需要严格的、高性能的、无副作用的纯格式校验。
不能使用网络 API 的受限环境。
InetAddress API 最准确、最健壮,符合官方标准。
代码极其简单,易于维护。
自动处理 IPv4 和 IPv6。
可能会尝试解析主机名,有极小的网络开销。
可能会将主机名(如 localhost)视为有效。
绝大多数 Java 应用场景下的首选方案,当需要验证一个字符串是否是有效的 IP 地址时,这应该是你的第一选择。
分享:
扫描分享到社交APP
上一篇
下一篇