Java 输入输出(IO)
程序离不开 IO——读文件、写文件、网络传输、用户输入。Java 提供了三套 IO 体系:传统字节流/字符流(java.io)、NIO(java.nio)、NIO.2(Files API)。本章带你掌握日常最常用的部分,过滤掉冷门细节。
1. 三套 IO API 的关系
Java IO 经历了三代演进:
- java.io(Java 1.0):字节流(InputStream/OutputStream)+ 字符流(Reader/Writer)+ File 类。基于阻塞模型。
- java.nio(Java 1.4):Channel + Buffer + Selector,非阻塞 IO,适合高并发网络。
- java.nio.file(Java 7+,NIO.2):Path + Files,API 极度简洁,现代项目首选。
新手建议:文件读写直接用 NIO.2(Files.readString),传统字节/字符流了解原理即可。
2. File 类:文件/目录操作
File 类表示文件或目录的路径(不读写内容),可以查询属性、创建、删除、列举:
import java.io.File;
import java.io.IOException;
public class FileDemo {
public static void main(String[] args) throws IOException {
// File 类:操作文件/目录的路径(不读写内容)
File f = new File("test.txt");
System.out.println(f.exists()); // 是否存在
System.out.println(f.isFile()); // 是文件吗
System.out.println(f.isDirectory()); // 是目录吗
System.out.println(f.length()); // 字节数
System.out.println(f.getAbsolutePath()); // 绝对路径
// 创建文件
if (!f.exists()) {
boolean ok = f.createNewFile(); // 创建空文件
System.out.println("创建: " + ok);
}
// 创建目录
File dir = new File("mydir/sub");
dir.mkdirs(); // 创建多级目录
// 列出目录内容
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
System.out.println(file.getName());
}
}
// 删除文件(空目录才能删)
// f.delete();
}
}注意 File 不能读写文件内容,只能操作路径。新项目建议用 Path(NIO.2)替代。
3. 字节流(InputStream / OutputStream)
字节流以字节(byte)为单位,适合处理二进制文件(图片、视频、zip)。
import java.io.*;
public class ByteStreamDemo {
public static void main(String[] args) throws IOException {
// 写字节到文件
// try-with-resources 自动关闭流
try (FileOutputStream fos = new FileOutputStream("data.bin")) {
fos.write(72); // 写一个字节
fos.write(105);
byte[] bytes = "Hello".getBytes();
fos.write(bytes);
}
// 读字节
try (FileInputStream fis = new FileInputStream("data.bin")) {
int b;
// read() 一次读一个字节,返回 -1 表示结束
while ((b = fis.read()) != -1) {
System.out.print((char) b + " ");
}
System.out.println();
}
// 一次读一个字节效率低,推荐用缓冲流或一次读一段
try (FileInputStream fis = new FileInputStream("data.bin")) {
byte[] buf = new byte[1024];
int len = fis.read(buf); // 返回实际读取的字节数
String content = new String(buf, 0, len);
System.out.println(content);
}
}
}几个要点:
FileInputStream.read()一次读一个字节,返回 -1 表示结束。- 一次读一个字节效率低,实际用缓冲流(BufferedInputStream)或一次读一段(byte[] buf)。
- 读出来的字节转成字符串要指定编码,否则可能乱码。
- 流用完必须关闭,否则资源泄漏!推荐 try-with-resources。
4. 字符流(Reader / Writer)
字符流以字符(char)为单位,自动处理编码,适合文本文件(txt、csv、json、html)。最常用 BufferedReader 逐行读:
import java.io.*;
import java.nio.charset.StandardCharsets;
public class ReaderDemo {
public static void main(String[] args) throws IOException {
// 字符流:专门处理文本文件(自动处理编码)
// 写文本
try (BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("text.txt"),
StandardCharsets.UTF_8))) {
bw.write("第一行");
bw.newLine(); // 跨平台的换行
bw.write("第二行");
bw.write("你好,Java IO!");
}
// 读文本:逐行读(最常用)
try (BufferedReader br = new BufferedReader(
new InputStreamReader(
new FileInputStream("text.txt"),
StandardCharsets.UTF_8))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
// 简化写法:FileReader/FileWriter(用默认编码,Java 11+ 可指定)
try (BufferedReader br = new BufferedReader(
new FileReader("text.txt", StandardCharsets.UTF_8))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(">> " + line);
}
}
}
}字节流 vs 字符流的区别:
- 字节流:处理一切(图片、视频、二进制),但不适合中文文本(需要自己处理编码)。
- 字符流:专门处理文本,自动编解码。
- 经验法则:二进制用字节流,文本用字符流。
5. NIO.2:Path 与 Files(推荐!)
Java 7 引入的 Path + Files 是现代 IO 的首选——API 极简,自动管理资源:
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.util.*;
public class NioDemo {
public static void main(String[] args) throws IOException {
// NIO(Path + Files)是 Java 7+ 引入的现代 IO API
// 比 File 更简洁、更安全、功能更强
// 1. 一次性写文本(不用管流的关闭)
Path p = Path.of("hello.txt");
Files.writeString(p, "Hello, NIO!\n你好!");
// 2. 一次性读文本
String content = Files.readString(p);
System.out.println(content);
// 3. 读所有行(返回 List<String>)
List<String> lines = Files.readAllLines(p);
for (String line : lines) {
System.out.println(line);
}
// 4. 复制、移动、删除文件
Path target = Path.of("hello_copy.txt");
Files.copy(p, target, StandardCopyOption.REPLACE_EXISTING);
// Files.move(p, newPath); // 移动/重命名
// Files.delete(p); // 删除
// 5. 创建目录
Files.createDirectories(Path.of("a/b/c"));
// 6. 列出目录内容(Java 8 Stream)
try (var stream = Files.list(Path.of("."))) {
stream.filter(Files::isRegularFile)
.forEach(System.out::println);
}
// 7. 文件是否存在、大小
System.out.println(Files.exists(p)); // true
System.out.println(Files.size(p)); // 字节数
}
}对比传统 IO,NIO.2 的优势:
- 一行读写整个文件:
Files.readString/Files.writeString,不用手动 close。 - 跨平台路径:
Path.of("a", "b", "c.txt")自动处理分隔符。 - 异常更细:细分 NoSuchFileException、AccessDeniedException 等。
- 支持 Stream:
Files.lines(path)返回 Stream,可以链式过滤。
新项目几乎都用 NIO.2。传统 IO 只在维护老代码或框架内部见到。
6. 标准输入:Scanner
从控制台读用户输入,最简单的方式是 Scanner(基于 System.in):
import java.util.Scanner;
public class StdinDemo {
public static void main(String[] args) {
// Scanner:从标准输入(System.in)读用户输入
Scanner sc = new Scanner(System.in);
System.out.print("请输入你的名字: ");
String name = sc.nextLine(); // 读一行
System.out.println("你好, " + name);
System.out.print("请输入你的年龄: ");
if (sc.hasNextInt()) {
int age = sc.nextInt(); // 读 int
System.out.println("明年你 " + (age + 1) + " 岁");
} else {
System.out.println("不是有效的整数");
sc.next(); // 跳过错误的输入
}
System.out.print("输入三个数(空格分隔): ");
int a = sc.nextInt();
int b = sc.nextInt();
int c = sc.nextInt();
System.out.println("和 = " + (a + b + c));
sc.close(); // Scanner 也是资源,要关闭
}
}7. IO 处理的最佳实践
- 始终用 try-with-resources:避免资源泄漏,代码更简洁。
- 始终指定字符编码:Java 18 之前默认编码依赖平台,跨平台会乱码。
StandardCharsets.UTF_8。 - 大文件用缓冲:BufferedInputStream / BufferedReader 提升几个数量级性能。
- 超大文件用流式处理:
Files.lines()返回 Stream,逐行处理不会 OOM。 - 路径用 Path 不用 File:新代码统一 NIO.2。
小结
这一章你掌握了 Java IO 的核心:File、字节流、字符流、NIO.2(Files)、Scanner。新项目首选 NIO.2——简洁、安全、现代。下一篇我们看 Java 区别于其他语言的杀手锏——多线程并发。
← 上一篇 Java 异常处理
下一篇 Java 多线程 →