从一个大文件读取并写入Java中的许多(数十,数百或数千)文件?

时间:2009-12-30 20:26:48

标签: java large-files large-data-volumes

我有一个大型文件(4-5 GB压缩)的小消息,我希望按消息类型解析成大约6,000个文件。消息很小; 5到50个字节,具体取决于类型。

每条消息都以固定大小的类型字段(6字节密钥)开头。如果我读取类型为“000001”的消息,我想 write 将其有效负载附加到000001.dat等。输入文件包含多个消息;我想要N个同类输出文件,其中每个输出文件只包含给定类型的消息。

什么是一种有效的快速将这些消息写入这么多单个文件的方法?我想尽可能快地使用尽可能多的内存和处理能力。我可以将压缩或未压缩的文件写入磁盘。

我正在考虑使用带有消息类型键和输出流值的散列映射,但我确信有更好的方法来实现它。

谢谢!

6 个答案:

答案 0 :(得分:4)

您可能不需要哈希映射。你可以......

  1. 阅读邮件
  2. 以附加模式打开新文件
  3. 将邮件写入新文件
  4. 关闭新文件
  5. 不确定这是否会更快,因为你会做很多打开和关闭。

答案 1 :(得分:4)

类似Unix的系统通常会限制在任何给定时间打开的文件句柄数量;例如,在我的Linux上,它目前处于1024,但我可以在合理范围内改变它。但这些限制有很好的理由,因为打开文件是系统的负担。

您尚未回答我的问题,即您的输入中是否多次出现相同的密钥,这意味着可能需要将多个单独的批量数据连接到每个文件中。如果不是这样的话,佩斯的回答将是你能做到的最好的答案,因为所有的工作都需要完成,并且围绕如此简单的事件序列建立一个庞大的管理是没有意义的。 < / p>

但是如果输入中有多条消息用于同一个密钥,那么保持大量文件打开会很有效。不过,我建议不要试图让所有6000人同时打开。取而代之的是,我会以先到先得的方式开设类似500的东西;即你打开前500个(左右)不同消息键的文件,然后咀嚼整个输入文件,寻找要添加到500个中的东西,然后在输入时按EOF关闭它们。您还需要保留已经处理过的HashSet个密钥,因为您将再次重新读取输入文件,处理第一轮中没有捕获的下一批500个密钥。

理由:打开和关闭文件(通常)是一项代价高昂的操作;如果你能提供帮助,你不想多次打开和关闭数千个文件。因此,您尽可能多地打开句柄,所有句柄最终都会通过您的输入完成。另一方面,顺序通过单个输入文件流是非常有效的,即使您必须通过输入文件进行12次传递,与打开/关闭6000其他所需的时间相比,这样做的时间几乎可以忽略不计。文件。

<强>伪代码:

processedSet = [ ]
keysWaiting = true
MAXFILE = 500
handlesMap = [ ]
while (keysWaiting) {
  keysWaiting = false
  open/rewind input file
  while (not EOF(input file)) {
    read message
    if (handlesMap.containsKey(messageKey)) {
       write data to handlesMap.get(messageKey)
    } else if (processedSet.contains(messageKey) {
       continue // already processed
    } else if (handlesMap.size < MAXFILE) {
       handlesMap.put(messageKey, new FileOutputStream(messageKey + ".dat")
       processedSet.add(messageKey)
       write data to handlesMap.get(messageKey)
    else
       keysWaiting = true
    endif
  }
  for all handlesMap.values() {
     close file handle
  }
  handlesMap.clear
}

答案 2 :(得分:2)

我建议使用某种智能池:保持最大/最常用的文件打开以提高性能,关闭其余文件以节省资源。

如果主文件主要由记录类型1-5组成,请在需要时保持这些文件处于打开状态。其他可以根据需要打开和关闭,这样就不会使资源系统匮乏。

答案 3 :(得分:1)

我将对你的问题做一些假设:

  • 每条消息都以消息类型开头,作为固定大小的字段
  • 您有一个异质输入文件,其中包含多条消息;你想要N个同源输出文件,其中每个输出文件只包含给定类型的消息。

跳出来的方法是基于仿函数的:您创建消息类型到处理该特定消息的对象的映射。你的main()是一个调度循环,它读取固定的消息头,从地图中找到适当的函子,然后调用它。

您可能无法同时打开6,000个文件(每种消息类型一个);大多数操作系统都有大约1,024个同时打开文件的限制(尽管使用Linux,您可以更改控制它的内核参数)。所以这意味着你将重复打开和关闭文件。

可能最好的方法是在每个仿函数上设置一个固定计数缓冲区,以便在打开,写入和关闭之后,比如说10条消息。如果您的消息最多为50个字节,那么500字节(10 x 50)x 6,000将在任何给定时间保留在内存中。

我可能会编写我的仿函数来保存固定大小的字节数组,并创建一个通用仿函数类,一次读取N个字节到该数组中:

public class MessageProcessor
{
    int _msgSize;                   // the number of bytes to read per message
    byte[] _buf = new byte[1024];   // bigger than I said, but it's only 6 Mb total
    int _curSize;                   // when this approaches _buf.length, write

答案 4 :(得分:0)

由于您要对许多文件进行许多小写操作,因此您希望最大限度地减少写入次数,特别是考虑到最简单的设计几乎可以保证每次新写入都会涉及打开/关闭新文件。

相反,为什么不将每个键映射到缓冲区?最后,将每个缓冲区写入磁盘。或者,如果您担心自己将占用太多内存,则可以构造缓冲区以写入每1K或5K或任何行。例如

public class HashLogger {

          private HashMap<String,MessageBuffer> logs;

          public void write(String messageKey, String message)
          {
              if (!logs.contains(messageKey)) { logs.put(messageKey, new MessageBuffer(messageKey); }
              logs.get(messageKey).write(message);
          }

         public void flush()
         {
             for (MessageBuffer buffer: logs.values())
             {
                buffer.flush();
             }
            // ...flush all the buffers when you're done...
         }

    private class MessageBuffer {
             private MessageBuffer(String name){ ... }
             void flush(){ .. something here to write to a file specified by name ... }
             void write(String message){
             //... something here to add to internal buffer, or StringBuilder, or whatever... 
             //... you could also have something here that flushes if the internal builder gets larger than N lines ...
     }
}

你甚至可以创建单独的Log4j记录器,可以配置为使用缓冲记录,如果像slf4j这样的更现代的记录框架也不支持这一点,我会感到惊讶。

答案 5 :(得分:0)

系统中的打开文件通常有限制,无论如何以一个或多或少的随机顺序访问数千个小文件会严重影响您的系统。

考虑将大文件分解为单个消息的文件(或某种内存表,如果你有内存),并按消息类型对其进行排序。完成后,将消息写入相应的文件。

相关问题