按最大行分割非常大的文本文件

我想将包含字符串的大文件拆分成一组新的(较小的)文件并尝试使用nio2。

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

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

该解决方案有效,但我想询问是否有人通过使用java 8(也许lamdas with stream() – 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(); } } 

请注意直接使用InputStreamReader / OutputStreamWriter及其子类与FilesReader / Writer 工厂方法之间的区别。 在前一种情况下,系统的默认编码在没有给出显式字符集时使用,后者始终默认为UTF-8 。 因此,我强烈建议始终指定所需的字符集,即使它是Charset.defaultCharset()StandardCharsets.UTF_8来记录您的意图,如果您在创建ReaderWriter的各种方法之间切换,也可以避免意外。


如果要在行边界处分割,则无法查看文件的内容。 所以你不能像合并时那样优化它。

如果您愿意牺牲可移植性,可以尝试一些优化。 如果您知道charset编码将明确地将'\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 

缺点是它不适用于UTF-16EBCDICUTF-16并且与BufferedReader.readLine()不同,它不支持单独的'\r'作为旧MacOS9中使用的行终止符。

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

这些问题可以修复,但会增加这种方法的复杂性。 鉴于我的机器上的速度提升仅为15%(我没想到I / O在这里占主导地位),并且当复杂性提高时甚至更小,我认为这不值得。


最重要的是,对于此任务, Reader / Writer方法已足够,但您应该注意用于操作的Charset

我对@ 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"); }