今天小编给大家分享一下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。
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地址都是合法的,能够被正常解析。
不妨验证一下:
可以看出,当我们输入的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
可以看出,当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