Java判断ip是否为IPV4或IPV6地址的方式有哪些

作者:有用网 阅读量:220 发布时间:2024-01-04
关键字 java

今天小编给大家分享一下Java判断ip是否为IPV4或IPV6地址的方式有哪些的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。

    判断字符串是否为IP地址通常都是基于正则表达式实现的,无论是引入外部的依赖包亦或是自己写正则实现,基本都是基于正则表达式实现的判断。然而比较例外的是,jdk自身提供了

    Inet4Address.getByName
    方法也可以帮助我们实现ip地址的判断。

    一、判断是否为IPV4,IPV6地址的常见方式

    1. 使用Apache Commons Validator做判断

    需要引入依赖包

        <dependency>
          <groupId>commons-validator</groupId>
          <artifactId>commons-validator</artifactId>
          <version>1.6</version>
        </dependency>

    有了依赖包,后续调用

    InetAddressValidator
    的核心API就好了。

    1.1判断是否为IPV4地址

        private static final InetAddressValidator VALIDATOR = InetAddressValidator.getInstance();
        public static boolean isValidIPV4ByValidator(String inetAddress) {
            return VALIDATOR.isValidInet4Address(inetAddress);
        }

    1.2判断是否为IPV6地址

        public static boolean isValidIPV6ByValidator(String inetAddress) {
            return VALIDATOR.isValidInet6Address(inetAddress);
        }

    1.3判断是否为IPV6或者IPV4地址

        public static boolean isValidIPV6ByValidator(String inetAddress) {
            return VALIDATOR.isValid(inetAddress);
        }

    2. 使用Guava做判断

    引入依赖包

        <dependency>
          <groupId>com.google.guava</groupId>
          <artifactId>guava</artifactId>
          <version>30.0-jre</version>
        </dependency>

    调用

    InetAddresses.isInetAddress
    即可实现快速的判断,但这个方式能同时判断字符串是否为IPV4或者IPV6地址,如果你只想判断其中一种格式,那就不行了。
        public static boolean isValidByGuava(String ip) {
            return InetAddresses.isInetAddress(ip);
        }

    3. 使用OWASP正则表达式做判断

    OWASP提供了一系列用于校验常见web应用名词的正则表达式,通过OWASP_Validation_Regex_Repository你可以检索到他们。这个判断方式只能判断是否为IPV4地址。

        private static final String OWASP_IPV4_REGEX =
                "^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)." +
                        "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)." +
                        "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)." +
                        "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
    
        private static final Pattern OWASP_IPv4_PATTERN = Pattern.compile(OWASP_IPV4_REGEX);
    
        public static boolean isValidIPV4ByOWASP(String ip) {
            if (ip == null || ip.trim().isEmpty()) {
                return false;
            }
            return OWASP_IPv4_PATTERN.matcher(ip).matches();
        }

    4. 使用自定义正则表达式做判断

    如下通过自定义的正则表达式判断字符串是否为IPV4地址,它的正则表达式以及实现细节,其实和第一种方案中判断IPV4是一致的,如果你只想判断字符串是否为IPV4地址,又懒得引入外部包,那么3,4这两种方式适合你。

        private static final String IPV4_REGEX =
                "^(d{1,3}).(d{1,3}).(d{1,3}).(d{1,3})$";
    
        private static final Pattern IPv4_PATTERN = Pattern.compile(IPV4_REGEX);
    
        public static boolean isValidIPV4ByCustomRegex(String ip) {
            if (ip == null || ip.trim().isEmpty()) {
                return false;
            }
            if (!IPv4_PATTERN.matcher(ip).matches()) {
                return false;
            }
            String[] parts = ip.split(".");
            try {
                for (String segment : parts) {
                    if (Integer.parseInt(segment) > 255 ||
                            (segment.length() > 1 && segment.startsWith("0"))) {
                        return false;
                    }
                }
            } catch (NumberFormatException e) {
                return false;
            }
            return true;
        }

    5. 使用JDK内置的Inet4Address做判断

    JDK从1.4版本开始就提供了

    Inet4Address
    类实现对IP的各项校验操作,结合该类的
    getByName
    getHostAddress
    方法可实现IP地址判断,但是频繁的调用这两个方法会产生一定的性能问题。以下是通过JDK判断字符串是否为IPV4地址的方式:
        public static boolean isValidIPV4ByJDK(String ip) {
            try {
                return Inet4Address.getByName(ip)
                        .getHostAddress().equals(ip);
            } catch (UnknownHostException ex) {
                return false;
            }
        }

    二、并不适合ping命令

    1. IPV4的标准格式

    本文列举的几种判断方式都是针对标准的IP地址而言,标准指的是IP地址由4位通过逗号分割的8bit长度的数字字符串组成,由于每位数字只有8bit长度,所以每个数字的值应该在0~255范围内。相关文档可以参考RFC5321。

    Java判断ip是否为IPV4或IPV6地址的方式有哪些

    2. 有效性验证

    我们选举几组字符串,有缺少位数的,有数字以0开头的,也有一组是符合标准格式的。然后通过之前列举的方法判断是否为有效的IP地址。

    测试过程就不再赘述,直接将测试用例和测试结果汇总成如下的表格:

    用例 isValidIPV4ByValidator isValidIPV6ByValidator isValidByGuava isValidIPV4ByOWASP isValidIPV4ByCustomRegex isValidIPV4ByJDK
    172.8.9.28 true false true true true true
    192.168.0.072 false false false true false false
    172.08.9.28 false false false true false false
    172.9.28 false false false false false false
    192.168.072 false false false false false false
    192.168.1 false false false false false false
    2001:0db8:85a3:0000:0000:8a2e:0370:7334 false true true false false false

    通过这7个测试用例中,不难看出:

    • 第1个IP刚好是4位,每位都在0~255之间,且没有任何一位以0开头。所有判断IPV4的方法都返回了true,符合预期。

    • 第2,3个IP也都是4位地址,但是某一位出现以0开始的数字,此时采用OWASP正则表达式的方式返回了true,其他方法都返回了false。

    • 第4,5,6个IP都是3位地址,所有方法返回了false。

    • 最后一个是合法的ipv6地址,我们通过Apache Commons Validator或者Guava包提供的判断方法能够正常返回true。

    3. 性能对比

    本文在列举的第5个判断方法时特意提到了性能问题,那么使用

    Inet4Address
    判断IP地址到底会导致多大的性能损耗呢?实验证明,当判断使用大规模非法IP地址做输入,该方法的性能损耗将不敢想象!

    下面将通过一项测试来验证这个结论。

        private static List<String> generateFakeIp(int capacity) {
            List<String> ipList = new ArrayList<String>(capacity);
            for (int i = 0; i < capacity; i++) {
                int parts = boundRandom(1, 3);
                if (chanceOf50()) { //each ip has 50% chance to be 4 parts
                    parts = 4;
                }
                StringBuilder sbBuilder = new StringBuilder();
                for (int j = 0; j < parts; j++) {
                    if (sbBuilder.length() > 0) {
                        sbBuilder.append(".");
                    }
                    StringBuilder stringBuilder = new StringBuilder();
                    if (chanceOf10()) { //each part has 10% chance to generate a fake number
                        stringBuilder.append('a');
                    } else { //each part has 90% chance to generate the correct number
                        stringBuilder.append(boundRandom(0, 255));
                    }
                    sbBuilder.append(stringBuilder);
                }
                ipList.add(sbBuilder.toString());
            }
            return ipList;
        }
        
        private static long correctCount(List<String> ipList) {
            return ipList.stream().filter(ip -> isValidIPV4ByCustomRegex(ip)).collect(Collectors.toList()).size();
        }
        
        // 50% chance
        private static boolean chanceOf50() {
            return boundRandom(0, 9) < 5;
        }
    
        // 10% chance
        private static boolean chanceOf10() {
            return boundRandom(0, 9) < 1;
        }
    
        private static Random random = new Random();
        // random int between [start, end], both start and end are included
        private static int boundRandom(int start, int end) {
            return start + random.nextInt(end);
        }

    我们通过上面的

    generateFakeIp
    方法来产生一批随机的IP地址,这些IP中有正确格式的,也有非法格式的。

    主体测试方法如下,该方法将比较

    isValidIPV4ByCustomRegex
    isValidIPV4ByJDK
    这两种判断IP地址的总耗时,以分析性能问题。
        public static void performanceTest() {
            List<String> ipList = generateFakeIp(100);
            double chance = correctCount(ipList);
            System.out.println("start testing, correct ip count is : " + chance);
            long t1 = System.currentTimeMillis();
            ipList.stream().forEach( ip-> isValidIPV4ByCustomRegex(ip));
            long t2 = System.currentTimeMillis();
            ipList.stream().forEach( ip-> isValidIPV4ByJDK(ip));
            long t3 = System.currentTimeMillis();
            System.out.println("isValidIPV4ByCustomRegex cost time : " + (t2-t1));
            System.out.println("isValidIPV4ByJDK cost time : " + (t3-t2));
        }

    直接运行后,打印以下结果。

    start testing, correct ip count is : 37.0
    isValidIPV4ByCustomRegex cost time : 2
    isValidIPV4ByJDK cost time : 13745

    可以看到,当100个IP中只有37个是合法IP时,基于正则表达式的判断方法只用了2ms,而基于JDK内置的

    Inet4Address
    实现的判断方法却用了13s,这已经不在在同一个数量级了。如果我们将测试基数再扩大,那更加不敢想象,所以实际工作中,千万不要使用
    Inet4Address
    来做IP合法性判断。

    4. 判断IPV4的方法并不适合ping命令

    对于标准IPV4格式的地址来说,以上判断方式是没问题的,但是部分非标准IPV4格式的地址,却能够被ping命令正常解析。

    对于ping命令来说,我们这里列举的第2~6个IP地址都是合法的,能够被正常解析。

    不妨验证一下:

    Java判断ip是否为IPV4或IPV6地址的方式有哪些

    可以看出,当我们输入的IP地址中,某一位数字位以0开头,那么也能被正常解析,从图片可以看出

    192.168.0.072
    被解析成了
    192.168.0.58
    172.08.9.28
    被解析成了
    172.08.9.28
    。这是为什么呢?

    当ping命令接收的IP地址中,出现以0开头的数字位,那么ping命令将尝试以八进制解析该位,八进制的072,即对应十进制的58,所以

    192.168.0.072
    就被解析成了
    192.168.0.58

    如果以0开头的数字位,不符合八进制的格式,则依然以十进制对待该数字位,并忽略最高位的0,由于

    172.08.9.28
    08
    并不是一个合法的八进制数,所以依然按十进制对待并忽略最高位的0,即实际解析成
    172.8.9.28

    此外,当输入的IP地址并不是以逗号分割的四位,ping命令依然能够正常解析。分别ping

    196.168.072
    192.168
    196
    时,实际被解析成了
    196.168.0.072
    196.0.0.168
    0.0.0.192

    Java判断ip是否为IPV4或IPV6地址的方式有哪些

    可以看出,当IP不足四位时,ping命令会在合适的位置补0,其规律如下所示:

    1 part  (ping A)       : 0.0.0.A
    2 parts (ping A.B)     : A.0.0.B
    3 parts (ping A.B.C)   : A.B.0.C
    4 parts (ping A.B.C.D) : A.B.C.D


    #发表评论
    提交评论