【问题标题】:Java reading a file different methodsJava读取文件的不同方法
【发布时间】:2014-02-16 22:48:12
【问题描述】:

在 Java 中读取文本文件的方法似乎有很多很多(BufferedReaderDataInputStream 等)我个人最喜欢的是Scanner,在构造函数中带有File(它更简单,有效具有更好的数学数据处理能力,并且具有熟悉的语法)。

蜘蛛鲍里斯也提到了ChannelRandomAccessFile

有人能解释一下这些方法的优缺点吗?具体来说,我想在什么时候使用它们?

(编辑)我认为我应该具体一点,并补充说我非常喜欢Scanner 方法。所以真正的问题是,什么时候不想我想使用它?

【问题讨论】:

  • 别忘了ChannelRandomAccessFile 之类的...所有这些方法都是不同的。一些读取二进制流。一些字符流。一些标记化输入。没有真正的可比性 - 它们有不同的使用场景。
  • " 他们有不同的使用场景。" 是的。这就是我想问的原因。
  • 我的“首选武器”是LineNumberReader。非常适合逐行读取和解析人类可读的文本文件,例如 csv。
  • 我真的发现 Scanner 对于读取一维空白分隔的 ascii 数据文件很有用。
  • Scanner 无法与其他人相比。这里提到的其他技术只是做 I/O。 Scanner 进行扫描和格式转换。

标签: java file-io


【解决方案1】:

让我们从头开始。问题是你想做什么?

了解文件实际上是什么很重要。文件是磁盘上的字节集合,这些字节是您的数据。 Java 提供了各种不同层次的抽象:

  1. File(Input|Output)Stream - 将这些字节作为byte 的流读取。
  2. File(Reader|Writer) - 从字节流中读取为char 的流。
  3. Scanner - 从char 流中读取并对其进行标记。
  4. RandomAccessFile - 将这些字节读取为可搜索的byte[]
  5. FileChannel - 以安全的多线程方式读取这些字节。

在每个上面都有Decorators,例如,您可以使用BufferedXXX 添加缓冲。您可以使用PrintWriterFileWriter 添加换行意识。您可以使用InputStreamReaderInputStream 转换为Reader(当前为Reader 指定字符编码的唯一方法)。

那么 - 我什么时候不想使用它 [a Scanner]?

如果你愿意,你不会使用Scanner,(这些是一些例子):

  1. bytes 的形式读入数据
  2. 读入一个序列化的 Java 对象
  3. bytes 从一个文件复制到另一个文件,可能需要一些过滤。

Scanner(File file) 构造函数采用 File 并使用 平台默认编码打开一个 FileInputStream 也是毫无价值的 - 这几乎总是一个 想法。通常认为您应该明确指定编码以避免讨厌的基于编码的错误。此外,流没有被缓冲。

所以你可能会更好

try (final Scanner scanner = new Scanner(new BufferedInputStream(new FileInputStream())), "UTF-8") {
    //do stuff
}

丑,我知道。

值得注意的是,Java 7 提供了进一步的抽象层来消除循环文件的需要 - 这些在 Files 类中:

byte[] Files.readAllBytes(Path path)
List<String> Files.readAllLines(Path path, Charset cs)

这两种方法都将整个文件读入内存,这可能不合适。在 Java 8 中,通过添加对新的 Stream API 的支持进一步改进了这一点:

Stream<String> Files.lines(Path path, Charset cs)
Stream<Path> Files.list(Path dir)

例如,要从 Path 获取单词的 Stream,您可以这样做:

    final Stream<String> words = Files.lines(Paths.get("myFile.txt")).
            flatMap((in) -> Arrays.stream(in.split("\\b")));

【讨论】:

  • 很好的推论。尤其是提到 Java 8 是值得一提的,因为这个问题涉及未来的代码设计。
  • 您是说“文件”而不是“File”吗? ;)
【解决方案2】:

SCANNER:

可以使用正则表达式解析原始类型和字符串。 Scanner 使用分隔符模式将其输入分解为标记,默认情况下匹配空格。然后可以将生成的标记转换为不同类型的值。更多信息可以在http://docs.oracle.com/javase/7/docs/api/java/util/Scanner.html阅读

DATA INPUT STREAM:

让应用程序以与机器无关的方式从底层输入流中读取原始 Java 数据类型。应用程序使用数据输出流写入数据,这些数据稍后可以由数据输入流读取。DataInputStream 对于多线程访问不一定是安全的。线程安全是可选的,并且是此类中方法的用户的责任。更多内容可以阅读http://docs.oracle.com/javase/7/docs/api/java/io/DataInputStream.html

BufferedReader:

从字符输入流中读取文本,缓冲字符,以便高效读取字符、数组和行。可以指定缓冲区大小,也可以使用默认大小。对于大多数用途,默认值足够大。通常,由 Reader 发出的每个读取请求都会导致对底层字符或字节流发出相应的读取请求。因此,建议将 BufferedReader 包装在 read() 操作可能成本高昂的任何 Reader 周围,例如 FileReaders 和 InputStreamReaders。例如,

BufferedReader in   = new BufferedReader(new FileReader("foo.in"));

将缓冲来自指定文件的输入。如果没有缓冲,每次调用 read() 或 readLine() 都可能导致从文件中读取字节,转换为字符,然后返回,这可能非常低效。使用 DataInputStreams 进行文本输入的程序可以通过替换每个带有适当 BufferedReader 的 DataInputStream。更多详细信息请参见 http://docs.oracle.com/javase/7/docs/api/java/io/BufferedReader.html

【讨论】:

    【解决方案3】:

    注意:这种方法已经过时。正如鲍里斯在评论中指出的那样。我将把它留在这里作为历史,但你应该使用 JDK 中可用的方法。

    这取决于你正在做什么类型的操作和你正在读取的文件的大小。

    在大多数情况下,我建议对小文件使用commons-io

    byte[] data = FileUtils.readFileToByteArray(new File("myfile"));
    

    您可以将其读取为字符串或字符数组...

    现在,您正在处理大文件,或者直接在文件系统上更改文件的部分内容,那么最好使用 RandomAccessFile 甚至可能使用 FileChannel 来执行“nio”样式。

    【讨论】:

    • 您能否详细说明其他方法并解释为什么您更喜欢这种方法?
    • 这是非常过时的 - JDK 使用 nio API 从 Java 7 开始就有这些方法。为什么人们仍然坚持使用基于 File 的 API,然后安装 3rd 方库,这完全超出了我的理解。但是推荐其他人采用这种方法是不好的。
    • 实际上,我认为Scanner 可以做到这一点,尽管稍微贵一点。但是文件 I/O 性能通常并没有那么重要,当它出现时,瓶颈通常是磁盘。
    【解决方案4】:

    使用 BufferedReader

    BufferedReader reader;
    char[] buffer = new char[10];
        reader = new BufferedReader(new FileReader("FILE_PATH"));
    //or
        reader = Files.newBufferedReader(Path.get("FILE_PATH"));
        while (reader.read(buffer) != -1) {
                System.out.print(new String(buffer));
                buffer = new char[10];
        }
    //or
        while (buffReader.ready()) {
                System.out.println(
                    buffReader.readLine());
            }
        reader.close();
    

    使用FileInputStream-读取二进制文件到字节

    FileInputStream fis;
    byte[] buffer = new byte[10];
        fis = new FileInputStream("FILE_PATH");
    //or
        fis=Files.newInoutSream(Paths.get("FILE_PATH"))
        while (fis.read(buffer) != -1) {
            System.out.print(new String(buffer));
            buffer = new byte[10];
        }
        fis.close();
    

    使用文件——读取小文件到字符串列表

    List<String> allLines = Files.readAllLines(Paths.get("FILE_PATH"));
    for (String line : allLines) {
        System.out.println(line);
    }
    

    使用扫描仪 - 读取文本文件作为迭代器

    Scanner scanner = new Scanner(new File("FILE_PATH"));
    while (scanner.hasNextLine()) {
        System.out.println(scanner.nextLine());
    }
    scanner.close();
    

    在只读模式下使用 RandomAccessFile 读取文件

    RandomAccessFile file = new RandomAccessFile("FILE_PATH", "r");
    String str;
    while ((str = file.readLine()) != null) {
        System.out.println(str);
    }
    file.close();
    

    使用 Files.lines-读取行作为流

    Stream<String> lines = Files.lines(Paths.get("FILE_PATH") .forEach(s -> System.out.println(s));
    

    使用 FileChannel——通过使用堆外内存以及使用 MappedByteBuffer 来提高性能

    FileInputStream i = new FileInputStream(("FILE_PATH");
            ReadableByteChannel r = i.getChannel();
            ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
            while (r.read(buffer) != -1) {
                buffer.flip();
                while (buffer.hasRemaining()) {
                        System.out.print((char) buffer.get());
                }
                buffer.clear();
            }
    

    【讨论】:

      猜你喜欢
      • 2013-02-28
      • 2019-07-31
      • 1970-01-01
      • 2016-07-10
      • 2015-09-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-24
      相关资源
      最近更新 更多