Java 一步一步实现高逼格的字符串替换工具(一)
Java 一步一步实现高逼格的字符串替换工具(一)
如果你有一段模板, 需要用某些数据替换其中的关键信息,怎么做
"hello, {user}, welcome to {place}!"
通过传不同的user, 和 place 来输出不同的文案
##1.一般做法
用String.replaceAll 来进行替换就好了, 无非是多调用几遍,代码写起来也简单,如下
@Test
public void testReplace() {
String text = "hello {user}, welcome to {place}!";
String user = "Lucy";
String place = "China";
String res = text.replace("{user}", user).replace("{place}", place);
System.out.println(res); // 输出 hello Lucy, welcome to China!
}
上面看着也没什么问题,实现起来也不难,实际呢 ? 如果我想要一个通用的替换方法, 如下面的接口定义, 约定text中用大括号包起来的由后面的参数进行替换
2. 通用的工具怎么玩
要求实现下面这个接口,text为需要被替换的字符串, 用后面的参数来替换text中用 {}
包含起来的内容
public String replace(String text, String ... args);
这时,该怎么用上面的方法来实现替换呢 ?
如果有了解过 MessageFormat
的同学,估计很快就能想到,这个工具就是jdk提供给我们来实现文本格式化的利器,那么简单的实现如下
public String replace(String text, Object ... args) {
return MessageFormat.format(text, args);
}
@Test
public void testReplace2() {
String text = "hello {0}, welcome to {1}!";
String user = "Lucy";
String place = "China";
String ans = replace(text, user, place);
System.out.println(ans); // 输出 hello Lucy, welcome to China!
}
仔细瞅瞅,实现了我们的部分需求,但是还不完美,上面的实现要求{}
中的是后面参数再参数列表中的下标,而我们希望直接在 {}
中填写参数名, 直接用后面的参数名来替换, 这个时候可以怎么处理 ?
3. 进阶
要实现也简单,我自己先用正则把你的参数捞出来,然后替换成下标数字就可以了,麻烦的无非是如何写正则, 如何获取参数名罢了,正则还好讲,参数名的话如果不想用反射,那么直接改造下 传参的方式即可,丢一个map
进去就完美了
// 获取patter的过程较为负责,这里初始化时,做一次即可
private static Pattern pattern;
static {
pattern = Pattern.compile("((?<=\{)([a-zA-Z_]{1,})(?=\}))");
}
public String replaceV2(String text, Map<String, Object> map) {
List<String> keys = new ArrayList<>();
// 把文本中的所有需要替换的变量捞出来, 丢进keys
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
String key = matcher.group();
if (!keys.contains(key)) {
keys.add(key);
}
}
// 开始替换, 将变量替换成数字, 并从map中将对应的值丢入 params 数组
Object[] params = new Object[keys.size()];
for (int i = 0; i < keys.size(); i++) {
text = text.replaceAll(keys.get(i), i + "");
params[i] = map.get(keys.get(i));
}
return replace(text, params);
}
@Test
public void testReplaceV2() {
String text = "hello {user}, welcome to {place}! {place} is very beautiful ";
Map<String, Object> map = new HashMap<>(2);
map.put("user", "Lucy");
map.put("place", "China");
String res = replaceV2(text, map);
System.out.println(res); // hello Lucy, welcome to China! China is very beautiful
}
这下是不是就完美的实现你的需求?
上面的实现,功能是满足了,但是又是正则,又是替换,又是 调用MessageFormat.format
, 这么多步骤,这不是我想要的结果,干嘛不直接再 MessageFormat.format
中就把功能实现了,作为一个有追求的人,怎么能容忍这种曲线救国!!!(讲道理,我是个完全没追求的人)
先捋一把MessageFormat
的实现源码,然后发现上面有个坑,当被替换的是Long型数据时,输出有点鬼畜
@Test
public void testReplaceV2() {
String text = "hello {user}, welcome to {place}! now timestamp is: {time} !";
Map<String, Object> map = new HashMap<>(2);
map.put("user", "Lucy");
map.put("place", "China");
map.put("time", System.currentTimeMillis());
String res = replaceV2(text, map);
System.out.println(res); // 输出 : hello Lucy, welcome to China! now 2stamp is: 1,490,619,291,742 !
}
根本原因替换时, 对数字进行了格式化,没三个加一个,解决方法也比较简单,不传数字就可以了(就是这么粗暴)
更新后的代码
public String replaceV2(String text, Map<String, Object> map) {
List<String> keys = new ArrayList<>();
// 把文本中的所有需要替换的变量捞出来, 丢进keys
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
String key = matcher.group();
if (!keys.contains(key)) {
keys.add(key);
}
}
// 开始替换, 将变量替换成数字, 并从map中将对应的值丢入 params 数组
Object[] params = new Object[keys.size()];
for (int i = 0; i < keys.size(); i++) {
text = text.replaceAll(keys.get(i), i + "");
params[i] = map.get(keys.get(i) + "");
}
return replace(text, params);
}
如果你硬是要扣细节,要实现第二节里面定义的格式,不想传map,这个时候可以怎么玩?
--- 我也不知道怎么玩... 用反射后去的参数名是定义的参数名,如果你的接口定义的是可变参数,实际使用的时候就是一个数组了,这个时候想获取实际传入的参数名就无能为力了
并不完美,在正则获取结果之后,直接替换结果就好了,干嘛还要重复多次一举!!!,下面这样不也可以实现要求么
public String replaceV3(String text, Map<String, Object> map) {
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
String key = matcher.group();
text = text.replaceAll("\{" + key + "\}", map.get(key) + "");
}
return text;
}
@Test
public void testReplaceV3() {
String text = "hello {user}, welcome to {place}! {place} is a beautiful place!";
Map<String, Object> map = new HashMap<>(2);
map.put("user", "Lucy");
map.put("place", "China");
String res = replaceV3(text, map);
System.out.println(res); // hello Lucy, welcome to China! China is a beautiful place!
}
上面这种看起来比起前面的正则,捞出来后又去调用 MessageFormat.format
要好多了, 但是也有点问题
- 替换的数据量大时, replaceAll 的性能不咋的
- 如果是对一个模板进行批量替换时,改怎么做?
对于批量替换,显然采用前面的方案实现起来简单且高效多了, 简单的改造下即可
public List<String> replaceV4(String text, List<Map<String, Object>> mapList) {
List<String> keys = new ArrayList<>();
// 把文本中的所有需要替换的变量捞出来, 丢进keys
Matcher matcher = pattern.matcher(text);
int index = 0;
while (matcher.find()) {
String key = matcher.group();
if (!keys.contains(key)) {
keys.add(key);
// 开始替换, 将变量替换成数字,
text = text.replaceAll(keys.get(index), index + "");
index ++;
}
}
List<String> result = new ArrayList<>();
// 从map中将对应的值丢入 params 数组
Object[] params = new Object[keys.size()];
for (Map<String, Object> map: mapList) {
for (int i = 0; i < keys.size(); i++) {
params[i] = map.get(keys.get(i) + "");
}
result.add(replace(text, params));
}
return result;
}
4. 进阶++
对于上面的实现还是不满意,要求既高效、还可以选择并发替换、还能支持批量
需求会越来越高级,想一想该怎么实现上面的需求呢!
详情静待下一篇,主要是借鉴 MessageFormat
的实现原理, 想实现这样的功能当然是自己动手写才是真理
- 手写快排模版
- COGS 68. [NOIP2005] 采药【01背包复习】
- UESTC 30 &&HDU 2544最短路【Floyd求解裸题】
- 我的第一个网页制作:Hello World!
- UESTC 1584 Washi与Sonochi的约定【树状数组裸题+排序】
- Hyperledger - 超级账本项目:简介,安装,案例
- 我的第三个网页制作:b、i、s、u、sub、sup标签的使用
- 【AlphaGo Zero 核心技术-深度强化学习教程代码实战04】Agent类和SARSA算法实现
- 我的第二个网页制作:p,hn,br标签的使用
- 超级账本项目:架构设计
- 我的第四个网页制作:列表标签
- “盛大游戏杯”第15届上海大学程序设计联赛夏季赛暨上海高校金马五校赛题解&&源码【A,水,B,水,C,水,D,快速幂,E,优先队列,F,暴力,G,贪心+排序,H,STL乱搞,I,尼姆博弈,J,差分dp
- 虎嗅主站盲打成功(已进后台)
- 我的第五个网页制作:pre、html转义、abbr标签的使用
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- SwiftUI: 使用 Touch ID 和 Face I
- Linux 系统中查找正在运行的进程的完整命令、当前工作目录等信息的方法
- Go by Example 中文:通道方向
- mycat数据库集群系列之mysql主从同步设置
- Tun/Tap接口使用指导
- Swift中? 、! 和 ??
- 故障分析 | 记一次 MySQL 主从双写导致的数据丢失问题
- 集成 SpringBoot 2.3.2 + Shiro 1.5.3 + jwt (无状态)
- 技术译文 | MySQL 8.x DDL 和查询重写插件
- iOS webp图片展示处理
- Android内存优化 | LeakCanary/Profiler & 非静态内部类耗时操作 实战分析
- 使用keycloak实现k8s用户权限的统一管理
- python魔法方法是什么
- 如何同步上游分支代码?
- 在 Pycharm 中安装及使用 Jupyter (图文详解)