# Java 一步一步實現高逼格的字符串替換工具(二)

Java 一步一步實現高逼格的字符串替換工具(二)

上一篇實現了一個用於字符串替換的方法,主要是利用 正則 + jdk的字符串替換,本篇則會再以前的基礎上走一個擴展java

1. 以前的方法存在的問題

先把上一篇的兩個方法貼下,研究下有什麼問題,而後再看下能夠怎麼去改進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;
}

一個單個替換,一個批量替換,咱們一個一個分析,首先看數組

1. public static String format(String text, Map<String, Object> params)

  • 正則替換效率問題
  • String.replaceAll() 這個也是走的正則替換, 從咱們的業務場景來看,有更好的替換

apache的 commons-lang 有個 StringUtils 工具類, 咱們能夠用裏面的 replace 方法進行代替, 上面註釋的就是咱們推薦的使用方式app

2. 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!

2. 改進++

對上面的正則獲取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替換的玩法上

相關文章
相關標籤/搜索