Java IO 流与配置文件
本文系统讲解 Java IO 流与 Properties 配置文件,覆盖字节流、字符流、文件读写、文件复制、异常处理、资源关闭和配置文件读取保存等内容。文章通过案例演示程序如何与外部文件交换数据,帮助读者理解 Java 文件操作的核心流程。
第一章 IO 流的介绍和分类
1.1 什么是 IO 流
IO 是 Input 和 Output 的缩写:
- Input:输入,把外部数据读入程序。
- Output:输出,把程序数据写到外部。
流可以理解成数据管道:
文件 ----输入流----> Java 程序
Java 程序 ----输出流----> 文件
1.2 按数据单位分类
字节流以字节为单位,能处理所有文件:
InputStreamOutputStreamFileInputStreamFileOutputStream
字符流以字符为单位,专门处理纯文本:
ReaderWriterFileReaderFileWriter
图片、视频、压缩包用字节流;普通文本优先用字符流。
1.3 按流向分类
判断流向时,以 Java 程序为参照:
| 分类 | 说明 |
|---|---|
| 输入流 | 外部数据进入程序 |
| 输出流 | 程序数据写到外部 |
1.4 按功能分类
| 分类 | 说明 | 示例 |
|---|---|---|
| 节点流 | 直接连接数据源 | FileInputStream、FileReader |
| 处理流 | 包装其他流增强功能 | BufferedInputStream、BufferedReader |
第二章 FileOutputStream 写出数据
2.1 基本使用
FileOutputStream 用来把字节写入文件。
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamDemo {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("a.txt");
fos.write(97);
fos.close();
}
}
97 对应字符 a。
2.2 写字节数组
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class WriteBytesDemo {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("a.txt");
byte[] data = "你好,Java".getBytes(StandardCharsets.UTF_8);
fos.write(data);
fos.close();
}
}
2.3 写数组的一部分
import java.io.FileOutputStream;
import java.io.IOException;
public class WritePartBytesDemo {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("a.txt");
byte[] data = {97, 98, 99, 100};
fos.write(data, 1, 2); // 写 b 和 c
fos.close();
}
}
2.4 覆盖写和追加写
默认覆盖原文件:
FileOutputStream fos = new FileOutputStream("a.txt");
追加写:
FileOutputStream fos = new FileOutputStream("a.txt", true);
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class AppendDemo {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("log.txt", true);
fos.write(("登录成功" + System.lineSeparator()).getBytes(StandardCharsets.UTF_8));
fos.close();
}
}
第三章 IO 流标准异常处理代码
3.1 finally 写法
真实开发中,流必须可靠关闭。JDK7 前常用 finally:
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class FinallyCloseDemo {
public static void main(String[] args) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream("a.txt");
fos.write("hello".getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3.2 try-with-resources
JDK7 开始推荐这种写法。资源实现了 AutoCloseable,就能自动关闭。
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class TryWithResourcesDemo {
public static void main(String[] args) {
try (FileOutputStream fos = new FileOutputStream("a.txt")) {
fos.write("hello".getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
}
}
}
多个资源会按创建顺序的反方向关闭:
try (FileInputStream fis = new FileInputStream("source.txt");
FileOutputStream fos = new FileOutputStream("target.txt")) {
// 先使用 fis 和 fos,结束时先关 fos,再关 fis。
}
3.3 异常处理建议
- 学习阶段可以使用
e.printStackTrace()。 - 项目中通常使用日志框架记录异常。
- 不要 catch 后什么都不做。
- 不要无脑写
catch (Exception e)。 - IO 资源优先使用
try-with-resources。
第四章 FileInputStream 读取数据
4.1 一次读取一个字节
import java.io.FileInputStream;
import java.io.IOException;
public class ReadOneByteDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("a.txt");
int b;
while ((b = fis.read()) != -1) {
System.out.print((char) b);
}
fis.close();
}
}
read() 返回 int,读不到数据时返回 -1。
4.2 一次读取一个字节数组
import java.io.FileInputStream;
import java.io.IOException;
public class ReadBufferDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("a.txt");
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
String text = new String(buffer, 0, len);
System.out.print(text);
}
fis.close();
}
}
最后一次读取通常不会填满数组,所以必须用 new String(buffer, 0, len)。
4.3 中文乱码原因
中文通常由多个字节组成。字节流读取文本时,如果把一个中文字符拆开处理,就可能乱码。处理纯文本时,优先使用字符流,并明确编码。
第五章 字节流案例:文件拷贝
5.1 文件拷贝的本质
文件拷贝就是一边读,一边写:
源文件 ----FileInputStream----> 程序内存 ----FileOutputStream----> 目标文件
5.2 基础版文件拷贝
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyFileDemo {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("source.jpg");
FileOutputStream fos = new FileOutputStream("target.jpg")) {
byte[] buffer = new byte[1024 * 8];
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
这个版本可以复制图片、视频、文本、压缩包等任意文件。
5.3 封装文件复制方法
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopyUtils {
/**
* 使用字节流复制文件。
*
* @param sourcePath 源文件路径
* @param targetPath 目标文件路径
* @throws IOException 文件不存在、无权限或磁盘异常时抛出
*/
public static void copyFile(String sourcePath, String targetPath) throws IOException {
try (FileInputStream fis = new FileInputStream(sourcePath);
FileOutputStream fos = new FileOutputStream(targetPath)) {
byte[] buffer = new byte[1024 * 8];
int len;
while ((len = fis.read(buffer)) != -1) {
// 关键业务逻辑:最后一次读取可能不足整个缓冲区,只能写入实际读取到的 len 个字节。
fos.write(buffer, 0, len);
}
}
}
}
缓冲区常用 8192 字节,也就是 8KB。它能减少磁盘读写次数,比一个字节一个字节复制效率高很多。
第六章 FileReader 读取数据
6.1 FileReader 基本使用
FileReader 是字符输入流,适合读取文本。
import java.io.FileReader;
import java.io.IOException;
public class FileReaderDemo {
public static void main(String[] args) throws IOException {
FileReader reader = new FileReader("a.txt");
int ch;
while ((ch = reader.read()) != -1) {
System.out.print((char) ch);
}
reader.close();
}
}
6.2 字符数组读取
import java.io.FileReader;
import java.io.IOException;
public class FileReaderBufferDemo {
public static void main(String[] args) throws IOException {
FileReader reader = new FileReader("a.txt");
char[] buffer = new char[1024];
int len;
while ((len = reader.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, len));
}
reader.close();
}
}
6.3 指定编码读取
传统 FileReader 使用系统默认编码。如果文件编码和系统默认编码不一致,可能乱码。JDK11 开始可以指定编码:
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class FileReaderCharsetDemo {
public static void main(String[] args) throws IOException {
try (FileReader reader = new FileReader("a.txt", StandardCharsets.UTF_8)) {
char[] buffer = new char[1024];
int len;
while ((len = reader.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, len));
}
}
}
}
低版本 JDK 可以使用 InputStreamReader 指定编码。
第七章 FileWriter 写出数据
7.1 FileWriter 基本使用
FileWriter 是字符输出流,适合写文本。
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterDemo {
public static void main(String[] args) throws IOException {
FileWriter writer = new FileWriter("a.txt");
writer.write("你好,Java");
writer.write(System.lineSeparator());
writer.write("字符流适合写文本");
writer.close();
}
}
7.2 写字符、字符串、字符数组
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterMethodsDemo {
public static void main(String[] args) throws IOException {
FileWriter writer = new FileWriter("a.txt");
writer.write('A');
writer.write("BCDE");
char[] chars = {'你', '好', 'J', 'a', 'v', 'a'};
writer.write(chars);
writer.write(chars, 0, 2);
writer.close();
}
}
7.3 追加写、flush 和 close
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterAppendDemo {
public static void main(String[] args) throws IOException {
FileWriter writer = new FileWriter("log.txt", true);
writer.write("用户登录成功");
writer.write(System.lineSeparator());
writer.flush(); // 刷新后还能继续写
writer.write("继续记录日志");
writer.close(); // 关闭前会自动刷新,关闭后不能继续写
}
}
推荐写法:
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterTryDemo {
public static void main(String[] args) {
try (FileWriter writer = new FileWriter("a.txt")) {
writer.write("自动关闭资源");
} catch (IOException e) {
e.printStackTrace();
}
}
}
第八章 Properties 集合
8.1 Properties 是什么
Properties 常用于读取和保存 .properties 配置文件。配置文件通常是字符串键值对。
username=root
password=123456
url=jdbc:mysql://localhost:3306/test
driver=com.mysql.cj.jdbc.Driver
8.2 基本操作
import java.util.Properties;
public class PropertiesBasicDemo {
public static void main(String[] args) {
Properties properties = new Properties();
properties.setProperty("username", "root");
properties.setProperty("password", "123456");
System.out.println(properties.getProperty("username"));
System.out.println(properties.getProperty("missing", "默认值"));
}
}
8.3 读取配置文件
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
public class PropertiesLoadDemo {
public static void main(String[] args) {
Properties properties = new Properties();
try (FileReader reader = new FileReader("db.properties", StandardCharsets.UTF_8)) {
properties.load(reader);
System.out.println(properties.getProperty("url"));
System.out.println(properties.getProperty("username"));
System.out.println(properties.getProperty("password"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
8.4 保存配置文件
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
public class PropertiesStoreDemo {
public static void main(String[] args) {
Properties properties = new Properties();
properties.setProperty("theme", "dark");
properties.setProperty("language", "zh-CN");
try (FileWriter writer = new FileWriter("app.properties", StandardCharsets.UTF_8)) {
properties.store(writer, "application settings");
} catch (IOException e) {
e.printStackTrace();
}
}
}
8.5 配置读取工具
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
public class ConfigUtils {
/**
* 从指定路径加载 properties 配置文件。
*
* @param path 配置文件路径
* @return 加载完成的 Properties 对象
* @throws IOException 文件不存在、编码错误或读取失败时抛出
*/
public static Properties loadProperties(String path) throws IOException {
Properties properties = new Properties();
try (FileReader reader = new FileReader(path, StandardCharsets.UTF_8)) {
properties.load(reader);
}
return properties;
}
}
第九章 对象的序列化与反序列化(含现代企业级方案)
9.1 什么是序列化与反序列化
- 序列化 (Serialization):把 Java 对象转换为特定格式(如字节序列、JSON 字符串等)的过程,常用于将对象持久化保存到磁盘,或在网络中传输对象。
- 反序列化 (Deserialization):把特定格式的数据恢复为 Java 对象的过程。
9.2 现代项目主流方案:JSON 序列化(重点)
在现代企业级 Java 开发(尤其是 Spring Boot、微服务架构)中,JSON 序列化是最常用、最核心的方案。它的优点是跨语言、可读性好、体积小。 主流的 JSON 序列化框架包括 Jackson(Spring Boot 默认集成)、Fastjson2(阿里开源,性能极高)和 Gson(Google 出品)。
9.2.1 使用 Jackson 进行序列化与反序列化演示
引入 Jackson 依赖(Web 项目通常已自带):
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
代码演示:
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonSerializeDemo {
public static void main(String[] args) throws Exception {
User user = new User("admin", "123456");
// 核心类:ObjectMapper
ObjectMapper mapper = new ObjectMapper();
// 1. 序列化:Java 对象 -> JSON 字符串
String jsonString = mapper.writeValueAsString(user);
System.out.println("JSON序列化结果:" + jsonString);
// 输出: {"username":"admin","password":"..."}
// 2. 反序列化:JSON 字符串 -> Java 对象
User parsedUser = mapper.readValue(jsonString, User.class);
System.out.println("JSON反序列化对象:" + parsedUser.getUsername());
}
}
9.3 Java 原生序列化(简单了解)
Java 自身提供了一套基于 java.io.Serializable 和 ObjectOutputStream 的字节序列化机制。 ⚠️ 注意:原生序列化由于存在安全漏洞、无法跨语言调用、且序列化后体积庞大,在现代企业级开发中已经极少使用,仅做基础知识了解即可。
9.3.1 Serializable 接口与关键字
要想类的对象可以被原生序列化,该类必须实现 java.io.Serializable 标记接口。
import java.io.Serializable;
public class User implements Serializable {
// 推荐显式声明 serialVersionUID,用于版本控制,避免修改类后反序列化失败
private static final long serialVersionUID = 1L;
private String username;
// transient 修饰的成员变量不会被原生序列化
private transient String password;
public User() {}
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() { return username; }
}
9.3.2 原生流序列化代码演示
import java.io.*;
public class NativeSerializeDemo {
public static void main(String[] args) {
// 1. 序列化 (ObjectOutputStream)
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.dat"))) {
oos.writeObject(new User("admin", "123456"));
} catch (IOException e) {
e.printStackTrace();
}
// 2. 反序列化 (ObjectInputStream)
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.dat"))) {
User user = (User) ois.readObject();
// 由于 password 被 transient 修饰,反序列化后值为 null
System.out.println("反序列化完成");
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
总结
IO 流解决的是程序和文件之间的数据交换问题。学习时不要只记类名,更要理解每种流适合处理什么数据:
- 字节流适合处理所有类型的文件,尤其是图片、视频、压缩包等二进制文件。
- 字符流适合处理纯文本文件。
try-with-resources能自动关闭资源,是更推荐的异常处理方式。- 文件复制的本质是“边读边写”。
Properties让配置从代码中分离出来,适合保存简单键值对配置。- 序列化方面,现代开发中几乎都采用 JSON 序列化(Jackson/Fastjson 等),Java 原生
Serializable仅做基础了解即可。
掌握这些内容后,你就能独立完成文件读写、文件复制、文本处理和配置文件读取等常见任务。