2014-08-28 59 views
4

我想将包含字符串的大文件分割成一组新的(较小)文件并尝试使用nio2。按最大行分割非常大的文本文件

我不想将整个文件加载到内存中,所以我尝试了使用BufferedReader。

较小的文本文件应受限于文本行的数量。

解决方案的工作,但我要问,如果有人知道由延髓的Java 8(可能与流lamdas() - API?)具有更好的性能的解决方案和NIO2:

public void splitTextFiles(Path bigFile, int maxRows) throws IOException{ 

     int i = 1; 
     try(BufferedReader reader = Files.newBufferedReader(bigFile)){ 
      String line = null; 
      int lineNum = 1; 

      Path splitFile = Paths.get(i + "split.txt"); 
      BufferedWriter writer = Files.newBufferedWriter(splitFile, StandardOpenOption.CREATE); 

      while ((line = reader.readLine()) != null) { 

       if(lineNum > maxRows){ 
        writer.close(); 
        lineNum = 1; 
        i++; 
        splitFile = Paths.get(i + "split.txt"); 
        writer = Files.newBufferedWriter(splitFile, StandardOpenOption.CREATE); 
       } 

       writer.append(line); 
       writer.newLine(); 
       lineNum++; 
      } 

      writer.close(); 
     } 
} 
+0

由于您只读取一次和连续的文件,我不认为任何API可能会给你更好的性能。 Lambdas可以使代码看起来更好,但由于你的过程是大规模的IO绑定,所以它们不会影响性能。 – biziclop 2014-08-28 16:45:54

+0

谢谢。在http://stackoverflow.com/questions/25546750/merge-huge-files-without-loading-whole-file-into-memory/25548570?noredirect=1#comment39895492_25548570 nio2与FileChannel一起使用,它比基于字符的阅读器但是,我猜想,对于这种情况,我无法使用FileChannel,因为我需要访问文件的实际行。 – nimo23 2014-08-28 19:36:56

+0

好点,是的,这也是它的一部分。如果你想要固定大小的块(例如每个文件只有1MB),那么你肯定可以节省将字节转换为字符的成本。 – biziclop 2014-08-28 19:41:38

回答

3

当心直接使用InputStreamReader/OutputStreamWriter及其子类和Reader/Writerfactory methods of Files之间的差异。而在前一种情况下,当没有给出明确的字符集时使用系统的默认编码,后者总是默认为UTF-8。因此,我强烈建议始终指定所需的字符集,即使它是Charset.defaultCharset()StandardCharsets.UTF_8来记录您的意图,并避免意外如果您在创建ReaderWriter的各种方法之间切换。


如果你想分割线边界,没有办法查找文件的内容。因此,您无法按照like when merging的方式进行优化。

如果您愿意牺牲便携性,您可以尝试一些优化。如果您知道字符集编码将明确地映射'\n'(byte)'\n',因为对于大多数单字节编码以及UTF-8,您可以扫描字节级别的换行符以获取分割的文件位置并避免任何数据传输从你的应用程序到I/O系统。

public void splitTextFiles(Path bigFile, int maxRows) throws IOException { 
    MappedByteBuffer bb; 
    try(FileChannel in = FileChannel.open(bigFile, READ)) { 
     bb=in.map(FileChannel.MapMode.READ_ONLY, 0, in.size()); 
    } 
    for(int start=0, pos=0, end=bb.remaining(), i=1, lineNum=1; pos<end; lineNum++) { 
     while(pos<end && bb.get(pos++)!='\n'); 
     if(lineNum < maxRows && pos<end) continue; 
     Path splitFile = Paths.get(i++ + "split.txt"); 
     // if you want to overwrite existing files use CREATE, TRUNCATE_EXISTING 
     try(FileChannel out = FileChannel.open(splitFile, CREATE_NEW, WRITE)) { 
      bb.position(start).limit(pos); 
      while(bb.hasRemaining()) out.write(bb); 
      bb.clear(); 
      start=pos; 
      lineNum = 0; 
     } 
    } 
} 

的缺点是,它不与编码像UTF-16EBCDIC工作,不像BufferedReader.readLine()在老MacOS9使用也不会支持独行'\r'为行终止。

此外,它只支持小于2GB的文件;由于虚拟地址空间有限,在32位JVM上的限制可能会更小。对于大于限制的文件,有必要迭代源文件和它们的块。

这些问题可以解决,但会提高这种方法的复杂性。考虑到我的机器上的速度提升只有15%左右(因为I/O在这里占主导地位,所以我没有料到这一点),而当复杂度提高时,速度会更小,我认为这不值得。


的底线是,此任务的Reader/Writer方法是足够的,但是你应该采取有关用于操作Charset照顾。

0

我对@ nimo23代码进行了一些修改,考虑到为每个分割文件添加页眉和页脚的选项,它还会将文件输出到与原始文件具有相同名称的目录中_split添加到它。下面的代码:

public static void splitTextFiles(String fileName, int maxRows, String header, String footer) throws IOException 
    { 
     File bigFile = new File(fileName); 
     int i = 1; 
     String ext = fileName.substring(fileName.lastIndexOf(".")); 

     String fileNoExt = bigFile.getName().replace(ext, ""); 
     File newDir = new File(bigFile.getParent() + "\\" + fileNoExt + "_split"); 
     newDir.mkdirs(); 
     try (BufferedReader reader = Files.newBufferedReader(Paths.get(fileName))) 
     { 
      String line = null; 
      int lineNum = 1; 
      Path splitFile = Paths.get(newDir.getPath() + "\\" + fileNoExt + "_" + String.format("%03d", i) + ext); 
      BufferedWriter writer = Files.newBufferedWriter(splitFile, StandardOpenOption.CREATE); 
      while ((line = reader.readLine()) != null) 
      { 
       if(lineNum == 1) 
       { 
        writer.append(header); 
        writer.newLine(); 
       } 
       writer.append(line); 
       writer.newLine(); 
       lineNum++; 
       if (lineNum > maxRows) 
       { 
        writer.append(footer); 
        writer.close(); 
        lineNum = 1; 
        i++; 
        splitFile = Paths.get(newDir.getPath() + "\\" + fileNoExt + "_" + String.format("%03d", i) + ext); 
        writer = Files.newBufferedWriter(splitFile, StandardOpenOption.CREATE); 
       } 
      } 
      if(lineNum <= maxRows) // early exit 
      { 
       writer.append(footer); 
      } 
      writer.close(); 
     } 

     System.out.println("file '" + bigFile.getName() + "' split into " + i + " files"); 
    }