所以我一直在研究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+)的总定义需要被替换。
答案 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)
String中的replaceAll()方法很慢,因为正在为所有键重复编译正则表达式模式。一个想法是缓存'编译模式'而不是字符串,然后重复运行replaceAll。至少这将比当前版本快得多。
一个可能的想法是用前缀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.
这可能也会显着提高速度。有这样的Java库,例如concurrent-tree jar。如果性能不如预期的那样,有一些编程实践代码在线进行尝试,并且性能将是最佳的,因为可以找到原始数组的实现。