字符串是不可变的,也就是说当字符串的内容发生改变的时候,会创建一个新的String对象;但是如果内容没有发生改变的时候,String类的方法会返回原字符串对象的引用。
而正则表达式往往都和字符串相关,而且很多String对象中的方法都可以使用正则表达式来作为参数,比如:replace();方法,split();方法等,这些方法因为支持正则表达式,便可以以少量代码完成很多灵活强大的功能,比如:字符检索、替换、筛选等。那么废话不多说,便开始来进入正则表达式的介绍吧!
格式化输出
在C语言中,有着printf()方法用于格式化输出,不仅使用简单,而且功能很强大,可以做各种格式的打印输出。比如:
printf("var x is %d ", x);
在printf();方法的第一个参数为字符串,在字符串中使用了占位符"%d",表示此处是一个整数型的变量值;而字符串之后的参数,便是对字符串中的占位符的填充,参数可以有多个;在本例中:是把变量x的值填充到"%d"的位置,如果变量x的值是13,那么上述代码最后的打印效果就是:
var x is 13
Java 中的格式化输出
1.printf();
在早期的Java 版本中,是没有格式化输出的,但是从Java 5 开始,Java 也提供了格式化输出这一项功能,方法名称也叫printf();,该方法的调用和C语言中的printf方法大同小异,代码案例如下:
int x = 13;// 传统的输出打印System.out.println("var x is " + x);// 使用printf(); 格式化打印输出System.out.printf("var x is %d ", x);
虽然方法名称和C语言中的printf();方法一样,但两者还是有很多不同之处,其中,最大的一个不同之处便是:在C语言中的prtinf();方法中,不能使用 "+" 来做字符串连接;
2.format();
同样在Java 5中引入的格式化方法还有:format();,format();方法和printf();方法的使用都是一样的,参数都是一个格式化的字符串加上一个或者多个的字符串占位符填充,代码案例如下:
int x = 13;// 使用format(); 格式化打印输出System.out.format("var x is %d ", x);
3.String.format();
在String类中,有一个静态方法String.format();,也能用于格式化字符串,它能接受和printf();、format();这两个方法一样的参数,在完成字符串格式化后,返回格式化后的字符串;案例代码如下:
int x = 13;String format = String.format("var x is %d ", x);System.out.println(format);
不同于printf();、format();这两个方法的是,String.format()方法返回的是一个String对象,printf();、format();返回的是一个PrintStream对象,可直接用于打印输出。
4.Formatter类
无论是printf();方法,还是format();方法,亦或者String.format();方法,他们的内部都是调用java.util.Formatter类的format方法来做格式化的;故,可以把Formatter类看作一个翻译器,负责将占位符字符串与数据翻译成想要的结果。
由于底层都是使用Formatter类来做格式化处理的,所以我们也可以直接使用Formatter类来做格式化处理,直接调用Formatter类的format方法,案例代码如下:
import java.math.*;import java.util.*;// Formatter类 案例public class FormatterDemo { public static void main(String []args) { Formatter formatter = new Formatter(System.out); char c = 'a'; formatter.format("c : %c", c); int i = 13; formatter.format("i : %d", i); BigInteger bigInt = new BigInteger(10000000000000"); formatter.format("bigInt : %d ", bigInt); }}
在格式化的字符串中,不同的占位符都有着各自的意义,表示着不同类型的数据类型:
% : 表示字符“%”, 在格式化的字符串中表示占位符d : 十进制的整数类型c : 表示Unicde字符b: 表示Boolean 类型的值s: 表示String 类型的值f: 表示十进制的浮点数(包括float、double类型的数据)e: 表示科学计数的浮点数x: 表示十六进制的整数h: 表示十六进制的散列码
在上述的案例中,“%d”表示的就是十进制的整数类型的占位符,“%c”表示的就是Unicode字符类型的占位符,其他的占位符根据相同的格式使用即可。
正则表达式
正则表达式(英文:Regular Expression,在代码中常简写为regex、regexp或RE),是对字符串(包括普通字符和特殊字符)操作的一种逻辑公式,用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。
正则表达式是一种文本模式,是一种强大且灵活的文本处理工具;通过正则表达式,可以构建复杂的文本模式,对输入的字符串进行过滤和搜索,解决字符串处理的相关问题,比如:匹配、分隔、替换、编辑和验证等。
一般地,正则表达式被定义为以某种方式描述字符串,比如要在一段文本中查找数字,正则表达式就为"d+",前一个反斜杠“”表示转义,剩下的“d”表示一个数字,“+表示一个或者多个”;
public static void main(String[] args) {System.out.println("7897979".matches("d+"));}
案例运行结果为:true。
对比其他语言中的正则表达式,Java 语言对反斜杠“”的处理会有所不同,正则表达式:“”,表示的就是一个普通的反斜杠,也是同样的道理,前一个反斜杠“”表示转义;如果想要表示“”,正则表达式就得是“”。
正则表达式字符
在JDK文档中的java.util.regex.Pattern的文档中,详细介绍了Java 中的正则表达式字符,下面是一些常用的:
1.字符
x 字符xxhh 十六进制值为0xhh的字符甥桨桨 十六进制表示为0xhhhh的Unicode字符 制表符Tab 换行符 回车f 换页e 转义(Escape)
2.字符类
. 任意字符[abc] 包含a、b、c的任意字符,与(a|b|c)作用相同[^abc] 除了a、b、c的之外的任意字符[a-zA-Z] 从a-z或者A-Z的任意字符[abc[hij]] 包含a、b、c、h、i、j的任意字符,与(a|b|c|h|i|j)作用相同[a-z&&[hij]] 从a-z中的包含h、i、j的任意字符s 空白字符(包括空格、tab、换行、换页、回车)S 非空白字符,与[^s]作用相同d 数字[0-9]D 非数字[^0-9]w 词字符[a-zA-Z0-9]W 词字符[^w]
3.逻辑操作符
XY X在Y之后X|Y X或Y(X) 捕获组(capturing group),可在表达式中用i引用第i个捕获组
4.边界匹配符
^ 每行的开头$ 每行的结尾b 词字符的边界B 非此字符的边界G 前一个匹配的结束
下面就通过一个案例展示上述正则表达式字符的使用:
// 写出能匹配字符 "laofu13"的正则表达式public class RegexDemo {public static void main(String[] args) { String regex = "([a-z]+[d]+)"; System.out.println("laofu13".matches(regex)); }}
由于上述的正则表达式字符表示的都只是单个字符,因此,如果要匹配多个字符,就要使用"+"来表示一个或多个字符。
正则表达式量词
- 贪婪型:除非有特殊的选项设置,否则贪婪型的表达式会对文本做尽可能多的匹配;就算文本的开头就能被表达式匹配,贪婪型的表达式依然会继续往下匹配;
- 勉强型:此种量词也称懒惰的、非贪婪型的,勉强型的表达式会对文本做最少的匹配;
- 占有型:此种量词是Java 语言专有的。正则表达式在匹配时,会产生很多的状态,以便在匹配失败时可以回溯;占有型的表达式不保存这些状态,因此可以防止回溯,可以保证表达式有效执行。
在上述的正则表达式量词中,"+"表示一个或者多个,"*"表示零个或者多个,"?"表示一个或者零个。
正则表达式中的字符序列
在Java 中的CharSequence类是用来表示字符序列的,在字符序列中定义了一些用于操作字符序列的api,而Java 中大部分与字符串相关的类都实现了CharSequence接口,所以正则表达式操作都能接受CharSequence类型的参数。
split() 方法
String类中的split();方法可用于拆分字符串为字符串数组,split();方法能接受正则表达式参数,把字符串从匹配正则表达式的地方拆分开。代码案例如下:
public class SplitDemo { public static void main(String[] args) { String str = "laofu13老夫"; String strArray = str.split("d+"); System.out.println(Arrays.toString(strArray)); }}
代码案例运行结果如下:
[laofu, 老夫]
字符串拆分成功。此外,split();方法还有另一个重载方法:
String[] split(CharSequence input, int limit);// 方法的limit参数用于限制分割成字符串的数量,但如果不传limit参数,将不会做限制
Pattern 和 Matcher
Pattern 类和 Matcher类是Java 中用于构建正则表达式对象的,进行正则匹配的类;尽管String类也支持正则表达式,但毕竟功能有限,Pattern 类和 Matcher类了功能更加强大的正则,而且是专为正则表达式提供的。
在JDK API文档中提供了这两个类的调用示例:
Pattern p = Pattern.compile("a*b"); Matcher m = p.matcher("aaaaab"); boolean b = m.matches();
最后的boolean类型的b表示的就是匹配结果。根据文档中代码示例,就可以编写自定义的正则表达式案例:字符串"laofu13lafulaofufulao"中,"fu"至少出现一次的位置是哪几个?
import java.util.regex.Matcher;import java.util.regex.Pattern;// 正则表达式案例public class RegexDemo { public static void main(String[] args) { String str = "laofu13lafulaofufulao"; Pattern pattern = Pattern.compile("(fu){1,}"); Matcher matcher = pattern.matcher(str); while (matcher.find()) { System.out.println("Match "" + matcher.group() + "" at positions " + matcher.start() + "-" + (matcher.end() - 1)); } }}
案例运行结果如下:
Match "fu" at positions 3-4Match "fu" at positions 9-10Match "fufu" at positions 14-17
Pattern.compile();方法能根据正则表达式生成Pattern对象;Pattern对象的matcher方法接收要检索的字符串作为参数,生成一个Matcher对象;Matcher对象会做最终的正则匹配。在Pattern对象中也提供了split();方法和matches();方法,实现效果和String类中的同名方法是一样的。
find();方法
find();方法用于在字符串中查找正则匹配,如果有匹配,返回true,否则返回false;find();方法还支持传入一个int类型的参数,如果未传入,find();方法会像迭代器那样依次往前检索字符串;如果传入了该参数,就可以用于不断重新设置字符串检索的起点。则上述的正则表达式案例就可以稍作修改以实现起始位置的重新设置:
import java.util.regex.Matcher;import java.util.regex.Pattern;// 正则表达式案例public class RegexDemo { public static void main(String[] args) { String str = "laofu13lafulaofufulao"; Pattern pattern = Pattern.compile("(fu){1,}"); Matcher matcher = pattern.matcher(str); // 检索过程中会不断重新设置搜索的起始位置 int i = 1; while (matcher.find(i)) { System.out.println("Match "" + matcher.group() + "" at positions " + matcher.start() + "-" + (matcher.end() - 1)); i++; } }}
作修改后的案例运行结果如下:
Match "fu" at positions 3-4Match "fu" at positions 3-4Match "fu" at positions 3-4Match "fu" at positions 9-10Match "fu" at positions 9-10Match "fu" at positions 9-10Match "fu" at positions 9-10Match "fu" at positions 9-10Match "fu" at positions 9-10Match "fufu" at positions 14-17Match "fufu" at positions 14-17Match "fufu" at positions 14-17Match "fufu" at positions 14-17Match "fufu" at positions 14-17Match "fu" at positions 16-17Match "fu" at positions 16-17
由于循环是从0开始的,然后每次检索都会自增1,也就是说检索其实是从字符串第一个字符开始的,然后依次每个字符进行检索,一旦匹配成功,find();方法便会返回true。
第一个匹配的"fu"字符串前有3个字符,所以从前3个字符的位置开始匹配的,匹配到的都是索引位置为3-4的"fu";
第二个匹配的"fu"字符串和第一个匹配的"fu"字符串之间有6个字符,所以第二个匹配的"fu"字符串,会6次匹配到索引位置为9-10的"fu";
以此类推。
组(group)
正则表达式中的“组”用一对括号划分,一对括号就是一个组。组号为0表示整个正则表达式,组1表示被第一对括号包围的表达式,组2便是被第二对括号包围的表达式,以此类推。比如有以下的正则表达式:
A(B(C))D
在上述表达式中,组0是ABCD,组1是BC,组2是C。Matcher对象中也提供了一系列针对组操作的方法:
int groupCount(); // 返回该匹配模式中的分组数量,但是第0组不算在其中String group();// 返回前一次匹配操作的整个匹配String group(int i);// 返回前一次匹配操作的指定组号的匹配,如果没有匹配,返回nullint start(int group);// 返回前一次匹配操作中匹配到的组的起始索引int end(int group);// 返回前一次匹配操作中匹配到的组的最后一个字符索引+1的值
案例代码如下:
import java.util.regex.Matcher;import java.util.regex.Pattern;// 正则表达式分组案例public class RegexGroupDemo { public static void main(String[] args) { String str = "laofu is 13 also dasheng." + "hello world hello Java hello"; Pattern pattern = Pattern.compile("(S+)s+(S+)s+"); Matcher matcher = pattern.matcher(str); while (matcher.find()) { for (int i = 0; i < matcher.groupCount(); i++) { System.out.print("[" + matcher.group(i) + "]"); } System.out.println(); } }}
案例代码运行结果如下:
[laofu is ][laofu][13 also ][13][dasheng.hello ][dasheng.][world hello ][world][Java hello][Java]
reset();方法
通过Matcher对象的reset();方法,可重新设置Matcher对象检索的字符序列;
import java.util.regex.Matcher;import java.util.regex.Pattern;// 正则表达式reset();案例public class ResetDemo { public static void main(String[] args) { String str = "laofu13lafulaofufulao"; Pattern pattern = Pattern.compile("(fu){1,}"); Matcher matcher = pattern.matcher(str); while (matcher.find()) { System.out.println("Match "" + matcher.group() + "" at positions " + matcher.start() + "-" + (matcher.end() - 1)); } System.out.println("重新设置检索的字符序列 :" ); // 重新设置检索的字符序列 matcher.reset("13lafu4fu59fulao"); // 重新匹配字符序列 while (matcher.find()) { System.out.println("Match "" + matcher.group() + "" at positions " + matcher.start() + "-" + (matcher.end() - 1)); } }}
案例运行如下:
Match "fu" at positions 3-4Match "fu" at positions 9-10Match "fufu" at positions 14-17重新设置检索的字符序列 :Match "fu" at positions 4-5Match "fu" at positions 7-8Match "fu" at positions 11-12
replace(); 方法
String类中提供了多个替换方法用于将匹配正则表达式的内容替换为指定的字符序列,比如:
Stringreplace(CharSequence target, CharSequence replacement);// 用指定的字符序列替换字符串中每一个匹配的目标字符序列为指定的内容StringreplaceAll(String regex, String replacement);// 替换字符串中所有匹配正则表达式的内容为指定的字符序列StringreplaceFirst(String regex, String replacement)// 替换字符串中第一个匹配正则表达式的内容为指定的字符序列
上述这些方法都是String类中提供的,用于直接操作字符串,要么替换第一个,要么替换全部;而在Matcher对象中提供了一个可以执行渐进式替换的方法:appendReplacement(StringBuffer sbuf, String replacement);,此方法允许调其他的方法来处理replacement,从而可以完成更强大、灵活的字符串操作;案例如下:
import java.util.regex.Matcher;import java.util.regex.Pattern;// 正则表达式appendReplacement();案例public class AppendReplacementDemo { public static void main(String[] args) { String str = "laofu13lafulaofufulao"; Pattern pattern = Pattern.compile("(fu){1,}"); Matcher matcher = pattern.matcher(str); // 完成相关操作后的字符串对象 StringBuffer sbuf = new StringBuffer(); while (matcher.find()) { matcher.appendReplacement(sbuf, appendReplacement); } // System.out.println(sbuf); }}
案例中使用的appendTail();方法作用是把余下未匹配的字符复制到sbuf中,所以案例最终的运行结果中,把字符串中匹配的内容替换为全大写的内容。详情如下:
laoFU13laFUlaoFUFUlao
Pattern 标记
Pattern 类中的compile();方法还有一个重载版本:
static Patterncompile(String regex, int flags);
该方法除了正则表达式regex外,还接受一个flag标记,可以对匹配结果做调整;flag标记有如下的可选项:
static intCANON_EQ// 启用规范对等static intCASE_INSENSITIVE// 忽略大小写// 也可以通过嵌入式标志表达式(?i)启用该模式static intCOMMENTS// 空格符和注释会被忽略// 也可以通过嵌入式标志表达式(?x)启用该模式static intDOTALL// "."会匹配所有字符// 也可以通过嵌入式标志表达式(?s)启用该模式static intLITERAL// 启用模式的字面解析static intMULTILINE// ^匹配一行的开始,$匹配一行的结束// 也可以通过嵌入式标志表达式(?m)启用该模式static intUNICODE_CASE// 在这个模式下,如果你还启用了CASE_INSENSITIVE标志,// 那么它会对Unicode字符进行大小写不明感的匹配。// 也可以通过嵌入式标志表达式(?u)启用该模式static intUNICODE_CHARACTER_CLASS// 启用Unicode版本的Predefined字符类和POSIX字符类。static intUNIX_LINES// 在.、^和$行为中,值识别终结符// 也可以通过嵌入式标志表达式(?d)启用该模式
在方法中,可以同时使用多个flag标记,多个flag标记之间使用"|"分隔:
import java.util.regex.Matcher;import java.util.regex.Pattern;// 正则表达式 flag标记案例public class FlagsDemo { public static void main(String[] args) { String str = "laofu13laofuLaofufuLAO"; Pattern pattern = Pattern.compile("^lao", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); Matcher matcher = pattern.matcher(str); // 完成相关操作后的字符串对象 StringBuffer sbuf = new StringBuffer(); while (matcher.find()) { System.out.println(matcher.group()); } }}
案例运行结果中匹配到了所有忽略大小写的,以lao开头的行的内容,详情如下:
laolaoLaoLAO
扫描输入
Scanner 定界符
在默认情况下,Scanner会根据空白字符对输入内容进行分词,但是也能使用正则表达式自定义分界符:
import java.util.regex.Matcher;import java.util.regex.Pattern;import java.util.Scanner;// Scanner 定界符案例public class DelimiterDemo { public static void main(String[] args) { Scanner sc = new Scanner("13, laofu, ds, hello"); // 设置定界符 sc.useDelimiter("s*,s*"); while (sc.hasNext()) { System.out.println(sc.next()); } }}
案例中,使用useDelimiter();方法来设置定界符,就这样,Scanner中的输入被我们定义的定界符一一拆分开了:
13laofudshello
扫描输入
除了设置定界符之外,还可以在Scanner中使用正则表达式来扫描输入,筛选出特定的内容:
import java.util.regex.*;import java.util.*;// Scanner 扫描输入案例public class ScannerRegexDemo { public static void main(String[] args) { String data = "13.14.13.14@02/02/2020" + "15.16.15.16@02/03/2020" + "17.18.17.18@02/05/2020" + "[以上这些ip地址需要注意!]"; Scanner sc = new Scanner(data); String pattern = "(d+[.]d+[.]d+[.]d+)@(d{2}/d{2}/d{4})"; while (sc.hasNext(pattern)) { sc.next(pattern); MatchResult match = sc.match(); String ip = match.group(1); String date = match.group(2); System.out.format("ip: %s, date: %s ", ip, date); } }}
案例运行结果如下:
ip: 13.14.13.14, date: 02/02/2020 ip: 15.16.15.16, date: 02/03/2020 ip: 17.18.17.18, date: 02/05/2020
案例中,Scanner 和 正则表达式配合使用,构建出了强大、灵活的字符串分词工具,而且使用简单,上手简单,以少量代码便可完成强大的功能。
小结
- 格式化输出,以多种不同的格式化输出的案例引出正则表达式;
- 正则表达式,详述正则表达式语法,每一个字符、字符类所表示的含义,以及相关的逻辑操作符;通过多个案例,从0开始,一步步构建强大的正则表达式及其相关操作;
- Pattern 和 Matcher,Pattern 和 Matcher两个类是Java 正则表达式对象的核心,通过这两个类便可完成正则表达式对象的构建,然后调用对象中的方法来完成对正则表达式的操作;
- Pattern 标记,借由Pattern 类中提供的flag,可对正则表达式的匹配结果做调整,比如排序;
- 扫描输入,正则表达式还可以应用在扫描输入中,设置定界符来完成特定输入的格式化,Scanner 和 正则表达式配合使用,构建出了强大、灵活的字符串分词工具。
完结,老夫虽不正经,但老夫一身的才华!关注我,收获更多编程基础知识。