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
的實作原理, 想實作這樣的功能當然是自己動手寫才是真理