# Java 一步一步实现高逼格的字符串替换工具(二)

Java 一步一步实现高逼格的字符串替换工具(二)

上一篇实现了一个用于字符串替换的方法,主要是利用 正则 + jdk的字符串替换,本篇则会再之前的基础上走一个扩展

1. 之前的方法存在的问题

先把上一篇的两个方法贴下,研究下有什么问题,然后再看下可以怎么去改进

// 获取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 方法进行代替, 上面注释的就是我们推荐的使用方式

2. public static List<String> batchFormat(String text, List<Map<String, Object>> params)

这个的实现原理比较简单

  • 先用正则把所有需要替换的捞出来, 放在列表中, 并将坑位用数字来替换
  • 然后使用 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对象的实现中,单引号内部有特殊用途,认为两个单引号之间的为一个整体,不做替换

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()的方式不满意,特别是后者的潜规则还不少,我们要实现一个纯粹的,高效的,可扩展的替换工具,应该这么玩?

既然已经深入了MessageFormat的源码,那么就简单了,把他的实现逻辑抠出来,砍掉各种潜规则,我们自己来实现即可

新版的设计思路:

- 首先将文本进行拆分
    - 以`{}`作为分割, 大括号前后的各自作为新的`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替换的玩法上

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏SeanCheney的专栏

《Pandas Cookbook》第01章 Pandas基础

公司网址,http://www.dunderdata.com(dunder是蒸馏朗姆酒的残留液体,取这个名字是类比数据分析过程) GitHub地址:https...

17520
来自专栏小樱的经验随笔

洛谷 P1598 垂直柱状图【字符串+模拟】

P1598 垂直柱状图 题目描述 写一个程序从输入文件中去读取四行大写字母(全都是大写的,每行不超过72个字符),然后用柱状图输出每个字符在输入文件中出现的次数...

32650
来自专栏数据结构与算法

codevs 1213 解的个数

1213 解的个数 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题目描述 Description 已知整数x...

34440
来自专栏菩提树下的杨过

java:POI导出excel

POI是一个开源项目,专用于java平台上操作MS OFFICE,企业应用开发中可用它方便导出Excel. 下面是使用示例: 1、maven中先添加依赖项 1 ...

37360
来自专栏一个会写诗的程序员的博客

《Kotlin极简教程》第五章 Kotlin面向对象编程(OOP)一个OOP版本的HelloWorld构造函数传参Data Class定义接口&实现之写pojo bean定一个Rectangle对象封

We frequently create a class to do nothing but hold data. In such a class some s...

26340
来自专栏Golang语言社区

实效go编程--2

Go函数的返回值或结果“形参”可被命名,并作为常规变量使用,就像传入的形参一样。 命名后,一旦该函数开始执行,它们就会被初始化为与其类型相应的零值; 若该函数执...

35470
来自专栏jeremy的技术点滴

py3_cookbook_notes_01

34580
来自专栏about云

Scala的map实现key和value排序及各种排序比较等知识讨论

问题导读 1.map能否直接排序? 2.如何转换,才能排序? 3.排序结果可以存储在哪两个集合中? 4._*如何使用? 5.排序函数中,哪个可以进行升序和降序...

41780
来自专栏SeanCheney的专栏

《Pandas Cookbook》第07章 分组聚合、过滤、转换1. 定义聚合2. 用多个列和函数进行分组和聚合3. 分组后去除多级索引4. 自定义聚合函数5. 用 *args 和 **kwargs

第01章 Pandas基础 第02章 DataFrame运算 第03章 数据分析入门 第04章 选取数据子集 第05章 布尔索引 第06章 索引对齐 ...

48820
来自专栏desperate633

HashMap实现原理分析(Java源码剖析)内部实现存储结构-字段功能实现-方法Map中各实现类的总结小结

HashMap是Java程序员使用频率最高的用于映射(键值对)处理的数据类型。随着JDK(Java Developmet Kit)版本的更新,JDK1.8对Ha...

10920

扫码关注云+社区

领取腾讯云代金券