【问题标题】:How to write a number of large files with memory efficiency in Java 7Java 7 中如何写出大量具有内存效率的大文件
【发布时间】:2018-02-16 10:09:45
【问题描述】:

我需要将大量数据(我可以在一秒钟内获得高达 1KB 的数据)写入多个不同的文件(每个文件最多可以增加几个 GiB 的空间。

在我的应用程序中,一个线程不断生成数据,目前我正在为每个文件创建一个线程来写入数据(创建一个新文件取决于生产者正在生成的输入数据的某些标准)。

我正在使用由 BufferedWriter 包装的 FileWriter。最初我尝试使用 8KB 的默认缓冲大小进行写入。但是由于这使得每秒的写入次数更高,所以 CPU 消耗会迅速增加而不会下降。

所以,我现在已将缓冲大小增加到 50KB。 但这使我的应用程序由于 outOfMemory 问题而崩溃。

当我对其进行分析时,我可以看到所有数据都以 char 数组的形式存储,该数组由缓冲写入器创建(记住每个文件一个缓冲写入器。我有大约 400 个这样的文件)。

请在此处提出如​​何解决该问题的建议。 我也想知道是否有任何替代方案或更好的方法来实现该要求。

我不能将缓冲区大小减少到 50KB 以下,因为这会使我的 CPU 达到 100%。

编辑:好的,我认为我不需要添加代码,因为我可以使要求非常清楚(而且我与代码无关。没关系,只是我在寻找效率)。但是,由于我的问题被否决了,所以我将我的代码包括在这里。

public class DataWriter {

      private LinkedBlockingQueue<MyDataObject> dataQueue = new LinkedBlockingQueue<>();
      private ExecutorService singleThread = Executors.newSingleThreadExecutor();
      private boolean isRunning = true;
      private Map<String, FileWriterThread> map = Collections.synchronizedMap(new HashMap<String, FileWriterThread>());

      public DataWriter() {
         singleThread.submit(new DataProcessor());
      }

      public void writeProducedData(MyDataObject object) {
          if (isRunning) {
             dataQueue.offer(object);
          }
      }

      public void stopWriting() {
          isRunning = false;
      }

      private class DataProcessor implements Runnable {

         @Override
         public void run() {
            while (isRunning) {

            MyDataObject obj = dataQueue.take();

            if (obj.getMapKey() == null) {
                FileWriterThread thread = new FileWriterThread();
                map.put(obj.getMapKey(), thread);
            }

            FileWriterThread thread = map.get(obj.getMapKey());
            thread.writeData(obj);
         }
        }        
      }
    }

FileWriterThread 类:

public class FileWriterThread {

       private ExecutorService singleThread = Executors.newSingleThreadExecutor();
       private FileWriter fileWriter;
       private BufferedWriter bufferedWriter;
       private LinkedBlockingQueue<MyDataObject> dataQueue = new LinkedBlockingQueue<>();

    public FileWriterThread() {
      singleThread.submit(new DataProcessor());
    }

    public void writeData(MyDataObject obj) {
     if (fileWriter == null) {
       createWriter(obj.getFileName());
     }
     dataQueue.offer(obj);
    }

    public void stopWriting() {
      // close the file writer and buffer writer gracefully
    }

    private void createWriter(String fileName) {
      try {
        fileWriter = new FileWriter(fileName, true);
        bufferedWriter = new BufferedWriter(fileWriter, 50);
      } catch (Exception e){}   
    }

    private class DataProcessor implements Runnable {

       @Override
       public void run() {
         MyDataObject obj =  dataQueue.take();
         try {
           bufferedWriter.write(obj.toString());
         } catch(Exception e) {}
       }
    }
  }

【问题讨论】:

  • 显示代码的相关部分。
  • 如果您在单个磁盘上写入文件,则使用单个线程进行写入很可能会更快,并且比动态创建的拥有 400 个线程(这是巨大的)消耗的内存要少得多,并且每次写入一个单独的文件。当然,由于您没有发布任何一行代码,我们只能猜测。
  • 您是否以 1MB/s 的速度写入每个文件?因为那将是 400 MB/s,在 HDD 上是不可能的。你的硬件平台是什么?
  • 抱歉@AndrewHeys,已将其更正为 1 KB/s
  • @JBNizet,请查看我添加的代码。另外,如果我只使用一个线程写入 400 个文件,我正在写入的数据量仍然是一样的。你能告诉我这里的内存将如何减少吗?

标签: java java-io filewriter bufferedwriter


【解决方案1】:

拥有所有这些线程并没有给您带来任何好处——整个事情是受 IO 限制的,而不是受 CPU 限制的(或者,如果您的 CPU 没有努力处理 400 个线程,那么会是这样)。

在单个线程中,只需让您的生产者写信给适当的作者:

   Map<String, Writer> writers = ...;

   private handleOutput(byte[] output, String key) {
        writers.get(key).write(output);
   }

如果您想分离您的关注点,也许有几个(不是数百个)线程用于编写和一个用于生产,用Queue 将它们分开。

【讨论】:

  • 感谢@slim 的回复。这里有一个问题。如果我使用单个线程写入多个文件,我会获得比减少活动线程数更多的好处吗?我相信占用的内存与我们要创建相同数量的对象可能是在不同的线程上还是在同一个线程上对吗?
  • 如果你的机器没有那么多线程,它应该更快地写入文件,更快地耗尽缓冲区。为了节省内存,您可以使缓冲区更小。要使用更大的缓冲区,您可以增加堆大小(Java -Xmx 命令行参数)。
猜你喜欢
  • 2011-03-23
  • 2018-07-08
  • 1970-01-01
  • 2012-11-15
  • 1970-01-01
  • 2013-11-19
  • 2013-05-19
  • 1970-01-01
  • 2013-03-27
相关资源
最近更新 更多