最快/最有效的方法来解析文档,搜索字符串并用Java

时间:2017-08-23 09:35:47

标签: java string performance file parsing

所以我一直在研究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+)的总定义需要被替换。

2 个答案:

答案 0 :(得分:2)

反复调用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();
}

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

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会引用替换,以确保替换是按字面意思处理的,因为在从所有键构建复合正则表达式时,regexp反向引用无论如何都无法工作。如果您依赖于替换中的反向引用,这种方法将无法工作。

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

答案 1 :(得分:1)

  1. String中的replaceAll()方法很慢,因为正在为所有键重复编译正则表达式模式。一个想法是缓存'编译模式'而不是字符串,然后重复运行replaceAll。至少这将比当前版本快得多。

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

  3. 例如,假设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.
    

    这可能也会显着提高速度。有这样的Java库,例如concurrent-tree jar。如果性能不如预期的那样,有一些编程实践代码在线进行尝试,并且性能将是最佳的,因为可以找到原始数组的实现。