杰瑞科技汇

java inputstream 文件

核心概念:InputStream 及其子类

InputStream 本身是一个抽象类,我们不能直接实例化它,对于文件操作,我们通常会使用它的具体子类。

java inputstream 文件-图1
(图片来源网络,侵删)

FileInputStream

这是最直接、最常用的子类,用于从文件系统中的某个文件中读取字节。

BufferedInputStream

这是一个装饰器类(Decorator),它不直接与文件交互,而是包装一个其他的 InputStreamFileInputStream),为其提供一个内部缓冲区,缓冲可以大大减少直接访问磁盘的次数,从而显著提高读取性能。强烈建议在文件读取时使用它


基本用法:读取文件内容

我们将通过几个例子来演示如何使用 InputStream 读取文件,假设我们有一个名为 test.txt 的文件,内容为 "Hello, World!"。

示例 1:使用 FileInputStream(不推荐,仅作演示)

这种方式简单直接,但效率较低,因为它每次读取一个字节都可能访问磁盘。

java inputstream 文件-图2
(图片来源网络,侵删)
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class FileInputStreamExample {
    public static void main(String[] args) {
        // try-with-resources 语句,确保流在完成后自动关闭
        try (InputStream inputStream = new FileInputStream("test.txt")) {
            int byteData;
            // read() 方法读取一个字节,返回 int (0-255),
            // 如果到达文件末尾,则返回 -1
            while ((byteData = inputStream.read()) != -1) {
                // 将读取到的字节转换为字符并打印
                System.out.print((char) byteData);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

示例 2:使用 BufferedInputStream(推荐)

这是更高效、更常见的做法,我们先用 FileInputStream 打开文件,然后用 BufferedInputStream 包装它。

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class BufferedInputStreamExample {
    public static void main(String[] args) {
        try (InputStream fileInputStream = new FileInputStream("test.txt");
             // 用 BufferedInputStream 包装 FileInputStream
             BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream)) {
            int byteData;
            while ((byteData = bufferedInputStream.read()) != -1) {
                System.out.print((char) byteData);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注意try-with-resources 语句会按照声明的逆序自动关闭资源。bufferedInputStream 会先被关闭,然后是 fileInputStream


高级用法:读取字节数组

逐字节读取对于大文件来说效率依然不高,更常见的方式是读取到一个字节数组中。

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class ReadByteArrayExample {
    public static void main(String[] args) {
        // 定义一个 1024 字节的缓冲区
        byte[] buffer = new byte[1024];
        int bytesRead;
        try (InputStream inputStream = new BufferedInputStream(new FileInputStream("test.txt"))) {
            // read(byte[] b) 方法尝试将数据读入字节数组 b,
            // 返回实际读取的字节数,如果到达文件末尾,返回 -1。
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                // 将读取到的字节转换为字符串并打印
                // 注意:使用 String 构造函数时,指定偏移量和长度,避免打印出缓冲区中未填充的旧数据
                System.out.write(buffer, 0, bytesRead);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这种方法比逐字节读取快得多,因为它每次可以从磁盘读取一大块数据到内存中。

java inputstream 文件-图3
(图片来源网络,侵删)

最佳实践

  1. 始终使用 try-with-resources:从 Java 7 开始,这是处理 InputStreamOutputStream 等资源的标准方式,它可以确保资源在任何情况下(无论是正常结束还是发生异常)都会被正确关闭,避免了资源泄漏。

  2. 优先使用缓冲流:对于文件、网络等 I/O 操作,始终使用 BufferedInputStreamBufferedOutputStream 来包装原始流,这是提升性能最简单有效的方法。

  3. 指定正确的字符编码InputStream 读取的是原始字节,如果你需要将字节转换为字符串(例如读取文本文件),必须指定正确的字符编码(如 UTF-8),否则可能会出现乱码,直接将字节强制转换为 char 是不正确的,因为一个字符可能由多个字节组成。

    // 错误的转换方式
    // String str = new String(buffer); // 如果文件编码不是平台默认编码,会乱码
    // 正确的转换方式
    String str = new String(buffer, "UTF-8");
  4. 处理大文件:对于非常大的文件(如 GB 级别),不要一次性将所有内容读入内存,应该采用循环读取的方式,每次处理一个缓冲块的数据。


处理不同字符编码的文本文件

虽然 InputStream 是字节流,但我们通常用它来读取文本,为了正确处理字符,Java 提供了 Reader 体系,特别是 InputStreamReader,它可以在 InputStreamReader 之间架起一座桥梁。

InputStreamReader 可以将字节流按照指定的字符编码转换为字符流。

import java.io.*;
import java.nio.charset.StandardCharsets; // 推荐使用 StandardCharsets
public class ReadTextWithEncodingExample {
    public static void main(String[] args) {
        // 文件路径
        String filePath = "test_utf8.txt"; // 假设这是一个 UTF-8 编码的文件
        try (
            // 1. 创建字节输入流
            InputStream inputStream = new FileInputStream(filePath);
            // 2. 使用 InputStreamReader 将字节流转换为字符流,并指定编码
            Reader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
            // 3. 使用 BufferedReader 进行高效读取(装饰 Reader)
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader)
        ) {
            String line;
            // 4. 按行读取,直到文件末尾
            while ((line = bufferedReader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • InputStream:处理字节,适合处理二进制文件(如图片、视频、音频)。
  • Reader:处理字符,适合处理文本文件。
  • InputStreamReader:是连接 InputStreamReader 的桥梁,用于按指定编码读取文本。

现代替代方案:java.nio.file (NIO.2)

从 Java 7 开始,Java 引入了新的 I/O API,位于 java.nio.file 包中,NIO.2 提供了更强大、更灵活的文件操作能力,通常被认为是比传统 I/O 更好的选择。

使用 Files.newInputStream()

Files 类提供了很多静态工具方法,可以更方便地获取 InputStream

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class NioInputStreamExample {
    public static void main(String[] args) {
        Path path = Paths.get("test.txt");
        try (InputStream inputStream = Files.newInputStream(path)) {
            // 使用 NIO.2 的方式读取
            byte[] allBytes = Files.readAllBytes(path); // 如果文件不大,可以一次性读取
            System.out.println(new String(allBytes, StandardCharsets.UTF_8));
            // 或者使用流式处理
            // ... 这里可以结合上面的字节读取或 BufferedReader 逻辑 ...
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

使用 Files.lines() (读取文本行的最佳方式)

如果你只想读取文本文件的每一行,Files.lines() 是最简洁、最高效的方式,它返回一个 Stream<String>,可以方便地进行流式操作。

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;
public class NioLinesExample {
    public static void main(String[] args) {
        Path path = Paths.get("test.txt");
        try (Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8)) {
            // 使用流式操作,例如打印每一行
            lines.forEach(System.out::println);
            // 或者
分享:
扫描分享到社交APP
上一篇
下一篇