IO流
3053字约10分钟
2025-11-21
IO流
Java中的数据写入到本地磁盘中,叫输出流
Java程序读取本地磁盘,叫输入流
I/O(读/写)
IO 流
├─ 字节流
│ ├─ 节点流(直接操作数据源)
│ │ ├─ FileInputStream / FileOutputStream(文件)
│ │ ├─ Socket 相关流(网络)
│ │ └─ 其他(内存、管道等)
│ └─ 处理流(包装节点流/其他处理流)
│ ├─ 缓冲流:BufferedInputStream / BufferedOutputStream
│ ├─ 数据流:DataInputStream / DataOutputStream
│ ├─ 对象流:ObjectInputStream / ObjectOutputStream
│ └─ 打印流:PrintStream
│
└─ 字符流
├─ 节点流(直接操作数据源)
│ ├─ FileReader / FileWriter(文件)
│ ├─ CharArrayReader / CharArrayWriter(内存)
│ └─ 其他(字符串、管道等)
└─ 处理流(包装节点流/其他处理流)
├─ 缓冲流:BufferedReader / BufferedWriter
├─ 转换流:InputStreamReader / OutputStreamWriter(连接字节流与字符流)
└─ 打印流:PrintWriter
管理文件和文件夹
创建文件
// 在D盘中的book文件夹中创建文件(1.txt)
File file = new File("D:\\book\\1.txt");
try {
// 执行这一行代码时,文件才正常创建
file.createNewFile();
System.out.println("创建成功");
} catch (IOException e) {
e.printStackTrace();
}// 父文件夹路径 + 子文件路径
// 相当于拼接了
File file1 = new File("D:\\book");
String fileName = "2.txt";
File file2 = new File(file1, fileName);
try {
file2.createNewFile();
System.out.println("创建成功");
} catch (IOException e) {
e.printStackTrace();
}String fileName1 = "D:\\book";
String fileName2 = "3.txt";
File file3 = new File(fileName1, fileName2);
try {
file3.createNewFile();
System.out.println("创建成功");
} catch (IOException e) {
e.printStackTrace();
}删除文件
// 删除文件
File file = new File("D:\\book\\2.txt");
file.delete();
System.out.println("删除成功");删除目录
// 删除目录(目录中没有文件的前提下)
File file1 = new File("D:\\book\\ceshi");
file1.delete();
System.out.println("删除成功");创建目录
// 创建目录
File file3 = new File("D:\\ceshi1");
file3.mkdir();
System.out.println("文件夹创建成功");创建多级目录
// 创建多级目录,mkdirs也可以创建单个文件夹
File file4 = new File("D:\\ceshi1\\a\\b");
file4.mkdirs();
System.out.println("文件夹创建成功");获取文件/文件夹信息
File file = new File("D:\\book\\1.txt");
System.out.println("文件名字:" + file.getName()); // 1.txt
System.out.println("文件绝对路径:" + file.getAbsolutePath()); // D:\book\1.txt
System.out.println("文件父级目录:" + file.getParent()); // D:\book
System.out.println("文件大小(字节):" + file.length()); // 5
System.out.println("文件/文件夹是否存在:" + file.exists()); // true
System.out.println("文件是不是一个文件:" + file.isFile()); // true
System.out.println("文件是不是一个文件夹:" + file.isDirectory()); // false字节流
FileInputStream(读)
FileInputStream 文件输入流
read() 读取文件字符,当读取到末尾时,返回一个 -1
read和readLine区别:read是读取字符,readLine是读取一行字符串
String fileName = "D:\\book\\hi.txt";
int num = 0;
char[] c = new char[11];
FileInputStream f = null;
try {
f = new FileInputStream(fileName);
// 读取方法一
while (num != -1) {
// 将文件中的字符赋值给num(ASCII码)
num = f.read();
// 在将num转换成字符输出
System.out.print((char)num); // hello,world
}
// while (f.read() != -1) {
//// 这种写法之所以读取的结果不完整
//// 是因为read()被调用了两次,所以最后的结果不正确
// System.out.print((char)f.read());
// }
// 读取方法二
while ((num = f.read(c)) != -1) {
// 将文件中的字符赋值给num
// 在将num转换成字符输出
// new String(字符数组, 从下标0开始写入, 写入长度)
System.out.print(new String(c, 0, num)); // hello,world
System.out.println(num); // 数组长度:11
}
} catch {
...
} finally {
try {
// 关闭文件流,释放内存
f.close();
} catch (IOException e) {
e.printStackTrace();
}
}FileOutputStream(写)
FileOutputStream 文件输出流
使用 FileOutputStream 写入文件时,必须要使用close()方法结束,否则写入文件失败
String fileName = "D:\\book\\hello.txt";
FileOutputStream f = null;
try {
// 会覆盖掉文件中的内容
f = new FileOutputStream(fileName);
// 向文件中写入H值
f.write('H');
// 写入字符串
String text = "Hello";
// 将字符串转换成字符数组
f.write(text.getBytes());
// f.write(字符数组, 从下标0开始写入, 写入长度)
f.write(text.getBytes(), 0, text.length());
// 追加写入,不会覆盖原本类容
f = new FileOutputStream(fileName, true);
f.write(text.getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
f.close();
} catch (IOException e) {
e.printStackTrace();
}
}拷贝文件(二进制文件)
// 老的文件路径
String oldFilename = "D:\\img.png";
// 新的文件路径
String newFilename = "D:\\img1.png";
FileInputStream fis = null;
FileOutputStream fos = null;
byte[] bt = new byte[1024];
int num = 0;
try {
fis = new FileInputStream(oldFilename);
fos = new FileOutputStream(newFilename);
// 边读边写
// fis.read(bt) 将读取的文件写入到bt字节数组
// fos.write 写入
// num 字节大小长度
while ((num = fis.read(bt)) != -1) {
fos.write(bt, 0, num);
}
System.out.println("拷贝成功");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fis.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}字符流
FileReader(读)
FileReader 读取文件的方法和 FileInputStream 一样
- 优先用
FileInputStream:读取二进制文件(图片、视频、压缩包等),或需要直接操作字节流的场景。 - 优先用
FileReader:读取文本文件(如.txt、.csv),且希望直接处理字符(无需手动编码转换)的场景。
小问题:因为 FileReader 默认使用的是UTF-8来读取文件的,当读取的文件编码是别的格式时,就会出现乱码的问题(乱码问题可以使用转换流来解决)
FileWriter(写)
FileWriter 写入文件的方法和 FileOutputStream 一样
- 优先用
FileOutputStream:写入二进制文件(图片、视频、可执行文件等),或需要精确控制字节数据的场景。 - 优先用
FileWriter:写入文本内容(如日志、配置文件),尤其是需要直接操作字符串或字符的场景(无需手动处理编码转换)
注意点:
- 使用FileWriter写入文件时,必须要使用
close()方法结束,否则写入文件失败 - FileOutputStream 则没有这个问题
字节流与字符流区别
处理流是一种 “包装流”,它不能独立存在,必须依附于节点流或其他处理流,主要用于对数据进行加工、转换或增强功能(如缓冲、编码转换、对象序列化等)。
| 特性 | FileInputStream / FileOutputStream | FileReader / FileWriter | BufferedReader / BufferedWriter |
|---|---|---|---|
| 数据类型 | 字节流(byte) | 字符流(char) | 字符流(char,处理流) |
| 读取单位 | 字节(8 位) | 字符(16 位,依赖编码) | 字符 / 行(基于缓冲) |
| 底层操作 | 直接操作文件字节 | 基于FileInputStream,自动转换字节为字符(默认编码) | 包装其他字符流,提供缓冲和按行读取 |
| 编码处理 | 不处理编码,直接读字节 | 使用平台默认编码(可能导致乱码) | 依赖被包装的流的编码处理 |
| 主要用途 | 读取 / 写入二进制文件(图片、音频等) | 读取 / 写入文本文件(默认编码) | 高效读取 / 写入文本(缓冲 + 按行读取) |
缓冲流
BufferedReader
- 读取一行的文本
- 到达流的末尾时,返回
null
BufferedReader b = new BufferedReader(new FileReader("D:\\book\\hi.txt"));
String line;
// readLine() 是 BufferedReader 类提供的一个常用方法,用于读取一行文本
while ((line = b.readLine()) != null) {
System.out.println(line);
}
// 关闭流
b.close();BufferedWriter
BufferedWriter b = new BufferedWriter(new FileWriter("D:\\book\\hi.txt"));
// 插入一行字符串
b.write("hello,world");
// 换行
b.newLine();
b.write("hello,world");
b.newLine();
b.write("hello,world");
// 关闭流
b.close();Buffered拷贝(字节文件)
该方法不能拷贝二进制文件(如视频,音乐,图片等)
String line;
BufferedReader b1 = null;
BufferedWriter b2 = null;
try {
b1 = new BufferedReader(new FileReader("D:\\book\\hi.txt"));
b2 = new BufferedWriter(new FileWriter("D:\\book\\h2.txt"));
while ((line = b1.readLine()) != null) {
b2.write(line);
// 换行
b2.newLine();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
b1.close();
b2.close();
} catch (IOException e) {
e.printStackTrace();
}
}该方法可以读取任何文件
BufferedOutputStream bos = null;
BufferedInputStream bis = null;
byte[] bytes = new byte[1024];
int num;
try {
// 读取
bis = new BufferedInputStream(new FileInputStream("D:\\book\\img.png"));
// 写入
bos = new BufferedOutputStream(new FileOutputStream("D:\\book\\img11.png"));
while ((num = bis.read(bytes)) != -1) {
bos.write(bytes, 0, num);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
bis.close();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}BufferedInputStream和BufferedOutputStream
字节流的缓冲流是 BufferedInputStream和BufferedOutputStream,它们是对底层字节流的包装,通过引入缓冲区(内存区域)来提高 IO 操作效率。
public static void main(String[] args) {
// 源文件和目标文件路径
String source = "source.txt";
String dest = "dest.txt";
// 自动释放资源
try (// 1. 创建底层字节流(节点流)
FileInputStream fis = new FileInputStream(source);
FileOutputStream fos = new FileOutputStream(dest);
// 2. 用缓冲流包装底层流(处理流)
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos)) {
byte[] buffer = new byte[1024]; // 缓冲区(1KB)
int len; // 记录每次读取的字节数
// 3. 循环读写数据
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len); // 只写入有效字节
}
} catch (IOException e) {
e.printStackTrace();
}
}序列化与反序列化
序列化:保存数据时,保存数据的值和数据类型
反序列化:恢复数据时,恢复数据的值和数据类型
要想对象实现序列化机制,需要该对象实现两个接口(二选一)
Serializable // 没有实现方法(推荐使用这个方法)
Externalizable // 有实现方法序列化
public class ObjFile {
public static void main(String[] args) {
String filePath = "D:\\book\\log.dat";
ObjectOutputStream objfile = null;
try {
objfile = new ObjectOutputStream(new FileOutputStream(filePath));
// int
objfile.write(100);
// boolean
objfile.writeBoolean(true);
// char
objfile.writeChar('a');
// double
objfile.writeDouble(1.2);
// String
objfile.writeUTF("hello");
// 对象
objfile.writeObject(new Dog());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
objfile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
class Dog implements Serializable {}反序列化
ObjectInputStream objint = null;
try {
// 反序列化的顺序要和序列化的顺序一致
objint = new ObjectInputStream(new FileInputStream("D:\\book\\log.dat"));
System.out.println(objint.readInt());
System.out.println(objint.readBoolean());
System.out.println(objint.readChar());
System.out.println(objint.readDouble());
System.out.println(objint.readUTF());
System.out.println(objint.readObject());
// 调用序列化里面的对象
Dog dog = (Dog)(objint.readObject());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
objint.close();
} catch (IOException e) {
e.printStackTrace();
}
}注意点
序列化和反序列化的顺序要一致
序列化和反序列化对象要实现 Serializable
序列化类添加SerialVersionUID属性,为了提高版本的兼容性
// 显式定义序列化版本号 private static final long serialVersionUID = 1L;如果不定义
SerialVersionUID,Java 会根据类的结构(字段、方法、继承关系等)自动计算一个版本号。此时,即使对类做非常微小的修改(比如新增一个字段、调整方法的顺序,甚至只是加了一行注释),系统计算出的版本号都可能发生变化。序列化会将类中的属性进行序列化,但static和transient修饰的成员除外
在序列化中创建了一个对象,则该对象需要实现
Serializable方法public class Dog implements Serializable { A a = new A(); } class A implements Serializable {}序列化具备继承性,但某个类继承序列化类,该类即使没有实现
Serializable也可以被序列化
转换流
InputStreamReader
核心作用
- 字节转字符:将底层字节流(如从网络或文件读取的字节)按照指定编码转换为字符。
- 处理编码:支持指定字符集(如
UTF-8、GBK),避免因默认编码不一致导致的乱码。
public static void main(String[] args) throws Exception {
String filepath = "D:\\book\\hi.txt";
// 以gbk编码的方式读取
InputStreamReader gbk = new InputStreamReader(new FileInputStream(filepath), "gbk");
BufferedReader b1 = new BufferedReader(gbk);
System.out.println(b1.readLine());
b1.close();
}| 代码 | 含义 |
|---|---|
new InputStreamReader(inputStream) | 将任意字节流(inputStream)转换为字符流,使用系统默认编码 |
new InputStreamReader(new FileInputStream("file.txt"), "gbk") | 将文件字节流(FileInputStream)转换为字符流,强制使用GBK 编码 |
OutputStreamWriter
核心作用
- 字符转字节:接收字符数据,按照指定编码规则转换为字节流。
- 处理编码:支持指定字符集(如
UTF-8、GBK),确保字符在转换为字节时不会出现乱码。
public static void main(String[] args) throws Exception {
String filepath = "D:\\book\\hello.txt";
OutputStreamWriter out1 = new OutputStreamWriter(
new FileOutputStream(filepath), "gbk");
// 以gbk编码的方式写入
out1.write("你好,Java");
out1.close();
}打印流
PrintStream
public static void main(String[] args) throws Exception {
PrintStream out1 = System.out;
out1.println("nihao");
out1.write("java".getBytes());
// 指定打印的位置
System.setOut(new PrintStream("D:\\book\\log.dat"));
// 输出的内容将不在终端中显示,而是打印到上面指定的文件中
for (int i = 0; i < 10; i++) {
System.out.println("log测试1");
}
out1.close();
}PrintWriter
public static void main(String[] args) throws Exception {
// 指定打印的位置
PrintWriter p = new PrintWriter(
new FileWriter("D:\\book\\log.dat"));
// 输出的内容将不在终端中显示,而是打印到上面指定的文件中
p.print("nihao");
p.close();
}Properties(k-v)
要求:读取的文件必须是 k-v 格式的,也就是键=值,键值对没有空格,且不需要用引号引起来,默认为String类型
读取的文件
user=admin
pwd=123读取代码
public static void main(String[] args) throws IOException {
Properties p1 = new Properties();
p1.load(new FileReader("D:\\book\\pwd.txt"));
p1.list(System.out);
// 读取user等于的值
String user = p1.getProperty("user");
System.out.println(user); // admin
}写入代码
public static void main(String[] args) throws Exception {
Properties p1 = new Properties();
p1.setProperty("ip","172.0.0.1");
// 值1:修改文件的路径 | 值2:注释
p1.store(new FileOutputStream("D:\\book\\pwd.txt"), null);
// 追加模式
p1.store(new FileOutputStream("D:\\book\\pwd.txt", true), null);
}