通过正则验证IP地址是否合法

2,734 阅读6分钟

1 正则表达式

和编译原理中的文法类似,但是是只用一个字符串来描述句子的结构的规则,肉身判断正则的过程类似于英语阅读中划分句子结构。 本文当中正则的部分是根据菜鸟教程总结的

正则表达式的作用

  • 测试字符串内的模式,数据验证
  • 替换文本
  • 基于模式匹配从字符串中提取子字符串

1.1 正则表达式当中的语法

正则表达式是由普通字符以及特殊字符(称为“元字符”)组成的文字模式。

1.1.1 普通字符+元字符

  • 普通字符:包括没有显示指定为元字符的所有可打印和不可打印字符。包括所有大写字母、小写字母、所有数字、所有符号和一些其他符号
  • 元字符:靠着和普通字符组合在一起形成化学反应 |元字符| 作用 |举例| |:----|:----|:----| | \ | 将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转移符| "\n"匹配一个换行符;
    "\\"匹配"\";
    "\("匹配"("; | ^ | 匹配输入字符串开始位置 | ^Chapter [1-9][0-9]{0,1} 找到章节标题 | | 匹配输入字符串结束位置Chapter[19][09]0,1 | 匹配输入字符串结束位置 | ^Chapter [1-9][0-9]{0,1} 找到章节标题对应行 | | * | 匹配前面的子表达式子零次或多次 | zo* 能匹配"z"以及"zoo" | | + | 匹配前面的子表达式一次或多次|zo+ 能匹配"zo"以及"zoo"但不能匹配"z"| | ? | 匹配前面的子表达式零次或一次|"do(es)?"可以匹配"do"或"does"| | {n}| 精确匹配前面的子表达式n次,n为非负整数|o{2} 可以匹配"Boob"但不能匹配"Bob"| | {n,}| 最少匹配前面的子表达式n次,n为非负整数| o{3,}可以匹配"Booob"但不能匹配"Boob"| | {n,m}|最少匹配前面的子表达式子n次,且最多匹配前面的子表达式m次,m、n都是正整数且n<=m|o{1,3}能够匹配"noooob"当中前3个o| | ? | 跟在其他限制符(指*,+,?,{n},{n,m})后面,表示匹配模式是非贪婪的,匹配到的字符越少越好,原先默认是贪婪的,匹配到的越多越好|o+?匹配"noob"时只会匹配掉一个o;o+将匹配掉所有o | |(pattern)|匹配pattern并获取这一匹配,如果要匹配圆括号则使用"(" 、")" | | |(?:pattern)| 匹配pattern但不获取匹配结果(非获取匹配),不进行存储供以后使用,在使用"或"组合一个模式时很有用 | cit(?:y|ies),在单词很长的时候比直接写或显得更加简洁| |(?=pattern)|正向肯定预查|Windows(?=95|98|NT|2000)
    能够匹配"Windows2000"中的Windows,
    但不能匹配"Windows3.1"中的Windows| |(?!pattern)|正向否定预查|Windows(?!95|98|NT|2000)
    能够匹配"Windows3.1"中的Windows,
    但不能匹配"Windows2000"中的Windows| |(?<=pattern)|反向肯定预查|(?<=95|98|NT|2000)Windows
    能够匹配"2000Windows"中的Windows,
    但不能匹配"3.1Windows"中的Windows| |(?能够匹配"3.1Windows"中的Windows,
    但不能匹配"2000Windows"中的Windows| |a|b|匹配a或者匹配b|z|food能够匹配"z"或者匹配"food"| |[xyz]|字符集合,匹配包含的任意一个字符|[abc]可以匹配"plain"中的a| |[^xyz]|负值字符集合,匹配未包含的任意字符|[^abc]可以匹配"plain"中的p、l、i、n |[a-z]| 字符范围| [a-z] 可以匹配a到z范围内的所有小写字符 |[^a-z]|负值字符范围|[^a-z]可以匹配所有不在'a'到'z'范围内的任意字符| |\b|匹配到一个单词的边界|er\b可以匹配"never"中的er,但不能匹配"verb"中的"er"| |\B|匹配非单词边界|er\B可以匹配"verb"中的er,但不能匹配"never"中的er| |\d|匹配一个数字字符|相当于[0-9]| |\D|匹配一个非数字字符|相当于[^0-9]| |\f|匹配一个换页符|| |\n|匹配一个换行符|| |\r|匹配一个回车符|| |\t|匹配一个制表符|| |\v|匹配一个垂直制表符|| |\s|匹配任何空白字符|相当于[\f\n\r\t\v] |\S|匹配任何非空白字符|相当于[^\f\n\r\t\v] |\w|匹配字母、数字、下划线|等价于[A-Za-z0-9_] |\W|匹配非字母、数字、下划线|等价于[^A-Za-z0--9]

1.1.2 修饰符

修饰符不在正则表达式内部,位于表达式的外边,用于指定额外的匹配策略

/pattern/flags
修饰符含义
iignore--不区分大小,A和a没有区别
gglobal--全局匹配,查找所有的匹配项
mmulti line--多行匹配,使边界字符^和$匹配每一行的开头和结尾,而不是整个字符串的开头和结尾
s让圆点在原来能够匹配除换行符\n以外的任何字符外,也能匹配\n

1.1.3 运算符优先级

正则表达式从左到右进行计算,遵循优先级顺序,与算数表达式类似,相同优先级顺序从左到右,不同优先级运算从高到低。

优先级从高到低排列顺序为

  • \ ,转义符
  • (),(?:),(?=),[]
  • * ,+,?,{n},{n,},{n,m}
  • ^ ,$,\任何元字符、任何字符
  • |

1.2 尝试用正则表示一个邮箱

分析一下邮箱的组成

  • 首先是位于@前面的用于唯一标识用户的用户名,由一些字符组成(具体规则我也不清楚)
  • 然后是@
  • 位于@后面的是邮箱服务器域名,通常由 标号.标号表示,计算机网络第七版谢希仁记载:标号由字母和数字组成,除了连字符不能出现其他标点符号,有规定标号不能超过63个字符,为了记忆每个标号最好不要超过12个字符。但菜鸟上的邮箱正则表示域名这部分只用字母和-,好像域名中带数字见的比较少

根据上述特点,能够一步步写出相应的正则:

  • 首先是用户名开头,1个以上的字符集当中的符号的组合
    \b[\w.%+-]+
  • 单独一个@
  • 邮箱服务器域名
    第一部分表示二级域名以及之后的域名组成[\w.-]+
    第二部分一个点分隔二级域名和顶级域名 \.
    第三部分表示顶级域名结尾 [a-zA-Z]{2,6}\b 拼接在一起就是 \b[\w.%+-]+@[w.-]+\.[a-zA-Z]{2,6}\b

2 用正则验证IP地址是否合法

首先要验证的IP地址分为两类,IPV4和IPV6

2.1 IPV4地址

一般所说的IP地址默认是指IPV4地址,有32位,用点分十进制表示,每一个8位用一个十进制数(0-255)表示,不允许有零开头的数字,比如01这种。

而它的正则我们可以只写一个,然后将IPV4地址按照.分割成4部分,逐一判断;我们也可以写好一个8位后再写出完整的IPV4地址的正则

  • 首先考虑一个8位怎么写,包括所有1位数、所有两位数、100-200、200-249、250-255
String chunkIPV4 ="([0-9] | [1-9][0-9] | 1[0-9]{2} | 2[0-4][0-9] | 25[0-5])"
  • 然后用一个8位的正则拼成一个IPV4, 开头一个8位+. * 3 + 一个8位结尾
Pattern pattenIPv4 = Pattern.compile("^(" + chunkIPv4 + "\\.){3}" + chunkIPv4 + "$");

2.2 IPV6地址

IPV6采用冒号十六进制记法

  • IPV6的地址128位,用16进制数表示
  • 每组16个比特,一共8组
  • 组与组之间用 : 间隔
  • 一个16进制数可以表示4个比特,一组4个十六进制数
  • 字母不区分大小写,可以0开头但不能有多余的零(不能改变16进制数的位数)

另外,题目规定不能使用零压缩,也就是如果中间有一组16进制数为0不能将其省略为::,这个有点救命了,不然确实会变得复杂

  • 首先写出1组的正则,四个16进制数,前导0可要也可不要,不区分大小写
 String chunkIPv6 = "([0-9a-fA-F]{1,4})";
  • 然后用其拼成IPV6的正则, 开头 一个组 + : * 7 + 一个组结尾
Pattern pattenIPv6 = Pattern.compile("^(" + chunkIPv6 + "\\:){7}" + chunkIPv6 + "$");

2.3 代码

如果包含.代表要匹配的是IPV4,如果包含:代表要匹配的是IPV6,否则直接不匹配

import java.util.regex.Pattern;
class Solution {
  String chunkIPv4 = "([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])";
  Pattern pattenIPv4 =
          Pattern.compile("^(" + chunkIPv4 + "\\.){3}" + chunkIPv4 + "$");

  String chunkIPv6 = "([0-9a-fA-F]{1,4})";
  Pattern pattenIPv6 =
          Pattern.compile("^(" + chunkIPv6 + "\\:){7}" + chunkIPv6 + "$");

  public String validIPAddress(String IP) {
    if (IP.contains(".")) {
      return (pattenIPv4.matcher(IP).matches()) ? "IPv4" : "Neither";
    }
    else if (IP.contains(":")) {
      return (pattenIPv6.matcher(IP).matches()) ? "IPv6" : "Neither";
    }
    return "Neither";
  }
}