[英]Reading and writing multiple files in parallel
我需要用Java編寫一個程序,它將讀取目錄樹中相對較多的(~50,000)個文件,處理數據,並在單獨的(平面)目錄中輸出處理過的數據。
目前我有這樣的事情:
private void crawlDirectoyAndProcessFiles(File directory) {
for (File file : directory.listFiles()) {
if (file.isDirectory()) {
crawlDirectoyAndProcessFiles(file);
} else {
Data d = readFile(file);
ProcessedData p = d.process();
writeFile(p,file.getAbsolutePath(),outputDir);
}
}
}
可以說,為了便於閱讀,每個方法都被刪除和修剪,但它們都可以正常工作。 整個過程工作正常,但速度很慢。 數據處理通過遠程服務進行,需要5-15秒。 乘以50,000 ...
我之前從未做過任何多線程的事情,但我認為如果我這樣做,我可以獲得一些非常好的速度提升。 任何人都可以指出我如何有效地並行化這種方法?
我會使用ThreadPoolExecutor來管理線程。 你可以這樣做:
private class Processor implements Runnable {
private final File file;
public Processor(File file) {
this.file = file;
}
@Override
public void run() {
Data d = readFile(file);
ProcessedData p = d.process();
writeFile(p,file.getAbsolutePath(),outputDir);
}
}
private void crawlDirectoryAndProcessFiles(File directory, Executor executor) {
for (File file : directory.listFiles()) {
if (file.isDirectory()) {
crawlDirectoryAndProcessFiles(file,executor);
} else {
executor.execute(new Processor(file);
}
}
}
您將使用以下方式獲得Executor:
ExecutorService executor = Executors.newFixedThreadPool(poolSize);
其中poolSize
是您希望一次性使用的最大線程數。 (這里有一個合理的數字很重要; 50,000個線程並不是一個好主意。一個合理的數字可能是8.)請注意,在排隊所有文件之后,你的主線程可以等到事情完成后再調用executor.awaitTermination
。
假設您有一個硬盤(即只允許單個同時讀取操作,而不是SSD或RAID陣列,網絡文件系統等...),那么您只需要一個線程執行IO(讀取/寫入磁盤)。 此外,您只需要與擁有內核一樣多的線程執行CPU綁定操作,否則將浪費時間在上下文切換中。
鑒於上述限制,下面的代碼應該適合您。 單線程執行程序確保一次只能執行一個Runnable
。 固定線程池確保任何時候都不會執行NUM_CPUS
Runnable
。
這樣做的一件事是提供有關何時完成處理的反饋。
private final static int NUM_CPUS = 4;
private final Executor _fileReaderWriter = Executors.newSingleThreadExecutor();
private final Executor _fileProcessor = Executors.newFixedThreadPool(NUM_CPUS);
private final class Data {}
private final class ProcessedData {}
private final class FileReader implements Runnable
{
private final File _file;
FileReader(final File file) { _file = file; }
@Override public void run()
{
final Data data = readFile(_file);
_fileProcessor.execute(new FileProcessor(_file, data));
}
private Data readFile(File file) { /* ... */ return null; }
}
private final class FileProcessor implements Runnable
{
private final File _file;
private final Data _data;
FileProcessor(final File file, final Data data) { _file = file; _data = data; }
@Override public void run()
{
final ProcessedData processedData = processData(_data);
_fileReaderWriter.execute(new FileWriter(_file, processedData));
}
private ProcessedData processData(final Data data) { /* ... */ return null; }
}
private final class FileWriter implements Runnable
{
private final File _file;
private final ProcessedData _data;
FileWriter(final File file, final ProcessedData data) { _file = file; _data = data; }
@Override public void run()
{
writeFile(_file, _data);
}
private Data writeFile(final File file, final ProcessedData data) { /* ... */ return null; }
}
public void process(final File file)
{
if (file.isDirectory())
{
for (final File subFile : file.listFiles())
process(subFile);
}
else
{
_fileReaderWriter.execute(new FileReader(file));
}
}
最簡單(也可能是最合理的一種)方法是擁有一個線程池(看一下相應的Executor)。 主線程負責在目錄中進行爬網。 遇到文件時,創建一個“Job”(Runnable / Callable)並讓Executor處理該作業。
(這應該足以讓你開始,我不喜歡給出太多具體的代碼,因為一旦你閱讀了Executor,Callable等部分你就不難想出來了)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.