我必须在Java中重写一些旧版Perl应用程序。该应用程序执行了很多文本处理。 但是Java的正则表达式几乎比Perl慢6-8倍。我如何优化此
的性能Java代码需要26秒才能替换50k次字符串Perl代码采用4S
为了复制我在线文件上托管字符串的方案。在我的真实用例中,此字符串将来自输入队列
import java.io.IOException;
import java.io.PrintStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class RegexT {
public static void main(String[] args) throws IOException {
//RegexTest r = new RegexTest();
Map<String, String> m = new HashMap<>();
m.put("${ TO }", "rcpt");
m.put("${ MESSAGE_ID }", "37");
m.put("${ ID }", "40");
m.put("${ UNIQID }", "cff47534-fe6b-c45a-7058-8301adf1b97");
m.put("${ XOR }", "abcdef");
System.out.println(m);
String rx = "(\$\{[^}]+\})";
Pattern p = Pattern.compile(rx);
String s = readStringFromURL("https://raw.githubusercontent.com/ramprasadp/hostedtexfiles/master/msg2.txt");
// System.out.println(s); System.exit(0);
long start = System.currentTimeMillis();
for (int i = 0; i < 50000; i++) {
StringBuffer sb = new StringBuffer();
Matcher mat = p.matcher(s);
while (mat.find()) {
String repString = m.get(mat.group(1));
if (repString != null) {
mat.appendReplacement(sb, repString);
}
}
mat.appendTail(sb);
}
long timeTaken = System.currentTimeMillis() - start;
System.out.println("Time taken in ms = "+ timeTaken);
}
public static String readStringFromURL(String requestURL) throws IOException {
try (Scanner scanner = new Scanner(new URL(requestURL).openStream(),
StandardCharsets.UTF_8.toString())) {
scanner.useDelimiter("\A");
return scanner.hasNext() ? scanner.next() : "No file";
}
}
}
和perl中的相同逻辑
#!/usr/bin/perl
use Time::HiRes qw( gettimeofday tv_interval );
use strict;
my %data;
$data{'TO'} = "rcpt";
$data{'MESSAGE_ID'} = "37";
$data{'ID'} = "7";
$data{'UNIQID'} = "cff47534-fe6b-c45a-7058-8301adf1b97";
$data{'XOR'} = "abcdef";
#Get the content
my $msg_string = `wget -q -O - http://raw.githubusercontent.com/ramprasadp/hostedtexfiles/master/msg2.txt`;
my $start = [gettimeofday];
for (my $j=0;$j<50000; $j++) {
my $tmp_string = $msg_string;
$tmp_string =~ s/${ ([w_]+) }/$data{$1}/g;
}
print "Time taken in ms is " . 1000 * tv_interval ( $start )."n";
,除非在此代码上进行更深入的基准或分析,否则很难将这种低性能仅将其算作正则实现(尽管我倾向于 - 倾向于 - 同意 - 与您一致。..(。
所以,我尝试了此确切的代码, 14秒在我的计算机上运行。我尝试并行运行它,这将其从14秒降低到3秒:
IntStream.iterate(0, i -> i+1).limit(50000).parallel().forEach((i) -> {
Matcher mat = p.matcher(s);
StringBuffer sb = new StringBuffer();
while (mat.find()) {
String repString = m.get(mat.group(1));
if (repString != null) {
mat.appendReplacement(sb, repString);
}
}
mat.appendTail(sb);
});
现在,我想您的确切问题不是特别链接到代码是否依次运行的事实(您的现实世界应用程序不太可能执行此50000在单个字符串上查找/替换练习(,但是这至少会导致您的问题中未提及的其他方面(甚至根本不考虑(。否则,它还告诉您,如果这是强烈使用的,则最好并行运行。换句话说,这回答了"我如何优化此问题"的表现...
您可能必须将单一执行的结果与尽可能少的其他类别的参与来比较以最大程度地减少其他因素的影响,但是比较跨语言的效果肯定会保持挑战。<<<<<<<<<<<<<<<<<<<<
您可以考虑的替代性是 Groovy的模板引擎,其性能要比两者都更好(假设模板是重复使用的 - 这是很有可能的(。就您而言,这尤其好,因为您不需要更改模板文字中的占位符。
Map<String, String> binding = new HashMap<>();
binding.put("TO", "rcpt");
binding.put("MESSAGE_ID", "37");
binding.put("ID", "40");
binding.put("UNIQID", "cff47534-fe6b-c45a-7058-8301adf1b97");
binding.put("XO", "abcdef");
binding.put("XOR", "abcdef");
String text = s;
groovy.text.SimpleTemplateEngine engine = new groovy.text.SimpleTemplateEngine();
Template template = engine.createTemplate(text);
for (int i = 0; i < 50000; i++) {
template.make(binding).toString();
}
long timeTaken = System.currentTimeMillis() - start;
System.out.println("Time taken in ms = "+ timeTaken);
}
上面的时髦版本以 3.182秒完成。虽然其并行版本(同一流机构(以 2.313秒
完成您可以在此处找到有关Groovy模板的更多信息
简单的子字符串处理将其降低到12-13s:
long start = System.currentTimeMillis();
for (int i = 0; i < 50000; i++) {
String tmpS = s;
for (Entry<String, String> ms : m.entrySet()) {
int index = -1;
while ((index = tmpS.indexOf(ms.getKey())) >= 0) {
tmpS = tmpS.substring(0, index) + ms.getValue() + tmpS.substring(index + ms.getKey().length());
}
}
}
由于您知道每个标签有一个非常匹配的匹配项,因此您可以对其进行优化,使执行时间降低到7.5s:
for (Entry<String, String> ms : m.entrySet()) {
int index = tmpS.indexOf(ms.getKey());
tmpS = tmpS.substring(0, index) + ms.getValue() + tmpS.substring(index + ms.getKey().length());
}
不是perl速度,但比Java Regexp方法快4倍。
,如果您真的想节省几秒钟,则可以使用标签出现在消息开头的事实。我不知道是否总是这样,但这是一个公平的猜测。这只需要2.5秒:
for (int i = 0; i < 50000; i++) {
int i1 = s.lastIndexOf("}") + 1;
String tmpS = s.substring(0, i1);
for (Entry<String, String> ms : m.entrySet()) {
int i2 = tmpS.indexOf(ms.getKey());
tmpS = tmpS.substring(0, i2) + ms.getValue() + tmpS.substring(i2 + ms.getKey().length());
}
String result = tmpS + s.substring(i1);
}
现在,Java实现比原始的Perl实施更快。
我还将我的s.lastIndexOf("}")
技巧与您的原始regexp
方法组成字符串。