【问题标题】:Fastest/Most efficient way to parse a document, search for strings and replace them in document with Java解析文档、搜索字符串并在文档中用 Java 替换它们的最快/最有效的方法
【发布时间】:2018-01-31 19:47:24
【问题描述】:

所以我一直在研究 java 程序,它可以扫描和解析许多文件,用它们的可读格式替换术语(例如 func_123)。

提供定义的文件共有三个,因此每个文件都需要解析三次。

程序将定义加载到名为 Pair 的类中,并将该对放入 ArraryList。

然后程序逐行遍历每个文件并替换任何匹配的字符串。为每个文件创建并运行一个新线程。

那么解析、替换和将这些更改写入新文件的最快/最有效的方法是什么?

以下是我目前所拥有的。

解析每个文件的代码:

Thread thread = new Thread() {
    @Override
    public void run() {
        try {
            File temp = File.createTempFile("temp", "tmp");
            BufferedReader br = new BufferedReader(new FileReader(file));
            BufferedWriter bw = new BufferedWriter(new FileWriter(temp));
            String s = null;
            while ((s = br.readLine()) != null) {
            s = Deobfuscator2.deobfuscate(s);
                bw.write(s);
                bw.newLine();
            }
        bw.close();
        br.close();
        writeFromFileTo(temp, file);
        temp.delete();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
};

解码每个字符串的代码:

public static String deobfuscate(String s) {
    for (Pair<String, String> pair : fieldsMappings) {
        s = s.replaceAll(pair.key, pair.value);
    }
    for (Pair<String, String> pair : methodsMappings) {
        s = s.replaceAll(pair.key, pair.value);
    }
    for (Pair<String, String> pair : paramsMappings) {
        s = s.replaceAll(pair.key, pair.value);
    }
    return s;
}

对类:

public static class Pair <K,V> {

    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() {
        return key;
    }

    public V getValue() {
        return value;
    }

}

帮助函数将内容从一个文件复制到另一个文件:

private void writeFromFileTo(File file1, File file2) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(file1));
    BufferedWriter bw = new BufferedWriter(new FileWriter(file2));

    String s = null;
    while ((s = br.readLine()) != null) {
        bw.write(s);
        bw.newLine();
    }
    bw.close();
    br.close();
}

我试图尽可能清楚并提供所有相关代码,但如果您需要/想要其他任何内容,请告诉我。

我的代码有效,但我的问题是,当有很多文件要解析时,这样做似乎需要一些时间,并且可能会占用大量资源(如果我不限制线程)。总共有大约 33,000 多个(每个 10,000 多个)定义可能需要被替换。

【问题讨论】:

    标签: java string performance file parsing


    【解决方案1】:

    重复调用replaceAll 代价高昂,因为正则表达式将在每次传递时重新编译,而且您正在为每次替换创建字符串的新实例。更好的方法是预编译匹配任何键的正则表达式,然后遍历字符串并将找到的每个键替换为相应的值:

    static Pattern pattern;
    static List<String> replacements = new ArrayList<>();
    
    static {
        StringBuilder sb = new StringBuilder();
        for (List<Pair<String, String>> mapping : Arrays.asList(
                fieldsMappings, methodsMappings, paramsMappings)) {
            for (Pair<String, String> pair : mapping) {
                sb.append("(");
                sb.append(pair.key);
                sb.append(")|");
                replacements.append(Matcher.quoteReplacement(pair.value));
            }
        }
        // Remove trailing "|" character in regexp.
        if (sb.length() > 0) {
            sb.setLength(sb.length() - 1);
        }
        pattern = Pattern.compile(sb.toString());
    }
    
    public static String deobfuscate(String s) {
        StringBuffer sb = new StringBuffer();
        Matcher matcher = pattern.matcher(s);
        while (matcher.find()) {
            // Figure out which key matched and fetch the corresponding replacement.
            String replacement = null;
            for (int i = 0; i < replacements.size(); i++) {
                if (matcher.group(i) != null) {
                    replacement = replacements.get(i);
                    break;
                }
            }
            if (replacement == null) {
                // Should never happen.
                throw new RuntimeException("Regexp matched, but no group matched");
            }
            matcher.appendReplacement(sb, replacement);
        }
        matcher.appendTail(sb);
        return sb.toString();
    }
    

    上面的代码假设每个键都是一个正则表达式。如果键是固定字符串,则无需使用正则表达式组来识别匹配的键,您可以改用映射。这看起来像

    static Pattern pattern;
    static Map<String, String> replacements = new HashMap<>();
    
    static {
        StringBuilder sb = new StringBuilder();
        for (List<Pair<String, String>> mapping : Arrays.asList(
                fieldsMappings, methodsMappings, paramsMappings)) {
            for (Pair<String, String> pair : mapping) {
                sb.append(Pattern.quote(pair.key));
                sb.append("|");
                replacements.put(pair.key, Matcher.quoteReplacement(pair.value));
            }
        }
        // Remove trailing "|" character in regexp.
        if (sb.length() > 0) {
            sb.setLength(sb.length() - 1);
        }
        pattern = Pattern.compile(sb.toString());
    }
    
    public static String deobfuscate(String s) {
        StringBuffer sb = new StringBuffer();
        Matcher matcher = pattern.matcher(s);
        while (matcher.find()) {
            matcher.appendReplacement(sb, replacements.get(matcher.group()));
        }
        matcher.appendTail(sb);
        return sb.toString();
    }
    

    请注意,在构建替换列表/映射时,替换用Matcher.quoteReplacement 引用,以确保按字面意思处理替换,因为在从所有键构建复合正则表达式时,正则表达式反向引用无论如何都不起作用。如果您在替换中依赖反向引用,则此方法将不起作用。

    请注意,上面的代码尚未经过测试(甚至未编译)。

    【讨论】:

    • 我认为您的意思是使用 deleteCharAt(int),否则谢谢!性能有了相当显着的提高。 803 文件的高性能需要 15 分钟或更长时间,平衡需要 5 分钟。 :)
    • 很高兴听到这个消息! delete() 是对的,我现在在回答中将其替换为 setLength()
    【解决方案2】:
    1. String 中的replaceAll() 方法很慢,因为正则表达式模式会为所有键重复编译。一个想法是缓存“已编译模式”而不是字符串,然后重复运行 replaceAll。至少这会比当前版本快得多。

    2. 一个可能的想法是使用前缀 trie 优化“s 检查”。

    例如,假设 s 看起来像

    'qqq aaa 111 bbb 222 ccc rgege'
    

    键是aaa bbb 和ccc。然后您当前的算法检查 s 的字符 3 次。但是,如果您逐个检查字符并查找前缀 trie,并保留匹配位置和值的索引,则只需检查 s 一次即可知道

    replace aaa with aaaValue at 4, replace bbb at 12, and replace ccc at 20.
    

    这也可能会显着提高速度。有像并发树 jar 这样的 Java 库。如果性能不如预期,网上有一些关于try的编程练习代码,可以找到原始数组的trie实现,性能最佳。

    【讨论】:

      猜你喜欢
      • 2016-05-16
      • 2014-11-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-03
      • 2018-10-13
      相关资源
      最近更新 更多