上一篇實現了一個用於字符串替換的方法,主要是利用 正則 + jdk的字符串替換,本篇則會再以前的基礎上走一個擴展java
先把上一篇的兩個方法貼下,研究下有什麼問題,而後再看下能夠怎麼去改進apache
// 獲取patter的過程較爲負責,這裏初始化時,作一次便可 private static Pattern pattern; static { pattern = Pattern.compile("((?<=\\{)([a-zA-Z_]{1,})(?=\\}))"); } /** * 字符串替換, 將 {} 中的內容, 用給定的參數進行替換 * * @param text * @param params * @return */ public static String format(String text, Map<String, Object> params) { // 把文本中的全部須要替換的變量撈出來, 丟進keys Matcher matcher = pattern.matcher(text); while (matcher.find()) { String key = matcher.group(); // text = StringUtils.replace(text, "{" + key + "}", params.get(key) + ""); text = text.replaceAll("\\{" + key + "\\}", params.get(key) + ""); } return text; } public static List<String> batchFormat(String text, List<Map<String, Object>> params) { List<String> keys = new ArrayList<>(); // 把文本中的全部須要替換的變量撈出來, 丟進keys Matcher matcher = pattern.matcher(text); int tempIndex = 0; while (matcher.find()) { String key = matcher.group(); if (keys.contains(key)) { continue; } text = StringUtils.replace(text, key, tempIndex + ""); tempIndex++; keys.add(key); } List<String> result = new ArrayList<>(params.size()); String[] tempParamAry = new String[keys.size()]; for (Map<String, Object> param : params) { for (int i = 0; i < keys.size(); i++) { tempParamAry[i] = param.get(keys.get(i)) + ""; } result.add(MessageFormat.format(text, tempParamAry)); } return result; }
一個單個替換,一個批量替換,咱們一個一個分析,首先看數組
public static String format(String text, Map<String, Object> params)
String.replaceAll()
這個也是走的正則替換, 從咱們的業務場景來看,有更好的替換apache的 commons-lang 有個 StringUtils
工具類, 咱們能夠用裏面的 replace
方法進行代替, 上面註釋的就是咱們推薦的使用方式app
public static List<String> batchFormat(String text, List<Map<String, Object>> params)
這個的實現原理比較簡單ide
MessageFormat.format
進行替換這個流程比較清晰簡單,對於 MessageFormat.format
卻發現一個詭異的問題,當text中包含單引號時,後面的不會被替換, 測試case以下工具
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); text = "hello {0}, welcome to {2} ! what's a good day! today is {1}!"; ans = replace(text, "Lucy", new Date(), "HangZhou"); System.out.println(ans); }
輸出以下:測試
debug到源碼去看下,而後發如今生成 MessageFormat
對象的實現中,單引號內部有特殊用途,認爲兩個單引號之間的爲一個總體,不作替換ui
String text = "hello {0}, welcome to {2} ! what's {0}' a good day! today is {1}!"; String ans = MessageFormat.format(text, "Lucy", new Date(), "HangZhou"); System.out.println(ans); // 輸出 hello Lucy, welcome to HangZhou ! whats {0} a good day! today is 17-3-28 下午5:54!
對上面的正則獲取key,而後再調用
MessageFormat.format()
的方式不滿意,特別是後者的潛規則還很多,咱們要實現一個純粹的,高效的,可擴展的替換工具,應該這麼玩?this
既然已經深刻了MessageFormat
的源碼,那麼就簡單了,把他的實現邏輯摳出來,砍掉各類潛規則,咱們本身來實現便可debug
新版的設計思路:
- 首先將文本進行拆分 - 以`{}`做爲分割, 大括號先後的各自做爲新的`Word`; 大括號內的也做爲獨立的`Word` - 將拆分的`Word` 塞入一個數組中 - 遍歷上面的數組,替換變量 - 返回想要的結果
實現以下:
public static String formatV2(String text, Map<String, Object> params) { StringBuilder stringBuilder = new StringBuilder(); int startIndex = 0; for (int i = 0; i < text.length(); i++) { if (text.charAt(i) == '{') { if (startIndex > 0) { stringBuilder.append(text.substring(startIndex, i)); } startIndex = i + 1; continue; } if (text.charAt(i) == '}') { stringBuilder.append(params.get(text.substring(startIndex, i))); startIndex = i + 1; } } if (startIndex < text.length()) { stringBuilder.append(text.substring(startIndex)); } return stringBuilder.toString(); } /** * 規定大括號中不能再次出現大括號, 即不容許迭代替換 * * @param text * @param paramsList * @return */ public static List<String> batchFormatV2(String text, List<Map<String, Object>> paramsList) { List<Word> textList = splitText2words(text); List<String> result = new ArrayList<>(); StringBuilder stringBuilder; for (Map<String, Object> params: paramsList) { stringBuilder = new StringBuilder(); for (Word word: textList) { stringBuilder.append(replaceWord(word, params)); } result.add(stringBuilder.toString()); } return result; } private static String replaceWord(Word word, Map<String, Object> params) { if (word.getIsReplaceKey()) { return params.get(word.getWord()) + ""; } else { return word.getWord(); } } /** * 將文本根據{}進行分割 * <p/> * 如: {place} is a good place, what do you think {user}? * 分割: * - Word("place", true) * - Word(" is a good place, what do you think ", false) * - Word("user", true) * - Word("?", false) * * @param text * @return */ private static List<Word> splitText2words(String text) { List<Word> textList = new ArrayList<>(); int startIndex = 0; for (int i = 0; i < text.length(); i++) { if (text.charAt(i) == '{') { if (startIndex > 0) { textList.add(new Word(text.substring(startIndex, i), false)); } startIndex = i + 1; continue; } if (text.charAt(i) == '}') { textList.add(new Word(text.substring(startIndex, i), true)); startIndex = i + 1; } } if (startIndex < text.length()) { textList.add(new Word(text.substring(startIndex), false)); } return textList; } private static class Word { private String word; /** * true 則表示保存的是須要被替換的值 */ private Boolean isReplaceKey; public Word(String word, Boolean replaceKey) { this.word = word; this.isReplaceKey = replaceKey; } public String getWord() { return word; } public Boolean getIsReplaceKey() { return isReplaceKey; } @Override public String toString() { return "Word{" + "word='" + word + '\'' + ", isReplaceKey=" + isReplaceKey + '}'; } }
至此,一個算是不錯的文本替換工具類出來了,想一想還有什麼能夠改進的地方麼?
簡單的字符串進行替換有點low,若是我想在 {}
中執行一些表達式能夠怎麼玩 ?
下一篇則將精力主要集中在 {}
中value替換的玩法上