最近在工作中有涉及到IP有效性校验问题,按照以前的做法,习惯性百度一下“Java实现IP有效性验证”,复制相关代码到项目中,定义一个如IpUtils.java的类,实现一个校验validIp(ip)方法,然后再验证一下OK就了事。
然而这次并没有这么做,一来觉得自己对IP相关知识和Java正则表达式了解不够深,二来不想一味地只做代码搬运工,所以还是决定亲自实现这一个功能。
思路分析
既然要做就得把思路理清楚,就日常接触到的ip地址都是如“192.168.11.120”等形式,它的组成如下:
1、4个十进制数值,两个数值之间用“.”隔开
2、每个十进制数值范围为[0,255]
由此分析只需要写出一个判断数值字符串是在[0,255]之间的正则表达式“REG_IPV4_ITEM”(此处暂时这么定义),然后一个完整的IP地址正则表达式则是由4个“REG_IPV4_ITEM”和3个“.”组成,这样问题基本就能解决了。
实现过程
实现“REG_IPV4_ITEM”,如下将其分为[0,9],[10,99],[100,199],[200,249],[250,255]等区间
然后组成一个完整的“REG_IPV4”
完整代码如下:
public class IpUtils {
private static final String REG_IPV4_ITEM = "(([0-9])|([1-9]\\d)|(([1]\\d{2})|(([2][0-4]\\d)|([2][5][0-5]))))";
private static final String REG_IPV4 = REG_IPV4_ITEM + "(." + REG_IPV4_ITEM + "){3}";
private static final Pattern PATTERN_IPV4 = Pattern.compile(REG_IPV4);
public static boolean vaildIp(String ip) {
if (ip == null) {
return false;
}
return PATTERN_IPV4.matcher(ip).matches();
}
public static void main(String[] args) {
// 覆盖所有的Ip
long ipMaxNum = 0xfffffff;
for (long ipNum = 0; ipNum <= ipMaxNum; ipNum++) {
String ip = ipNum2Str(ipNum);
if (!vaildIp(ip)) {
System.out.println(ip + " not matche.");
}
}
}
private static String ipNum2Str(long ipNum) {
// 32进制ip转换为ip地址
int ip1 = (int) ((ipNum & 0xff000000) >> 24);
int ip2 = (int) ((ipNum & 0xff0000) >> 16);
int ip3 = (int) ((ipNum & 0xff00) >> 8);
int ip4 = (int) (ipNum & 0xff);
String ip = ip1 + "." + ip2 + "." + ip3 + "." + ip4;
return ip;
}
}
思考扩展
前面提到说自己的基础知识不够扎实,需要多动手实现。为什么有这个想法也是最近经常看一些关于编程思考的文章和书籍,受这些影响,也希望能通过以点带面去思考,形成一个完整的知识体系,所以之后我也对这当中的一些知识进行了梳理和扩展,得出如下一张思维导图(当然还可以继续扩展,如IP数据结构,网络相关知识,深入研究正则表达式源码,应用等)。
Java 正则表达式相关知识
在jdk-api帮助文档的“java.util.regex.Pattern”就有相关的描述,还有网上也有不少的例子,这里只讲几个注意点:
- 弄清楚正则表达式的基本语法很重要
- 使用前先进行正则表达式编译
- 用户正则表达式,事半功倍,否则事倍功半,所以需要多做练习,才能写好
- Pattern相关方法的使用
- 非捕获组的使用
通用的正则表达式语法学习可以参考http://www.runoob.com/regexp/regexp-syntax.html
PORT端口正则表达式
端口的范围是[0,65535],(其实完全没必要专门用正则表达式处理,此处主要是为URL正则表达式做铺垫),将其分为[0,9999],[10000,59999],[60000,64999],[65000,65499],[65500,
65529],[65530,65535]等区间,端口正则表达式如下
URL正则表达式
URL即为统一资源定位符,是对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示。一般表示如下(带“[]”为可选):
1、protocol :// hostname[:port] / path / [;parameters][?query]#fragment
2、protocol包括:http,https,ftp,file,ldap等等;
hostname包括:主机名,主机ip,域名等
此处我们只做最简单常用的URL正则表达式验证,即“http[s]://ip[:port]/xxx”,完整的正则表达式可以依次类推去实现。将URL正则看成是“HTTP正则表达式 + IPV4正则表达式+ PORT端口正则表达式”,后两部分在前面已经实现,而HTTP正则表达式也比较简单。完整的URL正则表达式如下:
完整代码如下:
public class IpUtils {
private static final String REG_IPV4_ITEM = "(([0-9])|([1-9]\\d)|(([1]\\d{2})|(([2][0-4]\\d)|([2][5][0-5]))))";
private static final String REG_IPV4 = REG_IPV4_ITEM + "(." + REG_IPV4_ITEM + "){3}";
private static final Pattern PATTERN_IPV4 = Pattern.compile(REG_IPV4);
private static final String REG_PORT = "((\\d{0,4})|([1-5](\\d{4}))|([6][0-4]\\d{3})|([6][5][0-4]\\d{2})|([6][5][5][0-2]\\d)|([6][5][5][3][0-5]))";
private static final Pattern PATTERN_PORT = Pattern.compile(REG_PORT);
private static final String REG_HTTP = "(http[s]?)";
private static final String REG_URL = "^(" + REG_HTTP + "://" + REG_IPV4 + "((:" + REG_PORT + ")?((/?.*)*)))$";
private static final Pattern PATTERN_URL = Pattern.compile(REG_URL);
public static boolean vaildIp(String ip) {
if (ip == null) {
return false;
}
return PATTERN_IPV4.matcher(ip).matches();
}
public static boolean vaildPort(String port) {
if (port == null) {
return false;
}
return PATTERN_PORT.matcher(port).matches();
}
public static boolean vaildSimpleUrl(String url) {
if (url == null) {
return false;
}
return PATTERN_URL.matcher(url).matches();
}
public static void main(String[] args) {
// 覆盖所有的Ip
long ipMaxNum = 0xfffffff;
for (long ipNum = 0; ipNum <= ipMaxNum; ipNum++) {
String ip = ipNum2Str(ipNum);
if (!vaildIp(ip)) {
System.out.println(ip + " not matche.");
}
}
for (int i = -5; i < 65539; i++) {
if (!vaildPort(i + "")) {
System.out.println(i + " not matche.");
}
}
String simpleURL = "https://10.23.52.22:520/ads/222/";
System.out.println(simpleURL + " vaild " + vaildSimpleUrl(simpleURL));
}
private static String ipNum2Str(long ipNum) {
// 32进制ip转换为ip地址
int ip1 = (int) ((ipNum & 0xff000000) >> 24);
int ip2 = (int) ((ipNum & 0xff0000) >> 16);
int ip3 = (int) ((ipNum & 0xff00) >> 8);
int ip4 = (int) (ipNum & 0xff);
String ip = ip1 + "." + ip2 + "." + ip3 + "." + ip4;
return ip;
}
}
IPV4相关知识
参考《TCP-IP协议族(第4版)》第5章-IPV4地址,总结大概如下:
- IPV4范围和表示方式
- IPV4分类,特殊地址
- IPV4编址,网络地址,子网掩码
- 网络划分
- NAT网络地址转换
IPV6相关知识
参考《TCP-IP协议族(第4版)》第26章-IPV6编址,总结大概如下:
- IPV6范围和记法
- 地址空间和类型
- 地址空间分配