/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}
在 Java 开发中,文件操作和 IO 流是非常基础且重要的内容。无论是读取配置文件、保存日志,还是实现文件上传下载,都离不开 IO 操作。
本文从实战角度出发,系统总结:
- File 类的常见用法
- 字节流:InputStream / OutputStream 的用法
- 字符流:Reader / Writer 的用法
- 字节流与字符流的区别与选择
- 资源管理与综合示例
一、File 类:文件与目录的抽象
java.io.File 是 Java 对文件系统中文件(包括目录)的抽象描述。
注意:创建一个 File 对象,并不代表该文件在磁盘上真实存在。它只是路径的抽象表示。
1. 构造方法
| File(String pathname) | 根据路径字符串创建 File 对象,可以是绝对路径或相对路径 |
| File(File parent, String child) | 根据父目录 File 对象和子路径创建 |
| File(String parent, String child) | 根据父目录路径字符串和子路径创建 |
示例:
File file1 = new File("hello.txt");
File file2 = new File("D:\\\\test", "hello.txt");
File parent = new File("D:\\\\test");
File file3 = new File(parent, "hello.txt");
2. 路径相关方法
File file = new File("..\\\\hello-world.txt");
System.out.println(file.getParent()); // ..\\
System.out.println(file.getName()); // hello-world.txt
System.out.println(file.getPath()); // ..\\hello-world.txt
System.out.println(file.getAbsolutePath()); // 绝对路径
System.out.println(file.getCanonicalPath()); // 修饰后的绝对路径,会解析 . 和 ..
3. 文件与目录操作
| exists() | 判断文件或目录是否真实存在 |
| isDirectory() | 判断是否为目录 |
| isFile() | 判断是否为普通文件 |
| createNewFile() | 创建普通文件,已存在返回 false |
| delete() | 删除文件或空目录 |
| deleteOnExit() | 程序退出时删除文件 |
| mkdir() | 创建单级目录,父目录不存在会失败 |
| mkdirs() | 创建多级目录,父目录不存在会一并创建 |
| renameTo(File dest) | 重命名或移动文件 |
| listFiles() | 返回目录下的子文件和子目录 |
示例:创建、删除文件
File file = new File("test.txt");
System.out.println(file.exists()); // false
System.out.println(file.createNewFile()); // true
System.out.println(file.exists()); // true
System.out.println(file.delete()); // true
System.out.println(file.exists()); // false
示例:创建目录
File dir = new File("some-parent\\\\some-dir");
// mkdir() 如果父目录不存在会失败
System.out.println(dir.mkdir()); // false
// mkdirs() 可以创建多级目录
System.out.println(dir.mkdirs()); // true
二、IO 流的分类
在 Java 中,IO 流按照处理数据的单位分为两类:
- 输入流:InputStream
- 输出流:OutputStream
- 输入流:Reader
- 输出流:Writer
注意:字符流本质上还是基于字节流实现的,只是加了一层编码/解码的包装。
三、字节流:InputStream / OutputStream
1. InputStream(字节输入流)
InputStream 是字节输入流的抽象类,最常用的子类是 FileInputStream。
| int read() | 读取一个字节,返回 -1 表示读取完毕 |
| int read(byte[] b) | 最多读取 b.length 个字节,返回实际读取数量,-1 表示读完 |
| void close() | 关闭流 |
推荐方式:使用缓冲区读取
try (InputStream is = new FileInputStream("hello.txt")) {
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) != –1) {
// buf 的 [0, len) 是本次读取到的数据
System.out.write(buf, 0, len);
}
}
2. OutputStream(字节输出流)
OutputStream 是字节输出流的抽象类,最常用的子类是 FileOutputStream。
| void write(int b) | 写入一个字节 |
| void write(byte[] b) | 写入整个字节数组 |
| void write(byte[] b, int off, int len) | 写入字节数组的指定范围 |
| void flush() | 刷新缓冲区,强制将数据写入目标 |
| void close() | 关闭流 |
写入示例:
try (OutputStream os = new FileOutputStream("output.txt")) {
String s = "你好中国";
byte[] b = s.getBytes("UTF-8");
os.write(b);
os.flush(); // 不要忘记 flush
}
四、字符流:Reader / Writer(重点补充)
字符流专门用于处理文本数据,它能自动处理字符集编码,避免字节流读取中文时的乱码问题。
1. Reader(字符输入流)
Reader 是字符输入流的抽象类,常用子类有 FileReader、BufferedReader。
| int read() | 读取一个字符,返回 -1 表示读完 |
| int read(char[] cbuf) | 最多读取 cbuf.length 个字符,返回实际读取数 |
| void close() | 关闭流 |
基础用法(FileReader):
try (Reader reader = new FileReader("hello.txt")) {
int ch;
while ((ch = reader.read()) != –1) {
System.out.print((char) ch);
}
}
高效用法(BufferedReader,支持按行读取):
BufferedReader 提供了 readLine() 方法,可以一次读取一行文本,非常适合处理文本文件。
try (BufferedReader reader = new BufferedReader(new FileReader("hello.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
注意:FileReader 使用系统默认字符集,如果文件编码与系统默认不一致,可能会出现乱码。此时应使用 InputStreamReader 手动指定编码。
2. Writer(字符输出流)
Writer 是字符输出流的抽象类,常用子类有 FileWriter、BufferedWriter。
| void write(int c) | 写入一个字符 |
| void write(char[] cbuf) | 写入字符数组 |
| void write(String str) | 写入字符串 |
| void flush() | 刷新缓冲区 |
| void close() | 关闭流 |
基础用法(FileWriter):
try (Writer writer = new FileWriter("output.txt")) {
writer.write("你好,世界\\n");
writer.write("Hello, World\\n");
writer.flush();
}
高效用法(BufferedWriter,支持换行):
try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
writer.write("我是第一行");
writer.newLine(); // 换行
writer.write("我是第二行");
writer.flush();
}
3. 转换流:InputStreamReader / OutputStreamWriter
转换流是字节流和字符流之间的桥梁,可以指定字符集编码,解决乱码问题。
读取时指定编码:
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream("hello.txt"), "UTF-8"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
写入时指定编码:
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream("output.txt"), "UTF-8"))) {
writer.write("你好,世界");
writer.flush();
}
五、字节流 vs 字符流:如何选择?
| 处理单位 | 字节(8位) | 字符(16位) |
| 适用场景 | 图片、音频、视频、压缩包等二进制文件 | 文本文件(txt、java、html 等) |
| 编码处理 | 需要手动处理编码 | 自动处理编码(或通过转换流指定) |
| 核心抽象类 | InputStream / OutputStream | Reader / Writer |
| 常见子类 | FileInputStream / FileOutputStream | FileReader / FileWriter、BufferedReader / BufferedWriter |
| 性能优化 | 使用 byte[] 缓冲区 | 使用 char[] 缓冲区或 BufferedReader |
选择原则:
- 如果是二进制文件(图片、音视频等),必须使用字节流。
- 如果是文本文件,优先使用字符流(尤其是 BufferedReader/BufferedWriter),避免乱码并提高效率。
- 如果文本文件编码特殊(如 UTF-8 而系统默认 GBK),必须使用转换流指定编码。
六、资源管理:try-with-resources
IO 资源非常宝贵,使用完毕后必须关闭。Java 7 提供了 try-with-resources 语法,只要资源实现了 AutoCloseable 接口,就能自动关闭。
标准写法:
try (InputStream is = new FileInputStream("source.txt");
OutputStream os = new FileOutputStream("dest.txt")) {
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) != –1) {
os.write(buf, 0, len);
}
os.flush();
} catch (IOException e) {
e.printStackTrace();
}
七、综合示例:文件复制(字节流 vs 字符流)
1. 使用字节流复制任意文件(推荐)
public class FileCopyDemo {
public static void main(String[] args) {
File source = new File("source.jpg");
File dest = new File("dest.jpg");
try (InputStream is = new FileInputStream(source);
OutputStream os = new FileOutputStream(dest)) {
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) != –1) {
os.write(buf, 0, len);
}
os.flush();
System.out.println("复制完成");
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. 使用字符流复制文本文件
public class TextCopyDemo {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream("source.txt"), "UTF-8"));
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream("dest.txt"), "UTF-8"))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
writer.flush();
System.out.println("文本复制完成");
} catch (IOException e) {
e.printStackTrace();
}
}
}
八、总结
| 文件抽象 | File | 描述文件或目录元信息 | 对象存在不代表文件真实存在 |
| 字节输入 | InputStream | 读取字节数据 | read() 返回 -1 表示读完 |
| 字节输出 | OutputStream | 写入字节数据 | write() 写入,flush() 刷新 |
| 字符输入 | Reader | 读取字符数据 | BufferedReader.readLine() 按行读取 |
| 字符输出 | Writer | 写入字符数据 | BufferedWriter.newLine() 换行 |
| 转换流 | InputStreamReader / OutputStreamWriter | 字节流转字符流 | 可指定字符集,解决乱码 |
| 资源管理 | try-with-resources | 自动关闭流 | 避免资源泄漏 |
掌握 File、字节流、字符流的基本用法,是 Java 文件编程的基础。
实际开发中:
- 操作文件元信息使用 File;
- 读写二进制文件使用 InputStream / OutputStream;
- 读写文本文件使用 Reader / Writer,优先选择 BufferedReader / BufferedWriter;
- 编码不一致时使用转换流指定字符集;
- 始终使用 try-with-resources 管理资源;
- 不要忘记 flush() 和 close()。



