Java 文件 IO 从入门到精通:字节流与字符流全解析
Java 文件 IO 全面解析:从路径到字节流与字符流
文件 IO 是 Java 开发中最基础也最常用的能力之一。无论是读写配置文件、处理文件上传下载,还是解析日志,都离不开对文件的操控。本文将从文件路径、文件类型出发,系统讲解 File 类与字节流/字符流的核心用法,帮你写出健壮、规范的 IO 代码。
目录
-
- 什么是文件
-
- 路径
-
- 文件的类型(文本 / 二进制)
-
- File 类
-
- 读写(流)
- 5.1 字节流 / 5.2 字符流
- 5.3 FileInputStream / 5.4 FileOutputStream
- 5.5 Reader / 5.6 Writer
1. 什么是文件
这里所说的"文件",指的是硬盘上存储的文件。我们只需调用操作系统提供的 API 来操作文件即可。
2. 路径
在 Windows 上,路径从盘符开始;在 Linux 上,路径从根目录(/)开始。
绝对路径

下图展示的是相对路径。
. 表示当前目录,是一个相对路径。
File file =new File("./test.txt");
.. 表示上一级目录。
/ 为正斜杠,\ 为反斜杠。
3. 文件的类型
文本文件
存储的是字符。
二进制文件
没有限制,可以存储任意数据。
4. File 类

以上图为例。
File file1 =new File("D:/MSIc4909.txt");
System.out.println(file1.getName());
System.out.println(file1.getParent());
System.out.println(file1.getPath());
System.out.println(file1.getAbsoluteFile());
System.out.println(file1.getCanonicalPath());

执行结果如上,最后一项为空(null)是因为当前进程的工作目录下找不到该相对路径所指向的文件。
File file1 =new File("c:/MSIc4909.txt");
System.out.println(file1.exists());
System.out.println(file1.isFile());
System.out.println(file1.isDirectory());
该文件是我虚构的,所以返回值都为 false。

delete() 有两种:第一种是直接删除;第二种(deleteOnExit)是先"登记"删除,待进程结束后再真正删除。
File file1 =new File("E:/MSIc4909.txt");
System.out.println(file1.exists());
file1.createNewFile();
System.out.println(file1.exists());
file1.delete();
System.out.println(file1.exists());


此外,renameTo 可用于重命名和移动文件。
file.renameTo(FILE);

5. 读写(流)
文件的核心操作其实就是读和写,为此 Java 提供了流(Stream)对象。
5.1 字节流
以字节为基本单位进行读写,常用来操作二进制文件。
输入:
InputStream
输出:
OutputStream
5.2 字符流
以字符为基本单位进行读写,常用来操作文本文件。
输入:
Reader
输出:
Writer
上述四个类都是抽象类,因此我们重点学习它们各自的子类,一共八个类。
5.3 FileInputStream
1. 打开文件
InputStream inputStream = new FileInputStream("./s.txt");
括号中的参数是文件目录,既可以是相对路径,也可以是绝对路径。
2. read

read 有三个重载方法:
- 一次读一个字节,返回
-1表示已读到文件末尾(由于计算机按字节寻址,这是最基础的读取方式)。 - 一次读若干字节,参数是一个数组(输出型参数)——方法会把读取到的内容填充到该数组中。之所以这样设计,是因为方法通常只能返回一个值;它会尽可能地把数组
b填满。 - 与第二种类似,但多了两个参数:
off表示从b[off]开始写入,len表示最多写入len个字节;若剩余数据不足,则读到的就少。
3. 关闭文件
inputStream.close();
inputStream.close();
System.out.println(inputStream.read());
之所以要强调关闭,是因为:读写文件时会在操作系统的"文件描述符表"中占用一项;如果打开的文件过多(描述符耗尽),就会引发问题。
如果你不及时释放,就会出现文件资源泄露(这是一类非常严重的 Bug)。

有时我们需要处理异常,下面介绍几种常见的写法。
写法一:try-catch 环绕
try{
InputStream inputStream = new FileInputStream("./s.txt");
int n=0;
while(n!=-1){
byte[] bytes =new byte[1024];
n=inputStream.read(bytes);
for (int i = 0; i < n; i++) {
System.out.println(bytes[i]);
}
}
}catch (IOException e){
e.printStackTrace();
}finally {
inputStream.close();
}
这种写法肯定是错的,因为 try 中声明的变量作用域无法延伸到 finally。
写法二:改进版(变量提到 try 外)
但这样变量有可能直接初始化失败而为 null。
InputStream inputStream =null;
try{
inputStream = new FileInputStream("./s.txt");
int n=0;
while(n!=-1){
byte[] bytes =new byte[1024];
n=inputStream.read(bytes);
for (int i = 0; i < n; i++) {
System.out.println(bytes[i]);
}
}
}catch (IOException e){
e.printStackTrace();
}finally {
inputStream.close();
}
写法三:最终正确版(增加判空)
为此我们还需要加一个判空判断,最终形式如下:
InputStream inputStream =null;
try{
inputStream = new FileInputStream("./s.txt");
int n=0;
while(n!=-1){
byte[] bytes =new byte[1024];
n=inputStream.read(bytes);
for (int i = 0; i < n; i++) {
System.out.println(bytes[i]);
}
}
}catch (IOException e){
e.printStackTrace();
}finally {
if(inputStream!=null) {
inputStream.close();
}
}
上面的写法显然比较繁琐,有没有更简洁的方案?有的。
使用 try-with-resources,会在 try 结束后自动调用 close,并妥善处理 close 自身可能抛出的异常:
try(InputStream inputStream = new FileInputStream("./s.txt")){
int n=0;
while(n!=-1){
byte[] bytes =new byte[1024];
n=inputStream.read(bytes);
for (int i = 0; i < n; i++) {
System.out.println(bytes[i]);
}
}
}catch (IOException e){
e.printStackTrace();
}
注意:如果你需要把流对象返回出去,或在关闭前后做额外处理(例如加锁控制),try-with-resources 就不太合适了,因为它会自动关闭流。
当然,如果调用完文件后进程马上退出,那不显式调用 close 也没问题。
5.4 FileOutputStream
1. 创建与打开文件
OutputStream outputStream =null;
outputStream = new FileOutputStream("./s.txt");
注意:以这种方式打开文件会清空原有内容。如果需要追加而非覆盖,可以传入第二个参数来解决。
2. 写入

写入方法与读取类似:
- 一次写一个字节(类似 ASCII 码值)。
- 写入整个字节数组。
- 写入字节数组的一部分。
outputStream = new FileOutputStream("./s.txt");
outputStream.write(105);
清空内容后,写入了一个 i(ASCII 105)。

outputStream = new FileOutputStream("./s.txt",true);
outputStream.write(105);
这次没有清空原有内容(追加模式)。

3. 关闭文件
outputStream.close();
5.5 Reader
1. 打开文件
Reader reader = new FileReader("./s.txt")
2. read

- 一次读一个字符。
- 尽可能把整个数组读满。
CharBuffer是对char数组的封装。- 读入后以
off为起始下标、写入长度为len的内容。
char[] bytes =new char[1024];
reader.read(bytes);
for (int i = 0; i < n; i++) {
System.out.println(bytes[i]);
}
5.6 Writer
1. 打开
Writer writer= new FileWriter("./s.txt")
2. 写入

写入方法比较直观,此处不再赘述。
3. 关闭
writer.close();
小结
本文系统梳理了 Java 文件 IO 的核心知识:文件路径(绝对路径与相对路径)以及文本文件、二进制文件的区别;File 类对文件路径与元信息的查询、创建、删除、重命名等操作;以及以 InputStream/OutputStream 为代表的字节流、以 Reader/Writer 为代表的字符流。其中尤其要牢记及时释放资源(手动 close 或使用 try-with-resources),避免文件描述符耗尽导致资源泄露。掌握这些基础,你就能从容应对绝大多数文件读写场景。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)