V
Vel·ToolKit
简洁 · 高效 · 即开即用
ZH
第 18 章 / 共 20 章

文件与 IO

Path、Files (NIO.2)、字节流 vs 字符流、目录遍历

IO 在 Java 中的应用场景

几乎所有非纯内存程序都要做 IO:读配置 / 写日志 / 处理上传文件 / 导出 CSV / 解析大数据。Java 的 IO API 分两代:传统 java.io(File/FileInputStream/Reader)与 NIO.2 java.nio.file(Path/Files),后者从 Java 7 开始是首选——更简洁、错误信息更好、跨平台路径处理更稳。

  • 小文件一次性读写:Files.readString / writeString
  • 大文件按行流式处理:Files.lines(结合 try-with-resources)
  • 二进制读写:Files.newInputStream / newOutputStream + BufferedStream
  • 目录遍历:Files.walk / Files.list
  • 字符 vs 字节:文本用 Reader/Writer(自动处理编码),二进制用 InputStream/OutputStream

Path:跨平台路径

Path 是 java.nio.file 的核心类,表示文件系统路径。Path.of(...) 是创建入口,能自动处理 Windows / Linux 分隔符差异。

// PathDemo.java
import java.nio.file.Path;
import java.nio.file.Paths;

public class PathDemo {
    public static void main(String[] args) {
        Path p = Path.of("data", "users", "list.json");
        System.out.println(p);                  // data/users/list.json (或 data\\users\\list.json on Windows)
        System.out.println(p.toAbsolutePath());
        System.out.println(p.getFileName());    // list.json
        System.out.println(p.getParent());       // data/users
        System.out.println(p.getName(0));        // data
        System.out.println(p.getNameCount());    // 3

        // 拼接
        Path base = Path.of("/var/log");
        Path full = base.resolve("app.log");
        System.out.println(full);                // /var/log/app.log

        // 规范化(消除 . 和 ..)
        System.out.println(Path.of("a/./b/../c").normalize()); // a/c
    }
}

小文件:一次性读写

适合配置文件、模板、小 JSON——通常几 KB ~ 几 MB。Files.readString / writeString 自动用 UTF-8(也可显式传字符集)。

// SmallFile.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;

public class SmallFile {
    public static void main(String[] args) throws IOException {
        Path p = Path.of("hello.txt");

        // 写(默认覆盖)
        Files.writeString(p, "hello\nworld\n");

        // 追加
        Files.writeString(p, "more\n", StandardOpenOption.APPEND);

        // 读字符串
        String content = Files.readString(p);
        System.out.println(content);

        // 读全部字节(二进制)
        byte[] bytes = Files.readAllBytes(p);
        System.out.println("size=" + bytes.length);

        Files.delete(p);
    }
}

大文件:按行流式处理

应用场景:分析几百 MB 的日志文件、读取 CSV 数据。Files.lines 返回一个 Stream<String>,**懒**地按行读,配合 try-with-resources 自动关闭底层文件句柄。

// BigFile.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;

public class BigFile {
    public static void main(String[] args) throws IOException {
        Path p = Files.createTempFile("log", ".txt");
        Files.writeString(p, """
            INFO  user login id=1
            ERROR db down
            INFO  user login id=2
            WARN  slow query 1200ms
            ERROR connection reset
            """);

        // 流式按行处理,自动关闭流(关键:try-with-resources)
        try (Stream<String> lines = Files.lines(p)) {
            long errCount = lines
                .filter(line -> line.startsWith("ERROR"))
                .peek(System.out::println)
                .count();
            System.out.println("errors = " + errCount);
        }

        Files.delete(p);
    }
}

字节流 vs 字符流

InputStream / OutputStream 处理字节,适合二进制(图片、PDF、压缩文件);Reader / Writer 处理字符,自动按指定编码解码字节,适合文本。BufferedReader / BufferedWriter 加缓冲,能大幅提速。

// Streams.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class Streams {
    public static void main(String[] args) throws IOException {
        Path p = Files.createTempFile("demo", ".txt");
        Files.writeString(p, "line1\nline2\nline3\n");

        // 字节流:读原始字节
        try (InputStream in = Files.newInputStream(p)) {
            int b;
            int count = 0;
            while ((b = in.read()) != -1) count++;
            System.out.println("bytes = " + count);
        }

        // 字符流 + 缓冲:按行高效读
        try (BufferedReader reader = Files.newBufferedReader(p, StandardCharsets.UTF_8)) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }

        Files.delete(p);
    }
}

目录与文件操作

// FileOps.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

public class FileOps {
    public static void main(String[] args) throws IOException {
        Path dir = Files.createTempDirectory("demo-");
        Path src = dir.resolve("a.txt");
        Path dst = dir.resolve("b.txt");

        Files.writeString(src, "hello");
        System.out.println(Files.exists(src));         // true
        System.out.println(Files.size(src));            // 5

        // 复制
        Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING);

        // 移动 / 重命名
        Path moved = dir.resolve("c.txt");
        Files.move(dst, moved, StandardCopyOption.REPLACE_EXISTING);

        // 删除
        Files.delete(src);
        Files.delete(moved);
        Files.delete(dir);
    }
}

目录遍历:Files.walk / Files.list

应用场景:扫描某个目录下所有 .java 文件统计代码行数、查找特定后缀文件、构建索引。walk 递归遍历,list 只看一层。两者都返回 Stream<Path>,**必须 try-with-resources**。

// WalkDemo.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;

public class WalkDemo {
    public static void main(String[] args) throws IOException {
        Path root = Path.of(".");

        // 只列当前目录
        try (Stream<Path> top = Files.list(root)) {
            top.forEach(System.out::println);
        }

        System.out.println("--- recursive ---");

        // 递归找所有 .java 文件
        try (Stream<Path> walk = Files.walk(root)) {
            walk.filter(Files::isRegularFile)
                .filter(p -> p.toString().endsWith(".java"))
                .limit(10)
                .forEach(System.out::println);
        }
    }
}

下载/上传:传统流写入

应用场景:HTTP 下载、网络读字节落盘、压缩文件读写。使用 InputStream.transferTo(Java 9+)一行搞定。

// Transfer.java
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;

public class Transfer {
    public static void main(String[] args) throws IOException {
        // 模拟一个网络流
        InputStream in = new ByteArrayInputStream("streaming payload".getBytes());
        Path dst = Files.createTempFile("out", ".bin");

        try (InputStream src = in; OutputStream out = Files.newOutputStream(dst)) {
            long bytes = src.transferTo(out);   // Java 9+
            System.out.println("transferred " + bytes + " bytes");
        }

        System.out.println(Files.readString(dst));
        Files.delete(dst);
    }
}