玩转字符串篇--替换的鬼斧神工

2,028 阅读4分钟

本文说明

1.1.问题

今天遇到一个问题,就是如何指定批量代换某些字符串。
场景:比如下面一段markdown,写文章时遇到很多固定的链接时,总是很长一段。
特别是表格里,感觉要崩溃啊,而且万一哪天要换个网址,一个个改还不改疯掉。
不行,得想个办法,再怎么说咱也是会敲些Hello World代码的人,逼格不能输。

Padding是一个可以产生内边距的控件
详情可见:![Padding](https://juejin.cn/user/149189281194766)

[Container](https://juejin.cn/user/149189281194766)有一个padding属性,
详情可见![Padding](https://juejin.cn/user/149189281194766)

1.2.想法

我就想写成下面的样子,带有$[XXX]的全给我自动换成相应的连接,看着也清爽,
所以咬个牙,搞一波,一劳永逸。最终结果是只要点一下,一个文件夹里所有markdown文件都转化。

Padding是一个可以产生内边距的控件
详情可见:$[Padding]
$[Container]有一个padding属性,
详情可见$[Padding]

2.实现

2.1:字符串匹配

首先要将$[XXX]中的XXX拿出来

String test="Padding是一个可以产生内边距的控件\n" +
        "详情可见:$[Padding]\n" +
        "$[Container]有一个padding属性,\n" +
        "详情可见$[Padding]";
parse(test);

private static void parse(String target) {
    String regex = "\\$\\[(?<result>.*?)\\]";
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(target);
    while (matcher.find()) {
        String matchStr=matcher.group("result");
    }
}

---->[打印结果]----
Padding
Container
Padding

2.2:之后怎么办

到这里卡壳了,感觉匹配出来也没什么用,在replace方法耗了些时间,然并卵
如果链接少的话replaceAll一个一个换也行,但很多就有点恐怖了
然后看到matcher的end()方法,内心一喜,有索引似乎有搞头,二话不说,存起来先

private static void parse(String target) {
        Map<Integer,String> cutPos=new TreeMap<>();//断点映射
        String regex = "\\$\\[(?<result>.*?)\\]";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(target);
        while (matcher.find()) {
            String matchStr=matcher.group("result");
            catPos.put(matcher.end(),matchStr);//放入映射
        }
}

2.3:最终处理

调试了一会,摸索到了一个方法:
断点可以将字符串分为两半,前段处理后再和后段拼在一起,这样第一个就ok了
然后处理拼成的字符串,这有一个问题:就是此时的断点索引要偏移,
因为原先的字符串已经改变了,当然这也难不倒聪明伶俐的我

private static void parse(String target) {
    Map<Integer,String> cutPos=new TreeMap<>();
    Map<String,String> urlMap=new HashMap<>();
    urlMap.put("Padding","https://juejin.cn/user/149189281194766
    urlMap.put("Container","https://juejin.cn/user/149189281194766");
    
    String regex = "\\$\\[(?<result>.*?)\\]";
    
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(target);
    while (matcher.find()) {
        String matchStr=matcher.group("result");
        System.out.println(matchStr);
        cutPos.put(matcher.end(),matchStr);
    }
    
    Iterator<Map.Entry<Integer, String>> iterator = cutPos.entrySet().iterator();
    String temp;
    int offset=0;
    while (iterator.hasNext()){
        Map.Entry<Integer, String> e = iterator.next();
        int k = e.getKey();
        String v=e.getValue();
        String src="$["+v+"]";
        String dest= "!["+v+"]"+"("+urlMap.get(v)+")";
        String substring = target.substring(0,k+offset);
        temp = substring.replace(src, dest);
        target = target.replace(substring, temp);
        offset+=dest.length()-src.length();
    }
}

然后一跑,大功告成,好了,正文即将开始


3.优化与封装

好了,已经拥有核心科技,就差包个壳了

3.1:Parser类

用于字符串的解析,注意可以自定义符号,不过记得转义

public class Parser {
    /**
     * 默认:$[X],自定义注意\\转义
     */
    private String symbol;

    public Parser() {
        this.symbol = "\\$\\[X\\]";
    }
    public Parser(String symbol) {
        this.symbol = symbol;
    }

    /**
     * 解析字符串
     * @param target 目标字符串
     * @param matchMap 匹配映射
     * @return 处理后的字符串
     */
    public String parse(String target, Map<String,String> matchMap) {
        String[] symbols = symbol.split("X");

        Map<Integer,String> cutPos=new TreeMap<>();
        String regex = symbols[0]+"(?<result>.*?)"+symbols[1];
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(target);
        while (matcher.find()) {
            String matchStr=matcher.group("result");
            cutPos.put(matcher.end(),matchStr);
        }

        Iterator<Map.Entry<Integer, String>> iterator = cutPos.entrySet().iterator();
        String temp;
        int offset=0;
        while (iterator.hasNext()){
            Map.Entry<Integer, String> e = iterator.next();
            int k = e.getKey();
            String v=e.getValue();
            String src="$["+v+"]";
            String dest= "!["+v+"]"+"("+matchMap.get(v)+")";
            String substring = target.substring(0,k+offset);
            temp = substring.replace(src, dest);
            target = target.replace(substring, temp);
            offset+=dest.length()-src.length();
        }

        return target;
    }
}

3.2:文件操作

对于我即将开源的Flutter组件集项目,每个文件里都涉及到很多别的组件或属性链接
所以我需要寻找一个解决方法,不然一个一个套,感觉不太实际,而且眼花缭乱
最终导致写代码的心情不佳,所以来个批量文件操作吧

public String parserFile(String path, Map<String,String> matchMap)
    return parserFile(new File(path),matchMap);
}

/**
 * 根据文件解析
 * @param file 文件
 * @param matchMap 匹配映射
 * @return
 */
public String parserFile(File file, Map<String,String> matchMap){
    InputStream is=null;
    StringBuilder sb = new StringBuilder();
    try {
        is= new FileInputStream(file);
        int len=0;
        byte[] buffer=new byte[1024];
        while ((len=is.read(buffer))!=-1){
            sb.append(new String(buffer,0,len));
        }
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        try {
            if (is != null) {
                is.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return parser(sb.toString(),matchMap);
}

3.3:文件夹批量文件操作

OK,打完收工,现在可以开开心心写md文件了,字符串操作真的很好玩
如果今后遇到什么需要替换的,照这个思路来就ok了,避免不必要的劳动付出。

public void parserDir(String path, Map<String, String> matchMap) {
    copyDir(path, path + "-src");//先拷贝一分
    parserDir(new File(path), matchMap);
}
private void parserDir(File file, Map<String, String> matchMap) {
    if (file.isDirectory()) {
        File[] files = file.listFiles();
        if (files == null) {
            return;
        }
        for (File f : files) {
            if (f.isDirectory()) {
                parserDir(f, matchMap);
            } else {
                if (f.getName().endsWith(".md")) {
                    handleFile(matchMap, f);
                }
            }
        }
    } else {
        parserFile(file, matchMap);
    }
}
private void handleFile(Map<String, String> matchMap, File f) {
    FileWriter fw = null;
    String result = parserFile(f, matchMap);
    try {
        fw = new FileWriter(f);//写出到磁盘
        fw.write(result);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (fw != null) {
                fw.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
/**
 * 复制整个文件夹内容
 *
 * @param oldPath String 原文件路径
 * @param newPath String 复制后路径
 * @return boolean
 */
public void copyDir(String oldPath, String newPath) {
    try {
        (new File(newPath)).mkdirs(); //如果文件夹不存在 则建立新文件夹
        File a = new File(oldPath);
        String[] file = a.list();
        File temp = null;
        if (file == null) {
            return;
        }
        for (int i = 0; i < file.length; i++) {
            if (oldPath.endsWith(File.separator)) {
                temp = new File(oldPath + file[i]);
            } else {
                temp = new File(oldPath + File.separator + file[i]);
            }
            if (temp.isFile()) {
                FileInputStream input = new FileInputStream(temp);
                FileOutputStream output = new FileOutputStream(newPath + "/" +
                        (temp.getName()).toString());
                byte[] b = new byte[1024 * 5];
                int len;
                while ((len = input.read(b)) != -1) {
                    output.write(b, 0, len);
                }
                output.flush();
                output.close();
                input.close();
            }
            if (temp.isDirectory()) {//如果是子文件夹
                copyDir(oldPath + "/" + file[i], newPath + "/" + file[i]);
            }
        }
    } catch (Exception e) {
        System.out.println("复制整个文件夹内容操作出错");
        e.printStackTrace();
    }
}