文档章节

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

小灰灰Blog
 小灰灰Blog
发布于 2017/03/27 22:15
字数 1632
阅读 419
收藏 3

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的实现原理, 想实现这样的功能当然是自己动手写才是真理

© 著作权归作者所有

小灰灰Blog
粉丝 196
博文 205
码字总数 368225
作品 0
武汉
程序员
私信 提问
Gradle系列一之Gradle介绍

Gradle概述: Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建工具。它使用一种基于Groovy的特定领域语言来声明项目设置,而不是传统的XML。Gradle就是工程的管理,帮我们做了...

zhang_pan
05/17
0
0
JVM字节码与Java代码层调优

jvm字节码指令 我们都知道,Java源代码不会像C/C++那样直接被编译为机器码,而是被编译成字节码,这造就了Java可以跨平台的特性。JVM实际执行的也是编译后的字节码,所以想要在Java代码层进行...

ZeroOne01
2018/07/27
0
0
利用MAVEN的profile 实现打包环境的切换

产生问题的背景 由于在项目开发的时候,我们一般都是使用的本地库,数据库连接写的是本地的,如果我们将项目打成war的时候,里面的配置连接写的是我们本地的,当我们直接把war拷贝到服务器上...

数据加载中
2018/09/15
0
0
怎样衡量两个字符串的相似度(编辑距离动态规划求解)

前言 目前计算句子相似性有很多不同的方案,比如基于语义词典的方法、基于相同词汇的方法、基于统计的方法和基于编辑距离的方法。这篇文章先介绍编辑距离的基础。 编辑距离 编辑距离其实就是...

超人汪小建
2018/06/12
0
0
ZCS与CAS(Central Authentication Service)单点登录系统的集成

原文转自:http://opengeek.cn/forum-viewthread-tid-151-fromuid-23.html CAS( Central Authentication Service)是由JA-SIG开发的一套开源的单点登录系统,在教育行业有着非常广泛的应用,...

红双囍
2011/04/16
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Jenkins系列_插件安装及报错处理

进入Jenkins之后我们可以进行插件的安装,插件管理位于以下模块: 发现上面报了一堆错误,是因为插件的依赖没有安装好,那么这一节,就先把这些错误解决掉吧。解决完成后,也就基本会使用插件...

shzwork
今天
2
0
mysql mysql的所有查询语句和聚合函数(整理一下,忘记了可以随时看看)

查询所有字段 select * from 表名; 查询自定字段 select 字段名 from 表名; 查询指定数据 select * from 表名 where 条件; 带关键字IN的查询 select * from 表名 where 条件 [not] in(元素...

edison_kwok
昨天
9
0
多线程同时加载缓存实现

import com.google.common.cache.Cache;import com.google.common.cache.CacheBuilder;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorServi......

暗中观察
昨天
3
0
利用VisualVM 内存查看

准备工作,建几个测试类。等下就是要查看这几个类里面的属性 package visualvm;public class MultiObject { private String str; private int i; MultiObject(String str...

冷基
昨天
2
0
组装一台工作游戏两用机

一、配置清单如下: 分类 项目 价格(元) 主板 华硕(ASUS)TUF Z370-PLUS GAMING II 电竞特工 Z370二代 支持9代CPU 1049 CPU 英特尔(Intel) i7 8700K 酷睿六核 盒装CPU处理器 2640 风扇 九...

mbzhong
昨天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部