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

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中用 {} 包含起來的內容code

public String replace(String text, String ... args);

這時,該怎麼用上面的方法來實現替換呢 ?orm

若是有了解過 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進去就完美了get

// 獲取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的實現原理, 想實現這樣的功能固然是本身動手寫纔是真理

相關文章
相關標籤/搜索