在Java中验证IPv4字符串

60

下面的方法用于验证字符串是否为正确的IPv4地址,如果是有效的则返回true。如果有更好的正则表达式和优雅的方法,请提出建议:

public static boolean validIP(String ip) {
    if (ip == null || ip.isEmpty()) return false;
    ip = ip.trim();
    if ((ip.length() < 6) & (ip.length() > 15)) return false;

    try {
        Pattern pattern = Pattern.compile("^(?:(?: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]?)$");
        Matcher matcher = pattern.matcher(ip);
        return matcher.matches();
    } catch (PatternSyntaxException ex) {
        return false;
    }
}

有不同的表示方法可用。您正在寻找点十进制表示法:请参阅http://en.wikipedia.org/wiki/IPv4#Address_representations - Michael Konietzka
一个ip.length()为7是有效的,例如0.0.0.0是7个字符,你的方法将返回false。 - rouble
2
我想知道为什么这仍然不是某些第三方库的一部分。我的意思是避免使用第三方库是好的,但是找到适合它的正则表达式非常容易出错,这在我看来比使用第三方API更糟糕。 - Arthur Eirich
1
正则表达式没问题,你只需要将 [0-9] 替换为 \d 并且把模式编译移出方法,这样就不必每次都编译了。 - Javier Diaz
17个回答

65

这里有一种更易读但效率稍低的方法可以处理它。

public static boolean validIP (String ip) {
    try {
        if ( ip == null || ip.isEmpty() ) {
            return false;
        }

        String[] parts = ip.split( "\\." );
        if ( parts.length != 4 ) {
            return false;
        }

        for ( String s : parts ) {
            int i = Integer.parseInt( s );
            if ( (i < 0) || (i > 255) ) {
                return false;
            }
        }
        if ( ip.endsWith(".") ) {
            return false;
        }

        return true;
    } catch (NumberFormatException nfe) {
        return false;
    }
}

4
我更喜欢这种方法。 - theGrayFox
1
如果你使用 ip.split("\\.", -1),你可以删除 if (ip.endsWith(".")) ... 这一部分。 - samthebest
1
"192.168.1" 是一个有效的IP地址。我认为你的方法有缺陷。 - PEdroArthur
1
@PEdroArthur 四个点分十进制数字是IPV4地址的标准表示法。点分八位格式从未在RFC中得到明确规定。第一次提到它是在MTP RFC(大约1981年)中,它说:“四个由点分隔并用括号括起来的小十进制整数,例如“[123.255.37.321]”,表示32位ARPA Internet地址,由四个8位字段组成。”你有任何证据表明“192.168.1”是有效的吗? - rouble
2
@rouble 我不知道。然而,我使用的所有网络堆栈都接受像192.168.1(192.168.0.1)和10.1(10.0.0.1)这样的地址。 - PEdroArthur
显示剩余5条评论

55

更新: Commons-HttpClient及其后继者HttpComponents-HttpClient已采用此功能。您可以像这样使用它的版本:InetAddressUtils.isIPv4Address(Str)


Apache Commons Validator的开发版具有InetAddressValidator类,该类具有isValidInet4Address(String)方法,以检查给定的String是否为有效的IPv4地址。

源代码 可在存储库中查看,如果您觉得需要改进,可能会提供一些想法。

快速查看所提供的代码表明,您的方法在每次调用时都会编译一个Pattern。我会将那个Pattern类移动到一个static字段中,以避免在每次调用时进行昂贵的模式编译流程。


InetAddressUtils.isIPv4Address(Str) 是Java内部API,不应使用它。 - user918888
@user918888,这是什么意思?它只是将其与正则表达式进行匹配并返回结果。 - Andrii Plotnikov
这是Apache Commons库的一部分,没问题。也许他认为它是一些“com.sun”类型的内部东西? - Dan

22

如果您想使用一个同时支持IPv4和IPv6的已发布版本的库,可以使用Guava库。

boolean isValid = InetAddresses.isInetAddress("1.2.3.4");

17

这里提供了一种使用Java 8流的解决方案来检查地址是否由四个数(0到255之间的数)组成且用点号分隔:

public class IpValidation {

    /**
     * Check that a string represents a decimal number
     * @param string The string to check
     * @return true if string consists of only numbers without leading zeroes, false otherwise
     */
    public static boolean isDecimal(String string) {
        // Check whether string has a leading zero but is not "0"
        if (string.startsWith("0")) {
            return string.length() == 1;
        }
        for(char c : string.toCharArray()) {
            if(c < '0' || c > '9') {
                return false;
            }
        }
        return true;
    }

    public static boolean isIp(String string) {
        String[] parts = string.split("\\.", -1);
        return parts.length == 4 // 4 parts
                && Arrays.stream(parts)
                .filter(IpValidation::isDecimal) // Only decimal numbers
                .map(Integer::parseInt)
                .filter(i -> i <= 255 && i >= 0) // Must be inside [0, 255]
                .count() == 4; // 4 numerical parts inside [0, 255]
    }
}

我认为这在所有情况下都不正确。该方法对于172.013.1.2返回true,而这是一个无效的IP地址字符串。它应该是172.13.1.2。'isNumeric'方法可以修改以纠正此错误。 - radiantRazor
http://www.ipaddressguide.com/ip和http://sqa.fyicenter.com/Online_Test_Tools/IP_Address_Format_Validator.php以及https://commons.apache.org/proper/commons-validator/apidocs/src-html/org/apache/commons/validator/routines/InetAddressValidator.html#line.86说它是无效的。http://formvalidation.io/validators/ip/和http://www.csgnetwork.com/directipverify.html?IPvalue=172.013.001.002说它是有效的。 - radiantRazor
4
ping 192.168.1.09 ping:无法解析 192.168.1.09:未知主机总结:以0开头的IP段表示该段为八进制,因此我们还需要验证剩余数字中不包含7、8(我承认以前我不知道,这就是我给出172.013.1.2示例的原因)。正确测试用例是172.13.1.008-无法通过。长话短说:以上验证方法不正确,可能导致误报。 - radiantRazor
2
在某些情况下可以工作,那对我来说足够好了。这是一个拒绝票的充分理由。很遗憾这个答案还得到了赏金。之所以可以工作的原因是以 0 开头的数字(本例中为 001)在八进制和十进制中具有相同的含义。只有在只有一个非零数字,并且后面没有零,而且这个数字不是 8 或 9 的情况下才成立。这真是太糟糕了。 - LarsH
IP地址中允许有前导零,但由于其解释有些模糊,我将更改答案以不允许前导零。 - Raniz
显示剩余6条评论

17

如果您不介意在无效的IP地址上使用DNS解析,例如www.example.com,则可以使用InetAddress方法进行检查:

public static final boolean checkIPv4(final String ip) {
    boolean isIPv4;
    try {
    final InetAddress inet = InetAddress.getByName(ip);
    isIPv4 = inet.getHostAddress().equals(ip)
            && inet instanceof Inet4Address;
    } catch (final UnknownHostException e) {
    isIPv4 = false;
    }
    return isIPv4;
}

该方法检查是否为Inet4Address实例,以及参数是否为IP地址而非主机名。如果您期望将许多主机名作为参数,请注意此实现使用DNS尝试解析它。这可能会影响性能。

否则,您可以查看boolean sun.net.util.IPAddressUtil.isIPv4LiteralAddress(String src) 中的字符串如何被解析以进行IPv4检查。


2
顺便说一下,这是我通常的做法:首先使用简单的正则表达式来检查参数是否由4个点分隔的数字组成(我不尝试检查数字是否在范围内等,只检查一般格式)。如果第一个检查通过,则使用InetAddress完成验证,就像这个答案中所示。 - theglauber

7

这里找到正则表达式示例。

package com.mkyong.regex;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class IPAddressValidator{

    private Pattern pattern;
    private Matcher matcher;

    private static final String IPADDRESS_PATTERN = 
        "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
        "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
        "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
        "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$";

    public IPAddressValidator(){
      pattern = Pattern.compile(IPADDRESS_PATTERN);
    }

   /**
    * Validate ip address with regular expression
    * @param ip ip address for validation
    * @return true valid ip address, false invalid ip address
    */
    public boolean validate(final String ip){         
      matcher = pattern.matcher(ip);
      return matcher.matches();             
    }
}

1
我认为这并不适用于所有情况。该方法对于172.013.1.2返回true,但这是一个无效的IP地址字符串。正确的应该是172.13.1.2。 - radiantRazor
@radiantRazor 172.013.1.2 是一个有效的地址,尽管可能会误导,因为013通常被解释为八进制,即11。请参见点十进制表示法 - Jonathan Rosenne

5

我认为这个解决方案非常优雅,尽管需要一分钟才能理解它。

基本思想是取一个子字符串然后进行验证。
请注意,我交换了x和y的位置,因为子字符串的起始点总是上一个子字符串的结尾点加1。

这个解决方案比正则表达式变体快20倍,比分割快2倍。

public static boolean validIP(String ip) {
    if(ip == null || ip.length() < 7 || ip.length() > 15) return false;

    try {
        int x = 0;
        int y = ip.indexOf('.');

        if (y == -1 || ip.charAt(x) == '-' || Integer.parseInt(ip.substring(x, y)) > 255) return false;

        x = ip.indexOf('.', ++y);
        if (x == -1 || ip.charAt(y) == '-' || Integer.parseInt(ip.substring(y, x)) > 255) return false;

        y = ip.indexOf('.', ++x);
        return  !(y == -1 ||
                ip.charAt(x) == '-' ||
                Integer.parseInt(ip.substring(x, y)) > 255 ||
                ip.charAt(++y) == '-' ||
                Integer.parseInt(ip.substring(y, ip.length())) > 255 ||
                ip.charAt(ip.length()-1) == '.');

    } catch (NumberFormatException e) {
        return false;
    }
}

如果您知道将会有许多错误的IP地址,请考虑在第一个if语句下添加以下代码。这将使代码变慢1.5倍,但在出现错误时可将其改进700倍。
    for (int i = 0; i < ip.length(); i++) {
        if (!Character.isDigit(ip.charAt(i)) && ip.charAt(i) != '.') return false;
    }

以上解决方案是否也适用于IPv4Address和IPv6Address?如果可能,请使用main()方法添加测试用例。 - deepakl.2000
这是我测试过的IPv4最快的函数,但我们能否将其扩展到IPv6呢? - banjara

5
尝试使用以下正则表达式来匹配IPv4地址:
String ip4Regex="^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})(\\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})){3}$";

希望这能有所帮助,:)

4
如果您使用问题中的代码,您需要更改以下行:

if ((ip.length() < 6) & (ip.length() > 15)) return false;

to

if ((ip.length() <= 6) || (ip.length() > 15)) return false;

4

除了Michael Konietzka的回答外,大多数答案都是错误的:例如10.8是一个有效的IP4地址(10.0.0.8的简写)。

请参见IP地址的文本表示在Java规范中的说明。

正如参考资料所示,要检查数字表示,可能会有1到4个部分,并且在每种情况下,部分的限制不同。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接