Java IO 分为两个主要家族:
-
传统
IO(java.io包): 基于流(Stream) ,数据一个字节(字符)一个字节(字符)地流动。适合较小数据量、阻塞式处理。 -
NIO(New IO,java.nio包): 基于缓冲区(Buffer) 和 通道(Channel) ,更高效、支持非阻塞 IO(可用于高性能服务器),适合大规模并发处理。
这里主要介绍传统 IO,对于文件操作来说也主要用到的是传统 IO,而网络通信会用到 NIO,并且现在主流的框架已经集成了 NIO,所以不需要我们去调用 NIO 的原生 API,所以这里不重点展开学,等到后面学框架的时候再学习时候使用即可!
Ⅰ. 文件 IO 的常用核心类(来自 java.io 包)
| 类名 | 类型 | 功能 |
|---|---|---|
| File | 表示文件或目录,不进行读写 | —— |
| FileInputStream / FileOutputStream | 字节流 | 用于读写文件(低级) |
| BufferedInputStream / BufferedOutputStream | 字节缓冲流 | 加快读写速度,包装在流外层 |
| FileReader / FileWriter | 字符流 | 用于读写文本文件,单字符读取(低效) |
| BufferedReader / BufferedWriter | 字符缓冲流 | 高效读写文本文件,提供按行读取等功能 |
| InputStreamReader / OutputStreamWriter | 转换流 | 字节流与字符流之间的桥梁 |
| ObjectInputStream / ObjectOutputStream | 对象流 | 支持对象的序列化与反序列化 |
💥注意事项:
-
其实因为
FileReader和FileWriter的局限性,比如不能指定编码、功能较弱、封装层少等,所以一般处理字符流的时候,用的是FileInputStream和FileOutputStream分别搭配上Scanner和PrintWriter即可! -
Java IO使用装饰者模式 (Decorator),允许你用更强功能 "包裹" 基础流,如下所示:
// 字节流包裹
BufferedInputStream in = new BufferedInputStream(new FileInputStream("file.txt"));
// 字符流包裹
BufferedReader br = new BufferedReader(new FileReader("test.txt")) Ⅱ. File 类
File 是 java.io 包中的一个核心类,用于表示文件或目录的抽象路径名 ,但它本身不进行文件内容读写 ,而是用于检查、创建、删除、重命名文件和目录等 "元数据" 操作。
| 方法 / 构造器 | 返回类型 | 说明 |
|---|---|---|
| File(String pathname) | 构造器 | 通过路径字符串构造文件或目录 |
| File(String parent, String child) | 构造器 | 使用父路径 + 子路径构造 |
| File(File parent, String child) | 构造器 | 使用父 File + 子路径构造 |
| exists() | boolean | 路径是否存在 |
| isFile() | boolean | 是否是普通文件 |
| isDirectory() | boolean | 是否是目录 |
| createNewFile() | boolean | 创建文件(已存在时不创建) |
| mkdir() | boolean | 创建目录(单层) |
| mkdirs() | boolean | 创建目录(多层) |
| delete() | boolean | 删除文件或空目录 |
| getName() | String | 获取文件/目录名 |
| getPath() | String | 返回构造 File 对象时传入的路径(原样返回,可能是相对路径 ) |
| getAbsolutePath() | String | 获取绝对路径,但不解析符号链接或目录符号 |
| getCanonicalPath() | String | 获取绝对路径,解析符号链接、 .和 ..等 ,等价于真实路径 |
| getParent() | String | 获取父路径(字符串) |
| length() | long | 获取文件大小(字节) |
| lastModified() | long | 最后修改时间戳(毫秒) |
| renameTo(File dest) | boolean | 重命名或移动文件 |
| list() | String[] | 返回目录下的文件名数组 |
| listFiles() | File[] | 返回目录下的 File 对象数组 |
| canRead() / canWrite() | boolean | 是否可读 / 可写 |
| setReadOnly() | boolean | 设置为只读 |
| isHidden() | boolean | 是否是隐藏文件 |
举个例子,假如当前路径是 /home/user,然后代码如下所示:
File file = new File("../test.txt");
System.out.println("getPath(): " + file.getPath());
System.out.println("getAbsolutePath(): " + file.getAbsolutePath());
System.out.println("getCanonicalPath(): " + file.getCanonicalPath());
// 结果:
getPath(): ../test.txt
getAbsolutePath(): /home/user/../test.txt
getCanonicalPath(): /home/test.txt题外话:try-with-resources⭐
try-with-resources 是 Java 7 引入的一种简洁语法结构,用于自动关闭实现了 AutoCloseable或 Closeable接口 的资源,主要是确保资源在使用完毕后被自动关闭,即使中间发生异常,也不会泄露资源 。
传统写法有点累赘,不优雅,如下所示:
try {
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}而 try-with-resources 写法如下所示:
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
}优势:
-
更简洁
-
自动关闭资源 (省去了
finally块)- 在
try中声明的资源,编译器会自动生成finally代码块 来调用它们的.close()方法。
- 在
-
避免资源泄漏
-
即使
try里面发生异常,Java保证所有资源都被依次、倒序地关闭 。 -
如果
close()方法本身也抛出异常,这些异常会被添加为抑制异常 (suppressed exceptions) ,而不会吞掉原来的主异常。
-
-
try中可以有多个语句,需要用;分割 即可!
Ⅲ. 字节流
字节流主要用于处理二进制数据 (如图片、音频、视频、文件),分别有输入字节流和输出字节流,但它们都是抽象类,需要通过特定功能的子类来创建实例,我们重点放在文件相关的字节流类上!
| 抽象类 | 子类示例 |
|---|---|
| InputStream | FileInputStream、BufferedInputStream、DataInputStream等等 |
| OutputStream | FileOutputStream、BufferedOutputStream、DataOutputStream等等 |
下面是两者各自的方法以及共同的方法:
| 方法签名 | 返回类型 | 功能说明 |
|---|---|---|
| 🔹 FileInputStream 专属方法 | ||
| int read() | int |
读取单个字节(返回 0–255,返回 -1 表示 EOF) |
| int read(byte[] b) | int |
读取多个字节,存入数组,返回实际读取的字节数 |
| int read(byte[] b, int off, int len) | int |
从偏移量 off 开始读取 len 个字节 |
| int available() | int |
返回可读取的字节数(非阻塞) |
| long skip(long n) | long |
跳过 n 个字节,返回实际跳过的字节数 |
| 🔸 FileOutputStream 专属方法 | ||
| void write(int b) | void |
写出一个字节(仅低 8 位有效) |
| void write(byte[] b) | void |
写出整个字节数组 |
| void write(byte[] b, int off, int len) | void |
从偏移量 off 写出 len 个字节 |
| void flush() | void |
强制刷新缓冲区,将数据立即写入文件 |
| 🔁 两者共有方法 | ||
| void close() | void |
关闭流并释放系统资源 |
| FileDescriptor getFD() | FileDescriptor |
获取底层文件描述符,用于底层文件操作(少用) |
一、输入样例
public static void main(String[] args) {
// try-with-resources风格:
try(InputStream in = new FileInputStream("./main.txt")) {
byte[] buf = new byte[1024];
while(true) {
int n = in.read(buf); // 磁盘数据读取到buf中
if(n == -1) {
System.*out*.println("读取完毕");
break;
}
// 处理数据,比如输出数据
for(int i = 0; i < n; ++i) {
System.*out*.printf("0x%x\n", buf[i]); *// 十六进制输出*
* *}
}
} catch (IOException e) {
e.printStackTrace();
}
}二、输出样例
public static void main(String[] args) {
try(OutputStream out = new FileOutputStream("./main1.txt")) {
byte[] buf = {
97, 98, 99, 100
};
out.write(buf); *// 将buf数据写入到文件中去*
* *} catch (IOException e) {
e.printStackTrace();
}
}Ⅳ. 字符流
这里主要采用的还是文件字节流加上 Scanner、PrintWriter 来处理字符,这两者分别有以下优势:
-
Scanner可以轻松地读取int,double,String, 一行一行地处理文本(nextLine()、nextInt()等),还可以设置编码格式!又因为它接受InputStream作为输入源,所以能直接配合FileInputStream使用! -
PrintWriter支持print(),println(),printf()等方法,并且支持write(String),即可以传入String参数,这种支持是非常爽的!
一、输入样例
public static void main(String[] args) {
try(InputStream in = new FileInputStream("./main.txt");
Scanner sc = new Scanner(in, "UTF-8")) {
// 循环读入每一行,然后输出
while(sc.hasNext()) {
String line = sc.nextLine();
System.*out*.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}二、输出样例
public static void main(String[] args) {
try(OutputStream out = new FileOutputStream("./main2.txt", "UTF-8");
PrintWriter pw = new PrintWriter(out)) {
*// 将文本输出到文件中,可以直接传String类型*
* *pw.write("啊啊~利刃啊liren\n利刃阿斯顿");
} catch (IOException e) {
e.printStackTrace();
}
}