我有一个目录中非常大(~300 mb)的文件列表,需要使用awk脚本多次过滤,每次使用不同的搜索参数。 我编写了一个使用fixedThreadPool执行器来生成多个线程的程序,每个线程内的任务实现都是这样的,它创建一个新的Runtime()对象,并通过一个使用bash shell执行的新进程执行awk脚本。脚本
以下是示例代码:
Class MultiThreadingImpl:
public class MultiThreadingImpl {
static List<File> filesList = new ArrayList<File>();
public static void main(String[] args) {
int numThreads = Runtime.getRuntime().availableProcessors();
ExecutorService executor = Executors.newFixedThreadPool(numThreads);//creating a pool of 5 threads
File logsDir = new File("TestFilesDir");
getLogFiles(logsDir);
String[] searchKeys = {"123456","PAT1"};
for (int i = 0; i < filesList.size() ; i++) {
Runnable worker = new WorkerThread(filesList.get(i),searchKeys[i]);
executor.execute(worker);//calling execute method of ExecutorService
}
executor.shutdown();
while (!executor.isTerminated()) { }
System.out.println("Finished all threads");
}
private static void getLogFiles(File logsDir) {
assert(logsDir.isDirectory());
for(File f : logsDir.listFiles(
new FilenameFilter(){
public boolean accept(File dir, String name) {
return !name.endsWith("_result.txt");
}
}
)){
filesList.add(f);
}
}
}
Class WorkerThread:
class WorkerThread implements Runnable {
private String outputFile;
private String searchKey;
private File logFile;
public WorkerThread(File logFile,String searchKey){
this.logFile = logFile;
this.searchKey = searchKey;
this.outputFile = String.format(logFile.getName().replace(".txt", "") + "_result.txt");
}
public void run() {
int res = 0;
Runtime runtime = Runtime.getRuntime();
String awkRegex = new StringBuilder("'/([0-9]{1}|[0-9]{2})[[:space:]][[:alpha:]]+[[:space:]][0-9]{4}/{n=0}")
.append("/"+searchKey+"/").append("{n=1} n' ").toString();
String awkCommand = new StringBuilder("/usr/bin/awk ").append(awkRegex)
.append(logFile.getAbsolutePath()).append(" &> ").append("/TestFilesDir").append(outputFile).toString();
System.out.println(Thread.currentThread().getName() + ":: Command : " + awkCommand);
String[] cmdList = { "/bin/bash", "-c", awkCommand};
try {
final Process process = runtime.exec(cmdList);
res = process.waitFor();
BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
while (stdInput.readLine() != null) {
//Emptying stream
}
StringBuffer strerror = new StringBuffer();
String serror = null;
while ((serror = stdError.readLine()) != null) {
strerror.append(serror + "\n");
}
System.out.println(Thread.currentThread().getName() + ":: Process Exit value: " + res);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在这里,我可以选择为每个输入文件写入唯一的输出文件,然后使用cat
合并它们,最后读取合并的文件。
我还可以选择阅读每个流程的输出。将流输出到字符串并合并所有字符串。
哪种机制更快?
还建议是否有办法让整个事情更快?
答案 0 :(得分:0)
从运营的角度来看:这不应该有所作为。事实上,对于许多现代操作系统具有的splice
系统调用,它实际上不应该有任何开销。但是,你在Java中做了一些可能有一些开销的事情(整个缓冲的读者业务:为什么?)
还建议是否有办法让整个事情更快?
为什么从Java调用一个调用awk
的shell来解析你的表达式?
只需在Java中使用字符串/正则表达式引擎。
Java确实有一些速度限制,但我相信它们可能不会很严重; BufferedStreamReader(InputStreamReader)
构造中存在一些开销,所以如果您真的在这方面挤出最后一点性能,那么您肯定只需继续并在本机代码中实现所有这些功能。 ;再说一次,与仅使用Java带来的工具相比,我不相信你会赢得更多。
从算法上讲,你正在做的事情很糟糕:一次浏览每个文件,一次完成所有过滤,不要多次浏览每个文件。不必要地产生新的过程也会产生额外的开销。
多线程在这里没有帮助。你绝对不是CPU限制,但IO绑定,多线程不能增加你的存储带宽 - 相反,它通常甚至会破坏线性访问并使速度变慢。
这一切都感觉它需要10行shell脚本而不是复杂的多线程Java应用程序,并且启动和执行起来会更快。
答案 1 :(得分:-1)
不要使用Runtime()和&#39; awk&#39;脚本。而是翻译'awk&#39;脚本到Java。即使Java版本的运行速度略慢于awk&#39;,也要混合使用awk&#39;而Java只会使程序复杂化。
此外,不要为每个要处理的文件创建一个线程(每次创建新线程时都会产生开销)。相反,只使用固定数量的线程,并使用一些逻辑在这些线程之间平均分配文件。每个线程将按顺序处理几个文件。 (你需要更高的速度,然后将文件放在一些共享文件系统中 - 例如S3 - 然后使用多台计算机处理你的文件。