Java读取包含200万行的文本文件的最快方法

40

目前我正在使用Scanner/FileReader并使用while hasnextline。我认为这种方法不太高效。是否有其他方法可以读取文件并具有类似功能的方法?

public void Read(String file) {
        Scanner sc = null;


        try {
            sc = new Scanner(new FileReader(file));

            while (sc.hasNextLine()) {
                String text = sc.nextLine();
                String[] file_Array = text.split(" ", 3);

                if (file_Array[0].equalsIgnoreCase("case")) {
                    //do something
                } else if (file_Array[0].equalsIgnoreCase("object")) {
                    //do something
                } else if (file_Array[0].equalsIgnoreCase("classes")) {
                    //do something
                } else if (file_Array[0].equalsIgnoreCase("function")) {
                    //do something
                } 
                else if (file_Array[0].equalsIgnoreCase("ignore")) {
                    //do something
                }
                else if (file_Array[0].equalsIgnoreCase("display")) {
                    //do something
                }
            }

        } catch (FileNotFoundException e) {
            System.out.println("Input file " + file + " not found");
            System.exit(1);
        } finally {
            sc.close();
        }
    }

1
这个链接有一些好的解决方案。 - Johny
9个回答

44
你会发现BufferedReader.readLine()非常快速,你可以使用它每秒读取数百万行。更有可能的是,你遇到的性能问题是由于字符串分割和处理引起的。

我没有进行时间检查,但是当我使用BufferedReader时,我认为读取部分比Scanner快大约20%。 - BeyondProgrammer
5
在我的情况下,文件读取过程中最主要的因素是分割操作。使用indexOf/lastIndexOf和substring方法可以简单地将这些成本降到最低。 - lalitm
1
对我来说,一旦将split()替换为substring()- indexOf()的组合,成本也会减少约50%。 - Vikas Prasad

25

我制作了一个gist,比较不同的方法:

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;
import java.util.function.Function;

public class Main {

    public static void main(String[] args) {

        String path = "resources/testfile.txt";
        measureTime("BufferedReader.readLine() into LinkedList", Main::bufferReaderToLinkedList, path);
        measureTime("BufferedReader.readLine() into ArrayList", Main::bufferReaderToArrayList, path);
        measureTime("Files.readAllLines()", Main::readAllLines, path);
        measureTime("Scanner.nextLine() into ArrayList", Main::scannerArrayList, path);
        measureTime("Scanner.nextLine() into LinkedList", Main::scannerLinkedList, path);
        measureTime("RandomAccessFile.readLine() into ArrayList", Main::randomAccessFileArrayList, path);
        measureTime("RandomAccessFile.readLine() into LinkedList", Main::randomAccessFileLinkedList, path);
        System.out.println("-----------------------------------------------------------");
    }

    private static void measureTime(String name, Function<String, List<String>> fn, String path) {
        System.out.println("-----------------------------------------------------------");
        System.out.println("run: " + name);
        long startTime = System.nanoTime();
        List<String> l = fn.apply(path);
        long estimatedTime = System.nanoTime() - startTime;
        System.out.println("lines: " + l.size());
        System.out.println("estimatedTime: " + estimatedTime / 1_000_000_000.);
    }

    private static List<String> bufferReaderToLinkedList(String path) {
        return bufferReaderToList(path, new LinkedList<>());
    }

    private static List<String> bufferReaderToArrayList(String path) {
        return bufferReaderToList(path, new ArrayList<>());
    }

    private static List<String> bufferReaderToList(String path, List<String> list) {
        try {
            final BufferedReader in = new BufferedReader(
                new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8));
            String line;
            while ((line = in.readLine()) != null) {
                list.add(line);
            }
            in.close();
        } catch (final IOException e) {
            e.printStackTrace();
        }
        return list;
    }

    private static List<String> readAllLines(String path) {
        try {
            return Files.readAllLines(Paths.get(path));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    private static List<String> randomAccessFileLinkedList(String path) {
        return randomAccessFile(path, new LinkedList<>());
    }

    private static List<String> randomAccessFileArrayList(String path) {
        return randomAccessFile(path, new ArrayList<>());
    }

    private static List<String> randomAccessFile(String path, List<String> list) {
        try {
            RandomAccessFile file = new RandomAccessFile(path, "r");
            String str;
            while ((str = file.readLine()) != null) {
                list.add(str);
            }
            file.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return list;
    }

    private static List<String> scannerLinkedList(String path) {
        return scanner(path, new LinkedList<>());
    }

    private static List<String> scannerArrayList(String path) {
        return scanner(path, new ArrayList<>());
    }

    private static List<String> scanner(String path, List<String> list) {
        try {
            Scanner scanner = new Scanner(new File(path));
            while (scanner.hasNextLine()) {
                list.add(scanner.nextLine());
            }
            scanner.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return list;
    }


}

运行:BufferedReader.readLine() 到 LinkedList, 行数:1000000, 估计时间:0.105118655

运行:BufferedReader.readLine() 到 ArrayList, 行数:1000000, 估计时间:0.072696934

运行:Files.readAllLines(), 行数:1000000, 估计时间:0.087753316

运行:Scanner.nextLine() 到 ArrayList, 行数:1000000, 估计时间:0.743121734

运行:Scanner.nextLine() 到 LinkedList, 行数:1000000, 估计时间:0.867049885

运行:RandomAccessFile.readLine() 到 ArrayList, 行数:1000000, 估计时间:11.413323046

运行:RandomAccessFile.readLine() 到 LinkedList, 行数:1000000, 估计时间:11.423862897

BufferedReader 是最快的,Files.readAllLines() 也可以接受,Scanner 因为正则表达式而较慢,RandomAccessFile 不可接受。


嘿@YAMM,在你的gist中,System.out("... into ArrayList")实际上使用的是LinkedList而不是ArrayList。这意味着,将缓冲读入ArrayList是最快的。 - imAmanRana
谢谢!我已经修复了!我还建议(几乎)总是使用ArrayList,因为整体性能更好。 - YAMM

9

Scanner 不能像 BufferedReader 那样快,因为它使用正则表达式读取文本文件,这使得它与 BufferedReader 相比更慢。通过使用 BufferedReader,您可以从文本文件中读取一个数据块。

BufferedReader bf = new BufferedReader(new FileReader("FileName"));

接下来,您可以使用readLine()从bf中读取。

希望它能满足您的需求。


2
我认为你的意思是“Scanner 无法像 BufferedReader 一样快”。 - fIwJlxSzApHEZIl

4

你可以使用来自JAVA NIO的FileChannelByteBuffer。从我观察到的情况来看,ByteBuffer的大小是读取数据更快的最关键部分。以下代码将读取文件的内容。

static public void main( String args[] ) throws Exception 
    {
        FileInputStream fileInputStream = new FileInputStream(
                                        new File("sample4.txt"));
        FileChannel fileChannel = fileInputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        fileChannel.read(byteBuffer);
        byteBuffer.flip();
        int limit = byteBuffer.limit();
        while(limit>0)
        {
            System.out.print((char)byteBuffer.get());
            limit--;
        }

        fileChannel.close();
    }

您可以在这里检查'\n'以获取新行。谢谢。


即使您可以分散和获取方式来更快地读取文件。

fileChannel.get(buffers);

在哪里

      ByteBuffer b1 = ByteBuffer.allocate(B1);
      ByteBuffer b2 = ByteBuffer.allocate(B2);
      ByteBuffer b3 = ByteBuffer.allocate(B3);

      ByteBuffer[] buffers = {b1, b2, b3};

这样做可以避免用户进程执行多次系统调用(可能会很耗费资源),同时允许内核优化数据处理,因为它拥有有关总传输量的信息,如果有多个CPU可用,则甚至可以同时填充和清空多个缓冲区。
来自这本书

1
如果数据被读取到JVM的Java侧,则直接字节缓冲区没有好处。它的好处在于,如果你只是在两个通道之间复制数据而不在Java代码中查看它,则可以受益于它。 - user207421
@EJP 我知道。我删除了这一行,你的评论就出现了。 :-) - Trying
@Trying,我想尝试使用FileChannel,你能否给我提供一些与上述代码相关的示例? - BeyondProgrammer
除非它有多个磁头,否则不能从单个磁盘并行读取。这里实际上没有任何东西可以读取行,因此这根本不是问题的答案。 - user207421
我不仅在读取文件,而且还在使用分隔符搜索我想要的单词。这种方法可行吗?如果(file_Array [0] .equalsIgnoreCase(“case”)){ //做某事 } - BeyondProgrammer
显示剩余3条评论

3

使用BufferedReader进行高性能文件访问。但默认的缓冲区大小为8192字节通常太小。对于大型文件,您可以通过数量级增加缓冲区大小以提高文件读取性能。例如:

BufferedReader br = new BufferedReader("file.dat", 1000 * 8192);
while ((thisLine = br.readLine()) != null) {
    System.out.println(thisLine);
}  

3
但它不会有太大影响。8192已经足够令人惊讶了。 - user207421

2

更新一下这个帖子,现在我们可以使用Java 8来完成这个任务:

Original Answer翻译成"最初的回答"

List<String> lines = Files.readAllLines(Paths.get(file_path);

0

如果有数百万条记录,您可以分块读取文件。这将避免潜在的内存问题。您需要保留最后一个指针以计算文件的偏移量。

try (FileReader reader = new FileReader(filePath);
                BufferedReader bufferedReader = new BufferedReader(reader);) {

            int pageOffset = lastOffset + counter;
            int skipRecords = (pageOffset - 1) * batchSize;

            bufferedReader.lines().skip(skipRecords).forEach(cline -> {
                try {
                    // PRINT
                    
                }

0

你必须调查程序中哪一部分花费了时间。

根据EJP的回答,你应该使用BufferedReader。

如果真正的字符串处理需要时间,那么你应该考虑使用线程,一个线程将从文件中读取并排队行。其他字符串处理器线程将出列行并处理它们。你需要调查要使用多少个线程,应用程序中使用的线程数必须与CPU核心数相关联,这样将使用完整的CPU。


如果字符串处理需要时间,那么多个线程做同样的事情会减少时间,对吧,就像并行处理一样。 - nullptr
只有当一行的处理不依赖于其他行的处理时,才能使用此功能。 - nullptr
1
如果字符串处理是瓶颈,将其放入单独的线程中只会移动瓶颈,而不是消除它。 - user207421
1
如果使用多个线程并行处理,瓶颈可以被消除。 - nullptr
1
并发并不总是解决问题的方法。实际的问题可能出在Scanner的性能、String.split()或equalsIgnoreCase(因为它需要深度比较字符串)。 - RecursiveExceptionException
不,如果您在多个线程中处理,则瓶颈可以被“分布”。您无法通过分发来消除处理。 - user207421

-2

如果您希望一次性读取所有行,那么您应该看看java 7的文件API。它非常简单易用。

但更好的方法是批处理处理此文件。拥有一个从文件中读取行块的阅读器和一个执行所需处理或持久化数据的写入器。拥有一个批处理将确保即使在未来行数增加到十亿,也可以正常工作。另外,您可以使用多线程的批处理来提高整个批处理的性能。我建议您查看spring batch。


当他一次读取和处理一行时,'批处理'会如何帮助呢? - user207421

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接