10、Java 基础 I/O 类库详解

【投稿赢 iPhone 17】「我的第一个开源项目」故事征集:用代码换C位出道! 10w+人浏览 1.6k人参与

开篇概览:Java I/O 体系结构

Java 的输入输出(I/O)系统基于流(Stream) 模型,所有数据读写都通过流进行。java.io 包提供了丰富的类库,分为两大体系:

  1. 文件操作基础File 类 —— 用于管理文件/目录元数据(路径、属性、创建/删除等),不涉及内容读写
  2. 流式数据传输
    • 字节流(Byte Streams):处理原始二进制数据(如图片、音频、视频),以 InputStream/OutputStream 为基类;
    • 字符流(Character Streams):专用于文本数据(自动处理字符编码),以 Reader/Writer 为基类;
    • 桥梁流(Bridge Streams):连接字节流与字符流(如 InputStreamReader);
    • 缓冲流(Buffered Streams):提升 I/O 效率。

⚠️ 重要前提

  • File 不能读写文件内容,仅操作文件系统元信息;
  • 所有流操作后必须关闭资源,推荐使用 try-with-resources 自动管理。

一、File 类:文件与目录的抽象表示

1.1 核心功能

  • 表示文件或目录的路径(绝对/相对);
  • 创建/删除/重命名文件或目录;
  • 查询文件属性(大小、权限、是否存在等);
  • 不涉及文件内容读写

1.2 常用构造方法与方法

方法说明
File(String pathname)通过路径字符串创建 File 对象
boolean exists()判断文件/目录是否存在
boolean isFile() / isDirectory()判断是否为文件/目录
long length()获取文件大小(字节)
boolean createNewFile()创建新文件(若不存在)
boolean mkdir() / mkdirs()创建目录(mkdirs() 可创建多级目录)
boolean delete()删除文件或空目录
String[] list() / File[] listFiles()列出目录内容

1.3 示例:File 类使用(详细中文注释)

import java.io.File;
import java.io.IOException;

public class FileDemo {
    public static void main(String[] args) {
        // 1. 创建 File 对象(表示一个文件路径)
        String filePath = "example.txt";
        File file = new File(filePath);

        // 2. 检查文件是否存在
        if (!file.exists()) {
            try {
                // 创建新文件(返回 true 表示创建成功)
                boolean created = file.createNewFile();
                System.out.println("文件创建成功: " + created);
            } catch (IOException e) {
                System.err.println("创建文件时发生错误: " + e.getMessage());
            }
        } else {
            System.out.println("文件已存在");
        }

        // 3. 获取文件基本信息
        System.out.println("文件名: " + file.getName());
        System.out.println("绝对路径: " + file.getAbsolutePath());
        System.out.println("是否为文件: " + file.isFile());
        System.out.println("文件大小(字节): " + file.length());

        // 4. 创建目录示例
        File dir = new File("testDir/subDir");
        if (dir.mkdirs()) { // mkdirs() 可创建多级目录
            System.out.println("目录创建成功: " + dir.getAbsolutePath());
        }

        // 5. 列出目录内容
        File parentDir = new File("."); // 当前目录
        if (parentDir.isDirectory()) {
            String[] files = parentDir.list(); // 获取文件名数组
            System.out.println("当前目录下的文件/目录:");
            for (String name : files) {
                System.out.println("  - " + name);
            }
        }

        // 6. 删除文件(程序结束前删除示例文件)
        // 注意:delete() 不能删除非空目录
        if (file.exists()) {
            boolean deleted = file.delete();
            System.out.println("文件删除成功: " + deleted);
        }
    }
}

关键点

  • File平台无关的路径表示;
  • 路径分隔符使用 /File.separator(避免硬编码 \);
  • delete() 无法删除非空目录(需先清空内容)。

二、字节流(Byte Streams):处理二进制数据

2.1 体系结构

InputStream(抽象基类)
├── FileInputStream       // 从文件读取字节
├── BufferedInputStream   // 带缓冲的输入流(提升效率)
└── ...(其他子类)

OutputStream(抽象基类)
├── FileOutputStream      // 向文件写入字节
├── BufferedOutputStream  // 带缓冲的输出流
└── ...(其他子类)

2.2 核心特点

  • 8 位字节(byte) 为单位读写;
  • 适用于所有类型数据(文本、图片、视频等);
  • 不处理字符编码

2.3 示例:FileInputStream / FileOutputStream(复制图片)

import java.io.*;

public class ByteStreamDemo {
    public static void main(String[] args) {
        // 源文件(假设项目根目录有 input.jpg)
        String sourcePath = "input.jpg";
        // 目标文件
        String targetPath = "output.jpg";

        // 使用 try-with-resources 自动关闭流
        try (FileInputStream fis = new FileInputStream(sourcePath);
             FileOutputStream fos = new FileOutputStream(targetPath)) {

            byte[] buffer = new byte[1024]; // 缓冲区(1KB)
            int bytesRead;

            // 循环读取,直到返回 -1(表示文件结束)
            while ((bytesRead = fis.read(buffer)) != -1) {
                // 将读取的字节写入目标文件
                fos.write(buffer, 0, bytesRead);
            }

            System.out.println("图片复制成功!");

        } catch (FileNotFoundException e) {
            System.err.println("文件未找到: " + e.getMessage());
        } catch (IOException e) {
            System.err.println("I/O 错误: " + e.getMessage());
        }
    }
}

优化建议

  • 使用缓冲区(如 byte[1024])避免频繁系统调用;
  • 永远不要用单字节读写read()/write(int)),效率极低。

2.4 示例:BufferedInputStream / BufferedOutputStream(带缓冲的字节流)

import java.io.*;

public class BufferedByteStreamDemo {
    public static void main(String[] args) {
        try (// 包装 FileInputStream 为 BufferedInputStream
             BufferedInputStream bis = new BufferedInputStream(new FileInputStream("data.bin"));
             BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.bin"))) {

            int data;
            // 逐字节读取(因有缓冲,实际效率高)
            while ((data = bis.read()) != -1) {
                bos.write(data);
            }
            // 缓冲流会在 close() 时自动 flush()

            System.out.println("二进制文件复制完成(使用缓冲流)");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

缓冲流优势

  • 内部维护缓冲区,减少底层 I/O 调用次数;
  • BufferedOutputStreamclose()flush() 时强制写入数据。

三、字符流(Character Streams):处理文本数据

3.1 体系结构

Reader(抽象基类)
├── FileReader            // 从文件读取字符(使用默认编码)
├── BufferedReader        // 带缓冲的字符输入流
├── InputStreamReader     // 将字节流转换为字符流(可指定编码)
└── ...

Writer(抽象基类)
├── FileWriter            // 向文件写入字符(使用默认编码)
├── BufferedWriter         // 带缓冲的字符输出流
├── OutputStreamWriter    // 将字符流转换为字节流(可指定编码)
└── ...

3.2 核心特点

  • 16 位字符(char) 为单位读写;
  • 自动处理字符编码(如 UTF-8、GBK);
  • 仅适用于文本数据

3.3 示例:FileReader / FileWriter(读写文本文件)

import java.io.*;

public class CharStreamDemo {
    public static void main(String[] args) {
        String textFile = "message.txt";

        // 1. 写入文本
        try (FileWriter writer = new FileWriter(textFile)) {
            writer.write("Hello, Java 字符流!\n");
            writer.write("这是第二行文本。");
            // FileWriter 会自动使用系统默认编码(如 Windows: GBK, Linux/macOS: UTF-8)
            System.out.println("文本写入完成");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 2. 读取文本
        try (FileReader reader = new FileReader(textFile)) {
            int ch;
            System.out.println("读取内容:");
            while ((ch = reader.read()) != -1) {
                System.out.print((char) ch);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

⚠️ 问题FileReader/FileWriter 无法指定编码,易导致乱码!


3.4 示例:InputStreamReader / OutputStreamWriter(指定编码)

import java.io.*;
import java.nio.charset.StandardCharsets;

public class BridgeStreamDemo {
    public static void main(String[] args) {
        String textFile = "utf8_message.txt";

        // 1. 使用 UTF-8 编码写入
        try (FileOutputStream fos = new FileOutputStream(textFile);
             OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8)) {

            osw.write("中文内容:Java I/O 支持 UTF-8 编码!\n");
            osw.write("English: UTF-8 is safe for multilingual text.");
            System.out.println("UTF-8 文本写入完成");

        } catch (IOException e) {
            e.printStackTrace();
        }

        // 2. 使用 UTF-8 编码读取
        try (FileInputStream fis = new FileInputStream(textFile);
             InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8)) {

            char[] buffer = new char[100];
            int charsRead;
            System.out.println("UTF-8 读取内容:");
            while ((charsRead = isr.read(buffer)) != -1) {
                System.out.print(new String(buffer, 0, charsRead));
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

最佳实践

  • 永远显式指定编码(推荐 StandardCharsets.UTF_8);
  • 避免依赖系统默认编码(跨平台易乱码)。

3.5 示例:BufferedReader / BufferedWriter(高效文本处理)

import java.io.*;
import java.nio.charset.StandardCharsets;

public class BufferedCharStreamDemo {
    public static void main(String[] args) {
        String logFile = "app.log";

        // 1. 写入多行日志(使用 BufferedWriter)
        try (BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(new FileOutputStream(logFile), StandardCharsets.UTF_8))) {

            writer.write("INFO: 应用启动");
            writer.newLine(); // 写入系统换行符(\n 或 \r\n)
            writer.write("DEBUG: 初始化配置完成");
            writer.newLine();
            writer.write("ERROR: 数据库连接失败");
            // BufferedWriter 会在 close() 时自动 flush()

            System.out.println("日志写入完成");

        } catch (IOException e) {
            e.printStackTrace();
        }

        // 2. 按行读取日志(使用 BufferedReader)
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(new FileInputStream(logFile), StandardCharsets.UTF_8))) {

            String line;
            System.out.println("日志内容:");
            while ((line = reader.readLine()) != null) { // readLine() 自动处理换行符
                System.out.println(line);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

BufferedReader 优势

  • readLine() 方法简化文本行读取;
  • 内部缓冲提升性能;
  • 自动处理不同平台的换行符(\n, \r\n)。

四、I/O 流选择指南

需求推荐流
读写二进制文件(图片、视频)FileInputStream / FileOutputStream + Buffered 包装
读写文本文件(指定编码)InputStreamReader / OutputStreamWriter + Buffered 包装
按行处理文本BufferedReaderreadLine()
高效写入多行文本BufferedWriterwrite() + newLine()
避免乱码永远显式指定编码(如 StandardCharsets.UTF_8

五、关键注意事项

5.1 资源管理

  • 必须关闭流!否则可能导致:
    • 文件句柄泄漏;
    • 缓冲数据未写入(BufferedOutputStream);
  • 推荐使用 try-with-resources(Java 7+):
    try (InputStream is = new FileInputStream("file.txt")) {
        // 使用流
    } // 自动调用 is.close()
    

5.2 编码问题

  • FileReader/FileWriter 使用系统默认编码,跨平台易乱码;
  • 始终使用 InputStreamReader/OutputStreamWriter 并指定编码

5.3 性能优化

  • 避免单字节/字符读写
  • 使用缓冲流BufferedInputStream/BufferedReader);
  • 缓冲区大小通常设为 8KB~64KB(如 new byte[8192])。

六、总结

组件作用关键类
File文件/目录元操作File
字节流二进制数据读写FileInputStream, BufferedOutputStream
字符流文本数据读写FileReader, BufferedWriter
桥梁流字节流 ↔ 字符流InputStreamReader, OutputStreamWriter

📌 核心原则
“文本用字符流(指定编码),二进制用字节流,永远用缓冲,资源自动关。”
掌握 Java I/O 基础,是处理文件、网络、数据库等数据传输场景的必备能力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙茶清欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值