并行读写多个文件

时间:2012-01-05 04:58:41

标签: java parallel-processing

我需要用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 ......

我之前从未做过任何多线程的事情,但我认为如果我这样做,我可以获得一些非常好的速度提升。任何人都可以指出我如何有效地并行化这种方法吗?

3 个答案:

答案 0 :(得分:9)

我会使用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); 
        }
    }
}

您将使用以下方式获取执行程序:

ExecutorService executor = Executors.newFixedThreadPool(poolSize);

其中poolSize是您希望一次性使用的最大线程数。 (这里有一个合理的数字很重要; 50,000个线程并不是一个好主意。一个合理的数字可能是8.)请注意,在排队所有文件之后,你的主线程可以等到事情完成后再调用executor.awaitTermination

答案 1 :(得分:6)

假设您有一个硬盘(即只允许单个同时读取操作,而不是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));
  }
}

答案 2 :(得分:1)

最简单(也可能是最合理的一种)方法是拥有一个线程池(查看相应的Executor)。主线程负责在目录中进行爬网。遇到文件时,创建一个“Job”(Runnable / Callable)并让Executor处理该作业。

(这应该足以让你开始,我不喜欢给出太多具体的代码,因为一旦你阅读了Executor,Callable等部分你就不难想出来了)