簡體   English   中英

並行讀寫多個文件

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM