String类replaceAll方法正则替换深入分析
背景:
前几天有人发了一个关于下面问题的贴,对这个有点好奇,故花时间做了点研究。
对单个反斜杠字符串替换成双斜杠的Java实现如下:
String s = "\";
方法一:String sr1 = s.replaceAll("\\", "\\\\");
方法二:String sr1 = s.replaceAll("\\", "$0$0");
我第一眼看到比较困惑,下面慢慢来分析。
分析:
对String类的replaceAll(String reg, String replacement)方法分析
一、两点疑惑
A. 为啥第一个参数reg必须是”\\”?
B. 为啥第二个参数replacement 必须是”\\\\”?
二、解答
A.因为reg这个参数表示一个正则表达式,首先字符串“\\”被转义后代表的实际是字符串\,这就是正则表达式,那么在正则表达式里也有转义,那么这个正则匹配的就是
B.首先字符串“\\\\”被转义后实际代表的其实是字符串\\;
接下来才是重点:
查看源码replaceAll方法的实现如下
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
其中Pattern.compile(regex).matcher(this), 返回的是一个Matcher对象。 先简单介绍java.util.regex.Matcher类,是通过解释 Pattern 对 字符序列执行匹配操作的引擎,其中持有对当前Pattern对象和当前String对象的引用。 执行一次调用其find方法,即对字符串执行一次从左向右的以Pattern为正则的匹配,并记录下匹配结果字符串的开始和结束位置索引,以及更新一个记录当前匹配结果的分组groups。 顺藤摸瓜,进入Matcher类的replaceAll方法,继续查看源码,
public String replaceAll(String replacement) {
// 对当前Matcher类进行重置,即对其中记录匹配结果的开始和结束位置索引,以及分组信息重置
reset();
// 执行第一次搜索
boolean result = find();
if (result) {
// 第一次搜索匹配成功
// 用于记录最终的替换结果字符串
StringBuffer sb = new StringBuffer();
// 循环搜索
do {
// *重点在此方法内:用于将从上一次匹配子字符串的下一个索引位置开始,到当前匹配的子字符串的结束索引位置的所有字符 append到字符串sb中
// 有点绕,可以暂时跳过,下面会对该方法进一步分析
appendReplacement(sb, replacement);
result = find();
} while (result);
// 将从最后一次匹配子字符串的下一个索引位置,到字符串的结尾的所有字符append到字符串sb中
appendTail(sb);
return sb.toString();
}
return text.toString();
}
继续南下,进入Matcher类的appendReplacement方法,
public Matcher appendReplacement(StringBuffer sb, String replacement) {
...省略部分代码
// 用于跟踪replacement字符串的索引
int cursor = 0;
String s = replacement;// Java api源码也有垃圾代码啊,呵呵 (s局部变量并未在后续代码中被使用)
// 对当前匹配到子字符串替换后的结果字符串
StringBuffer result = new StringBuffer();
// 遍历replacement字符串
while (cursor < replacement.length()) {
char nextChar = replacement.charAt(cursor);
if (nextChar == '\') {
// 重点1:当字符为时,跳过,并获取其后面的字符,追加到result
cursor++;
nextChar = replacement.charAt(cursor);
result.append(nextChar);
cursor++;
} else if (nextChar == '$') {
// 重点2:当字符为$时,跳过,并获取其后面的数值,并以此如果$后面第一个不为数字则抛异常,
// Skip past $
cursor++;
// The first number is always a group
int refNum = (int)replacement.charAt(cursor) - '0';
// 此处代码用于计算$符号后的数值,数值结果赋予refNum
...省略部分代码
// group(refNum) 用于获取正则表达式第refNum个分组表示的字符串,不详说了
if (group(refNum) != null)
result.append(group(refNum)); // 追加到result
} else {
// 当前字符不为 或 $ 则直接追加到result
result.append(nextChar);
cursor++;
}
}
// 将从上一次匹配的子字符串的结尾索引,到当前匹配的第一个字符串索引的字符串追加到sb
// lastAppendPosition参数为上一次执行appendReplacement方法最后追加的字符在原始字符串中的索引位置。
// first 参数为当前待替换的子字符串的首个字符在原始字符串中的索引位置
sb.append(getSubSequence(lastAppendPosition, first));
// 将当前配置子字符串替换后的结果字符串追加到sb
sb.append(result.toString());
// 更新lastAppendPosition,供下一个匹配执行appendReplacement方法使用
lastAppendPosition = last;
/*
到此, sb中追加了当前匹配的子字符串与前一次匹配子字符串中间的字符,以及当前匹配子字符串被替换后的字符串
*/
return this;
}
分析结束。 总结 1、replaceAll中第二个参数replacement中,有转义的作用,$用于获取分组匹配的当前子字符串 现在想想为什么要引入这个转义的功能? 我的猜测是 ----- 因为引入了$符的分组功能,所以为了解决能输出$字符,故引入转义功能 2、有助于理解Java的正则表达式; 3、世界上没有十全十美的代码,Java源码里也有垃圾代码,呵呵。 提供几个问题大家可以实践下: 1、对两个反斜杠字符串每个字符串都替换成双斜杠,如何实现? 即String s = "\\"; 替换成 String sr = "\\\"; 2、将单反斜杠替换成美元符,如何实现? 即String s = "\"; 替换成 String sr = "$"; 3、String s = "Jack is Rose's boyfriends." 使用$分组替换功能 替换成 String sr = "Rose is Jack's girlfriends."
- C/C++ Development using Visual Studio Code, CMake and LLDB
- TensorFlow-10-基于 LSTM 建立一个语言模型
- jquery及原生javascript对jsonp解决跨域问题实例详解
- css负边距之详解
- Python进阶教程(三)
- Python进阶教程(二)
- Python进阶教程(一)
- TensorFlow-11-策略网络
- 对比requirejs更好的理解seajs
- 深入浅出Logistic Regression之二分类
- 如何自动生成文本摘要
- Kaggle 神器 xgboost
- 从 0 到 1 走进 Kaggle
- Adaboost 算法
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Day62:二叉搜索树的第k个结点
- CentOS7下利用SRS搭建直播流媒体服务器
- 第9天:NLP补充——需要的基本知识
- 6.AVCodecContext和AVCodec
- WebAssembly之使用JS调用C/C++接口
- WebAssembly之emcc编译命令
- 食堂店小二儿教你学会栈
- Istio流量管理实现机制深度解析-基于1.4.0更新
- Istio 运维实战系列(1):应用容器对 Envoy Sidecar 的启动依赖问题
- R语言基于Reactome数据库的富集分析
- WiredTiger存储引擎之五:与事务相关的数据结构以及并发控制机制
- Tomcat NIO(8)-Poller线程的阻塞与唤醒
- 你的第一个React App (一 ) - 项目初始化
- 被JDK坑的没商量?来试试这些方法吧
- k8s 代码走读---client-go 编程交互测试代码