404. Java 文件操作基础 - 使用 Channel I/O 读写文件

12 阅读2分钟

404. Java 文件操作基础 - 使用 Channel I/O 读写文件

1️⃣ 背景介绍

  • Stream I/O:一次处理一个字符(或字节),适合顺序读取或写入。
  • Channel I/O:一次处理一个 缓冲区(Buffer),可以更高效地进行读写,尤其适合大文件。
  • ByteChannel 提供基本的读写方法。
  • SeekableByteChannelByteChannel 的扩展,支持:
    • 随机访问文件(移动到任意位置读写)
    • 文件截断
    • 查询文件大小
    • 支持 READ、WRITE、APPEND 等操作

Channel I/O 特别适合需要随机访问或高性能 I/O 的场景。


2️⃣ 常用方法

方法功能
Files.newByteChannel(Path, OpenOption...)打开或创建文件,返回 SeekableByteChannel
Files.newByteChannel(Path, Set<? extends OpenOption>, FileAttribute<?>...)可以指定一组 OpenOption 和文件属性

默认返回 SeekableByteChannel,在默认文件系统中可以直接 cast 成 FileChannel,从而使用更多高级功能:

  • 文件映射(memory-mapped I/O
  • 文件锁定(防止其他进程访问)
  • 从任意位置读写数据而不影响当前位置

3️⃣ 示例 1:读取文件并打印内容

public static void readFile(Path path) throws IOException {
    // 默认以 READ 模式打开
    try (SeekableByteChannel sbc = Files.newByteChannel(path)) {
        final int BUFFER_CAPACITY = 10; // 每次读取 10 个字节
        ByteBuffer buf = ByteBuffer.allocate(BUFFER_CAPACITY);

        String encoding = System.getProperty("file.encoding"); // 平台默认编码
        while (sbc.read(buf) > 0) {
            buf.flip(); // 切换为读取模式
            System.out.print(Charset.forName(encoding).decode(buf));
            buf.clear(); // 清空缓冲区以便下一轮写入
        }
    }
}

讲解要点

  1. 使用 ByteBuffer 来读取字节块,提高效率
  2. buf.flip() → 切换缓冲区为读取模式
  3. buf.clear() → 清空缓冲区,为下一次读取准备
  4. 默认使用平台编码解码字符,避免乱码

4️⃣ 示例 2:写入带权限的日志文件(POSIX 文件系统)

import static java.nio.file.StandardOpenOption.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.io.*;
import java.util.*;

public class LogFilePermissionsTest {

    public static void main(String[] args) {

        // 设置写入选项:追加 + 创建
        Set<OpenOption> options = new HashSet<>();
        options.add(APPEND);
        options.add(CREATE);

        // 设置文件权限:owner rw, group r, others 无权限
        Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-r-----");
        FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermissions.asFileAttribute(perms);

        // 准备写入的字节数据
        String s = "Hello World! ";
        ByteBuffer bb = ByteBuffer.wrap(s.getBytes());

        Path file = Paths.get("./permissions.log");

        try (SeekableByteChannel sbc = Files.newByteChannel(file, options, attr)) {
            sbc.write(bb);
            System.out.println("写入完成: " + s);
        } catch (IOException x) {
            System.err.println("写入异常: " + x);
        }
    }
}

讲解要点

  1. Files.newByteChannel() 可同时指定 写入选项 + 文件属性
  2. PosixFilePermissions.fromString("rw-r-----") → 设置 Unix 风格文件权限
  3. 使用 ByteBuffer.wrap() 将字节数组封装为缓冲区
  4. APPEND + CREATE → 文件不存在则创建,存在则追加内容

5️⃣ 总结

✅ Channel I/O 优势

  • 批量处理数据,提高效率
  • 支持随机访问(SeekableByteChannel)
  • 支持高级功能(FileChannel):
    • Memory-mapped I/O
    • 文件锁定
    • 从任意位置读写数据

✅ 使用技巧

  • flip()clear() 是 ByteBuffer 操作关键
  • try-with-resources 保证资源释放
  • POSIX 文件权限可以在创建文件时直接设置

🔹 建议练习

  1. 使用 Channel I/O 读取大文件并统计行数
  2. 写一个随机写入示例:在文件中间插入内容
  3. 在不同操作系统上验证 POSIX 权限设置的效果