最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Java IO 流:字节流:字符流与 try-with-resources 的正确打开方式
时间:2026-06-03 12:05:01 编辑:袖梨 来源:一聚教程网
文件读写作为后端开发的基础,Java IO体系的设计堪称经典。本文围绕字节流与字符流的区别展开,系统讲解它们的分类、操作方式及资源管理的最佳实践。
前言
学习IO流时,许多开发者容易混淆字节流和字符流。文件读写是后端开发的基础操作,而Java的IO体系设计虽然经典,但初次接触时理解两类流的差异至关重要。

1. IO 流分类
按方向划分:输入流(读取数据)与输出流(写入数据)
按单位划分:字节流(处理二进制)与字符流(处理文本)
按功能划分:节点流与处理流(如缓冲、转换等)
场景分类指南:
第一类,适用于图片、视频、可执行文件:选择字节流,原因在于二进制数据无需解释内容。
第二类,适用于文本、配置文件、日志:选择字符流,原因在于自动处理字符编码。
第三类,适用于大文件拷贝:选择缓冲流,原因在于减少系统调用次数。
2. 字节流:文件拷贝
// 逐字节拷贝方式,速度较慢
FileInputStream fis = new FileInputStream("src.jpg");
FileOutputStream fos = new FileOutputStream("dest.jpg");
int b;
while ((b = fis.read()) != -1) {
fos.write(b);
}
// 缓冲区拷贝方式,速度提升约100倍
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("dest.jpg"));
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
3. 字符流:文本处理
// 写入文本操作
PrintWriter pw = new PrintWriter(new FileWriter("log.txt", true));
pw.println("日志内容");
pw.printf("用户:%s,操作:%s%n", "张三", "登录");
// 读取文本操作,最常用的方式
BufferedReader br = new BufferedReader(new FileReader("log.txt"));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
4. try-with-resources:自动关闭的优雅
// 传统写法需在finally中手动关闭流,代码较为膨胀
// 现代写法在try结束时自动关闭资源
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
return br.readLine();
}
前提条件:资源必须实现AutoCloseable接口。所有IO流均已实现该接口。
5. 序列化:对象的持久化
// 序列化操作
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.dat"));
oos.writeObject(user);
// 反序列化操作
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.dat"));
User restored = (User) ois.readObject();
注意要点:transient标记的字段不参与序列化过程。serialVersionUID用于版本兼容性控制。
6. 实战:文件拷贝工具 + 配置读取
FileCopyTool.java:
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 文件拷贝工具:支持进度显示、速度统计
*/
public class FileCopyTool {
/**
* 拷贝文件
* @param srcPath 源文件路径
* @param destPath 目标文件路径
*/
public static void copy(String srcPath, String destPath) {
File srcFile = new File(srcPath);
// 前置检查
if (!srcFile.exists()) {
System.out.println("源文件不存在:" + srcPath);
return;
}
if (!srcFile.isFile()) {
System.out.println("源路径不是文件:" + srcPath);
return;
}
long fileSize = srcFile.length();
long copied = 0;
long startTime = System.currentTimeMillis();
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destPath))) {
byte[] buffer = new byte[8192]; // 8KB 缓冲区
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
copied += len;
// 每拷贝100KB打印一次进度
if (copied % (100 * 1024) < 8192) {
showProgress(copied, fileSize, startTime);
}
}
// 最终进度
showProgress(copied, fileSize, startTime);
System.out.println("n拷贝完成!");
} catch (IOException e) {
System.out.println("拷贝失败:" + e.getMessage());
}
}
/**
* 显示进度
*/
private static void showProgress(long copied, long total, long startTime) {
double percent = (double) copied / total * 100;
long elapsed = System.currentTimeMillis() - startTime;
double speed = copied / 1024.0 / (elapsed / 1000.0 + 0.001); // KB/s
System.out.printf("r进度: %.1f%% | 已拷贝: %.2f MB / %.2f MB | 速度: %.2f KB/s",
percent,
copied / 1024.0 / 1024.0,
total / 1024.0 / 1024.0,
speed);
}
public static void main(String[] args) {
copy("source.zip", "copy.zip");
}
}
ConfigReader.java(读取properties配置文件):
import java.io.*;
import java.util.Properties;
/**
* 配置文件读取工具
* properties文件格式:key=value
*/
public class ConfigReader {
private Properties props = new Properties();
/**
* 加载配置文件
*/
public void load(String filePath) {
try (InputStream is = new FileInputStream(filePath)) {
props.load(is);
System.out.println("配置加载成功");
} catch (IOException e) {
System.out.println("配置加载失败:" + e.getMessage());
}
}
/**
* 获取配置值
*/
public String getString(String key) {
return props.getProperty(key);
}
public String getString(String key, String defaultValue) {
return props.getProperty(key, defaultValue);
}
public int getInt(String key) {
return Integer.parseInt(props.getProperty(key));
}
public boolean getBoolean(String key) {
return Boolean.parseBoolean(props.getProperty(key));
}
/**
* 保存配置
*/
public void set(String key, String value) {
props.setProperty(key, value);
}
public void save(String filePath) {
try (OutputStream os = new FileOutputStream(filePath)) {
props.store(os, "Updated on " + new java.util.Date());
System.out.println("配置保存成功");
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ConfigReader config = new ConfigReader();
// 创建测试配置文件
try (PrintWriter pw = new PrintWriter("app.properties")) {
pw.println("# 应用配置");
pw.println("app.name=MyApp");
pw.println("app.version=1.0");
pw.println("db.host=localhost");
pw.println("db.port=3306");
pw.println("debug=true");
} catch (IOException e) {
e.printStackTrace();
}
// 读取配置
config.load("app.properties");
System.out.println("应用名:" + config.getString("app.name"));
System.out.println("数据库端口:" + config.getInt("db.port"));
System.out.println("调试模式:" + config.getBoolean("debug"));
// 修改并保存
config.set("app.version", "1.1");
config.save("app.properties");
}
}
最大感悟:IO操作务必关闭流,否则文件句柄泄漏可能导致系统无法分配新资源。try-with-resources是实现资源自动关闭的最佳实践。
相关文章
- Gemini下载怎么用?3个步骤搞定 06-04
- Gemini API密钥怎么申请?2026实测4种渠道对比 06-04
- 壹深圳app如何查看回放 06-04
- 我亲测了Gemini学生认证,全流程+踩坑记录 06-04
- Gemini 3.0使用教程 vs 4.0:3大区别与选择建议 06-04
- 干紫菜是紫色的炖汤后变成了绿色这是买到假紫菜了吗 小鸡宝宝考考你蚂蚁庄园3月9日答案 06-04