【问题标题】:Java Fastest way to read through text file with 2 million linesJava 读取 200 万行文本文件的最快方法
【发布时间】:2013-10-29 10:43:21
【问题描述】:

目前我正在使用扫描仪/文件阅读器并使用 while hasextline。我认为这种方法效率不高。有没有其他方法可以读取具有类似功能的文件?

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();
        }
    }

【问题讨论】:

  • 这个link有一些很好的解决方案

标签: java file


【解决方案1】:

我做了一个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,在您的要点中,System.out ("... into ArrayList") 实际上使用的是linkedList 而不是arrayList。所以意味着,缓冲区读入 ArrayList 是最快的。
  • 谢谢!我修好了它!我还建议(几乎)总是使用 ArrayList,因为整体性能更好。
【解决方案2】:

刚刚更新这个线程,现在我们有 java 8 来做这个工作:

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

【讨论】:

    【解决方案3】:

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

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

    接下来你可以使用readLine() 来读取 bf。

    希望它能达到你的目的。

    【讨论】:

    • 我认为您的意思是“扫描仪不能像 BufferedReader 一样快”
    【解决方案4】:

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

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

    【讨论】:

    • 但不会有太大作用。 8192 出乎意料地足够了。
    【解决方案5】:

    您会发现BufferedReader.readLine() 的速度与您的需要一样快:您可以使用它每秒读取数百万行。您的字符串拆分和处理更有可能导致您遇到的任何性能问题。

    【讨论】:

    • 我没有做时间检查,但是当我使用缓冲阅读器时,我认为阅读部分比扫描仪快 20%
    • 就我而言,拆分是文件读取的最主要因素。简单使用 indexOf/lastIndexOf 和 substring 有助于将这些成本降至最低。
    • 对我来说,将split() 替换为substring()-indexOf() 对后,成本也降低了约50%。
    【解决方案6】:

    如果您想一起阅读所有行,那么您应该看看 java 7 的 Files API。它使用起来非常简单。

    但更好的方法是批量处理此文件。有一个从文件中读取大块行的读取器和一个执行所需处理或保存数据的写入器。拥有批处理将确保即使将来生产线增加到十亿,它也能正常工作。您也可以拥有一个使用多线程的批处理来提高批处理的整体性能。我建议你看看春季批次。

    【讨论】:

    • 当他一次读取和处理一行时,“批处理”究竟有什么帮助?
    【解决方案7】:

    您可以使用来自 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 可用,它甚至可以填充和同时排空多个缓冲区。

    来自this 书。

    【讨论】:

    • 如果数据被读入 JVM 的 Java 端,直接字节缓冲区没有任何好处。如果您只是在两个通道之间复制数据而不在 Java 代码中查看它,那么它的好处就来了。
    • @EJP 我知道。我在这里删除了这条线,你的评论来了。 :-)
    • @Trying ,我想尝试使用 FileChannel 你能提供我上面代码中的任何例子吗?
    • 它不能从单个磁盘并行读取,除非它有多个磁头。这里根本没有任何东西可以真正读取行,所以它根本不是问题的答案。
    • 我不仅在阅读文件,而且还在使用 delimiter 搜索我想要的单词。这种方法有效吗? if (file_Array[0].equalsIgnoreCase("case")) { //做点什么 }
    【解决方案8】:

    您必须调查程序的哪个部分需要时间。

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

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

    【讨论】:

    • 如果字符串处理需要时间,那么多个踏板做同样的事情会减少时间,对吧,就像并行处理一样。
    • 只有当一行的处理不依赖于另一行的处理时才可用。
    • 如果字符串处理是瓶颈,把它放到一个单独的线程中只会移动瓶颈。不要消除它。
    • 多线程并行处理可以消除瓶颈。
    • 并发并不总是解决方案。实际问题是 Scanner、String.split() 或 equalsIgnoreCase 的性能(因为它必须深入比较字符串)。
    猜你喜欢
    • 2011-08-13
    • 1970-01-01
    • 2012-08-26
    • 2019-07-31
    • 1970-01-01
    • 2015-09-13
    • 2012-12-11
    相关资源
    最近更新 更多