【问题标题】:split very large text file by max rows按最大行拆分非常大的文本文件
【发布时间】:2014-10-22 14:29:00
【问题描述】:

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

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

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

该解决方案有效,但是我想问是否有人知道使用 java 8(可能是带有 stream()-api 的 lamdas?)和 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();
        }
}

【问题讨论】:

  • 由于您只按顺序读取文件一次,我认为任何 API 都不可能给您带来更好的性能。 Lambdas 可以使代码看起来更好,但由于您的进程受大量 IO 限制,因此它们根本不会影响性能。
  • 谢谢。在stackoverflow.com/questions/25546750/… nio2 与 FileChannel 一起使用,它比基于 char 的阅读器执行得更好,但是,我猜,对于这种情况,没有办法使用 FileChannel,因为我需要访问文件的实际行。
  • 好点,是的,这也是其中的一部分。如果你想要固定大小的块(例如每个文件正好是 1MB),你绝对可以节省将字节转换为字符的成本。

标签: java java-8 nio2


【解决方案1】:

注意直接使用 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() 不同,它不支持单独的'\r' 作为旧MacOS9 中使用的行终止符。

此外,它仅支持小于 2GB 的文件;由于虚拟地址空间有限,32 位 JVM 上的限制可能更小。对于大于限制的文件,需要逐个遍历源文件的块和map

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


底线是,对于此任务,Reader/Writer 方法就足够了,但您应该注意用于操作的 Charset

【讨论】:

    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-09-18
      • 2019-10-20
      • 2010-09-28
      • 2020-03-22
      • 2014-04-10
      • 2021-09-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多