用Java读取真正的大文件

时间:2014-03-03 17:01:35

标签: java servlets memory io nio

我正在Servlet中读取一个77MB的文件,将来这将是150GB。此文件不是使用任何类型的nio包编写的,而是使用BufferedWriter编写的。

现在这就是我需要做的。

  1. 逐行读取文件。每行是文本的“哈希码”。将它分成3个字符(3个字符代表1个字)可能很长,可能很短,我不知道。

  2. 读完该行后,将其转换为真实的单词。我们有一个单词和哈希的地图,所以我们可以找到单词。

  3. 到目前为止,我使用BufferedReader来读取文件。这对于像150GB这样的巨大文件来说很慢而且不好。即使对于这个77MB的文件,也需要数小时才能完成整个过程。因为我们无法让用户等待几个小时,所以应该在几秒钟之内。所以,我们决定将文件加载到内存中。首先我们考虑将每一行加载到LinkedList中,因此内存coulkd将其保存。但是你知道,记忆不能保存这么大的数量。在大搜索之后,我决定将文件映射到内存就是答案。内存比磁盘快,所以我们也可以超快速地读取文件。

    代码:

    public class MapRead {
    
        public MapRead()
        {
            try {
                File file = new File("E:/Amazon HashFile/Hash.txt");
                FileChannel c = new RandomAccessFile(file,"r").getChannel();
    
                MappedByteBuffer buffer = c.map(FileChannel.MapMode.READ_ONLY, 0,c.size()).load();
    
                for(int i=0;i<buffer.limit();i++)
                {
                    System.out.println((char)buffer.get());
                }
    
                System.out.println(buffer.isLoaded());
                System.out.println(buffer.capacity());
    
    
    
            } catch (IOException ex) {
                Logger.getLogger(MapRead.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    
    
    }
    

    但我看不出任何“超级快”的东西。我需要一行一行。我有几个问题要问。

    1. 您阅读我的描述并知道我需要做什么。我已经完成了第一步,那是正确的吗?

    2. 我的地图方式是否正确?我的意思是,这与以正常方式阅读它没有区别。那么这首先将“整个”文件保存在内存中吗? (假设使用一种名为Mapping的技术)然后我们必须编写另一个代码来访问该内存?

    3. 如何在超级“快速”中逐行阅读? (如果我必须先将整个文件加载/映射到内存中几个小时,然后在几秒钟内以超高速访问它,我也完全没问题了)

    4. 在Servlet中读取文件好吗? (因为它被人数访问,并且一次只能打开一个IO流。在这种情况下,这个servlet将被数千个访问一次)

    5. 更新

      这是我使用SO用户 Luiggi Mendoza 的答案更新代码时的样子。

      public class BigFileProcessor implements Runnable {
          private final BlockingQueue<String> linesToProcess;
          public BigFileProcessor (BlockingQueue<String> linesToProcess) {
              this.linesToProcess = linesToProcess;
          }
          @Override
          public void run() {
              String line = "";
              try {
                  while ( (line = linesToProcess.take()) != null) {
      
                      System.out.println(line); //This is not happening
                  }
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }
      
      
      public class BigFileReader implements Runnable {
          private final String fileName;
          int a = 0;
      
          private final BlockingQueue<String> linesRead;
          public BigFileReader(String fileName, BlockingQueue<String> linesRead) {
              this.fileName = fileName;
              this.linesRead = linesRead;
          }
          @Override
          public void run() {
              try {
      
                  //Scanner do not work. I had to use BufferedReader
                  BufferedReader br = new BufferedReader(new FileReader(new File("E:/Amazon HashFile/Hash.txt")));
                  String str = "";
      
                  while((str=br.readLine())!=null)
                  {
                     // System.out.println(a);
                      a++;
                  }
      
              } catch (Exception ex) {
                  ex.printStackTrace();
              }
          }
      }
      
      
      
      public class BigFileWholeProcessor {
          private static final int NUMBER_OF_THREADS = 2;
          public void processFile(String fileName) {
      
              BlockingQueue<String> fileContent = new LinkedBlockingQueue<String>();
              BigFileReader bigFileReader = new BigFileReader(fileName, fileContent);
              BigFileProcessor bigFileProcessor = new BigFileProcessor(fileContent);
              ExecutorService es = Executors.newFixedThreadPool(NUMBER_OF_THREADS);
              es.execute(bigFileReader);
              es.execute(bigFileProcessor);
              es.shutdown();
          }
      }
      
      
      
      public class Main {
      
          /**
           * @param args the command line arguments
           */
          public static void main(String[] args) {
              // TODO code application logic here
              BigFileWholeProcessor  b = new BigFileWholeProcessor ();
              b.processFile("E:/Amazon HashFile/Hash.txt");
          }
      }
      

      我正在尝试在BigFileProcessor中打印文件。我理解的是这个;

      1. 用户输入文件名

      2. BigFileReader逐行读取该文件

      3. 每个行之后,BigFileProcessor被调用。这意味着,假设BigFileReader读取第一行。现在调用BigFileProcessor。现在BigFileProcessor完成该行的处理后,现在BigFileReader读取第2行。然后再次调用该行BigFileProcessor,依此类推。

        < / LI>

        可能是我对此代码的理解不正确。我该如何处理这条线?

3 个答案:

答案 0 :(得分:6)

我建议在这里使用多线程:

  • 一个线程会注意读取文件的每一行并将其插入BlockingQueue以便进行处理。
  • 另一个线程将take来自此队列的元素并处理它们。

要实现这个多线程工作,最好使用ExecutorService接口并传递Runnable个实例,每个实例都应该实现。记住只有一个任务来读取文件。

如果队列具有特定大小,您还可以管理停止阅读的方法,例如如果队列有10000个元素,那么等到它的大小降到8000,然后继续读取并填充队列。

  

在Servlet中读取文件很好吗?

我建议永远不要在servlet中做繁重的工作。相反,触发异步任务,例如通过JMS调用,然后在这个外部代理中,您将处理您的文件。


上述解释问题的简要示例:

public class BigFileReader implements Runnable {
    private final String fileName;
    private final BlockingQueue<String> linesRead;
    public BigFileReader(String fileName, BlockingQueue<String> linesRead) {
        this.fileName = fileName;
        this.linesRead = linesRead;
    }
    @Override
    public void run() {
        //since it is a sample, I avoid the manage of how many lines you have read
        //and that stuff, but it should not be complicated to accomplish
        Scanner scanner = new Scanner(new File(fileName));
        while (scanner.hasNext()) {
            try {
                linesRead.put(scanner.nextLine());
            } catch (InterruptedException ie) {
                //handle the exception...
                ie.printStackTrace();
            }
        }
        scanner.close();
    }
}

public class BigFileProcessor implements Runnable {
    private final BlockingQueue<String> linesToProcess;
    public BigFileProcessor (BlockingQueue<String> linesToProcess) {
        this.linesToProcess = linesToProcess;
    }
    @Override
    public void run() {
        String line = "";
        try {
            while ( (line = linesToProcess.take()) != null) {
                //do what you want/need to process this line...
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class BigFileWholeProcessor {
    private static final int NUMBER_OF_THREADS = 2;
    public void processFile(String fileName) {
        BlockingQueue<String> fileContent = new LinkedBlockingQueue<String>();
        BigFileReader bigFileReader = new BigFileReader(fileName, fileContent);
        BigFileProcessor bigFileProcessor = new BigFileProcessor(fileContent);
        ExecutorService es = Executors.newFixedThreadPool(NUMBER_OF_THREADS);
        es.execute(bigFileReader);
        es.execute(bigFileProcessor);
        es.shutdown();
    }
}

答案 1 :(得分:2)

NIO不会在这里帮到你。 BufferedReader并不慢。如果您受I / O约束,则您受I / O限制 - 获得更快的I / O.

将文件映射到内存可能会有所帮助,但前提是您实际使用内存,而不是仅仅将所有数据复制到您返回的大字节数组中。映射文件的主要优点是它可以将数据保存在Java堆之外,并远离垃圾收集器。

您的最佳表现将来自处理数据,如果可以的话,不会将其复制到堆中。

您的某些性能可能会受到对象创建的影响。例如,如果您尝试将数据加载到LinkedList中,那么您为List本身创建(可能)数百万个节点,加上数据周围的对象(即使它们只是字符串)

基于内存映射数组创建字符串可能非常有效,因为String将简单地包装数据,而不是复制数据。但是,如果您正在使用除ASCII之外的其他内容(因为字节不是Java中的字符),您必须能够识别UTF。

此外,如果您正在加载包含大量对象的大型内容,请确保堆中有可用空间。而通过自由空间,我的意思是实际的空间。你可以有一个500MB的堆,由-Xmx指定,但是ACTUAL堆最初不会那么大,它会增长到那个限制。

假设你有足够的内存,你可以通过-Xms来做到这一点,它会将堆预先分配到所需的大小,或者你可以简单地快速byte[] buf = new byte[400 * 1024 * 1024]来制作一个巨大的内存分配,强制GC,并拉伸堆。

您不想做的事情是分配一百万个对象,并且随着它的增长,每隔10000左右就有一个VM GC。预先分配其他数据结构也很有用(特别是ArrayLists,LinkedLists不是那么多)。

答案 2 :(得分:0)

将文件分成更小的部分。为此,您需要访问seekable读取权限,以便快进到文件的其他部分。

对于每个部分,生成多个工作线程,每个线程都有自己的哈希查找表副本。让已完成的线程join成为一个收集器线程,它将按顺序写入已完成的块并表示处理完成。

最好是流文件块而不是将所有内容加载到内存中。