JavaSE:I/O 流
前言
- 字节流和字符流的操作相同,只是操作的数据类型不同。
- 节点流和过滤流的关系,是 的体现。
- 转换流,是 的体现。
1、流
流:内存与存储设备之间传输数据的通道。
分类
- 按方向(以内存为基准)
- 输入流:将存储设备中的内容读到内存中。
- 输出流:将内存中的内容写到存储设备中。
- 按单位
- 字节流:以字节为单位,可以读写所有数据。
- 字符流:以字符为单位,只能读写文本数据。
- 按功能
- 节点流:具有实际传输数据的读写功能。
- 过滤流:在节点流的基础之上增强功能()
2、字节流
2.1、抽象类
2.1.1、InputStream
字节输入流
- read():从输入流读取数据的下一个字节,返回下一个字节
- read(byte[]):从输入流读取数据的一些字节,存储到缓冲区,返回读取字节数
- read(byte[], off, len):从输入流读取数据 len 个字节,从偏移 off 开始读取,存储到缓冲区,返回读取字节数
- close():关闭输入流,释放资源
2.1.1、OutputStream
字节输出流
- write(int):将指定的字节(ASCII 码)写入输出流。
- write(byte[]):将指定的字节数组写入输出流。
- write(byte[], off, len):将指定的字节数组的 len 个字节,从偏移 off 开始写入到输出流。
- flush():刷新输出流,并将任何缓冲的字节写出。
- close():先调用 flush(),后关闭输出流,释放资源。
注:write() 仅将数据写入流中,flush() 才将数据刷新到磁盘文件中。
2.2、节点流
直接读取磁盘文件
2.2.1、文件输入流
FileInputStream
-
创建流:参数为文件名
-
创建缓冲区:减少磁盘 I/O,提高效率
-
读取数据
-
关闭流
public void readFile(String fileName) throws IOException { // 1、创建流 FileInputStream fis = new FileInputStream(fileName); // 2、缓冲区 byte[] buf = new byte[1024]; int len = 0; // 3、读取 while ((len = fis.read(buf)) != -1) { System.out.println(new String(buf, 0, len)); } // 4、关闭流 fis.close(); }
2.2.2、文件输出流
使用
-
创建流:参数 1 为文件名,参数 2 表示是否追加(true 表示追加,false 表示覆盖文件)
-
写数据
-
刷新到磁盘
-
关闭流
public void writeFile(String location) throws IOException { // 1、创建流 FileOutputStream fos = new FileOutputStream(location, true); // 2、写 String str = "Hello, I/O world!"; fos.write(str.getBytes()); // 3、刷新 fos.flush(); // 4、关闭流 fos.close(); }
2.2.3、case:文件复制
通过文件字节流实现文件复制
思路:通过输入流读取文件数据,通过输出流写数据。
-
创建流
-
创建缓冲区:减少磁盘 I/O,提高效率
-
读写数据
-
关闭流:先开后关
public void copyFile(String source, String dest) throws IOException { // 1、创建流 FileInputStream fis = new FileInputStream(source); FileOutputStream fos = new FileOutputStream(dest); // 2、缓冲区 byte[] buf = new byte[1024]; int len = 0; // 3、读写 while ((len = fis.read(buf)) != -1) { fos.write(buf, 0, len); } // 4、关闭流:先开后关 fos.close(); fis.close(); }
2.3、缓冲流
属于过滤流
在节点流的基础上使用缓冲区。
- 优点:提高 IO 效率,减少访问磁盘次数。
- 原理:
- 使用节点流读(写)数据到过滤流的缓冲区中。
- 调用方实际读(写)的是缓冲区中的数据,而不是直接读(写)磁盘。
- 开启和关闭
- 创建过滤流时,需要传入节点流。
- 关闭过滤流时,会自动关闭节点流()
2.3.1、缓冲输入流
BufferedInputStream
public void readFile(String filename) throws IOException {
// 1、创建流
FileInputStream fis = new FileInputStream(filename);
BufferedInputStream bis = new BufferedInputStream(fis);
// 2、缓冲区
byte[] buf = new byte[1024];
int len = 0;
// 3、读取
while ((len = bis.read(buf)) != -1) {
System.out.print(new String(buf, 0, len));
}
// 3、关闭流
bis.close();
}
说明:上面使用的缓冲区(称为 b,1K),与过滤流内置的缓冲区(称为 B,默认 8K)不是同一个概念。
在调用 bis.read(buf) 时,实际情况如下
- 过滤流内部使用节点流,将一定的字节数读到 8K 缓冲区 B 中。
- 读取缓冲区中一定数量的字节数,存储到 1K 缓冲区 b 中。
2.3.2、缓冲输出流
BufferedOutputStream
public void writeFile(String location) throws IOException {
// 1、创建流
FileOutputStream fos = new FileOutputStream(location);
BufferedOutputStream bos = new BufferedOutputStream(fos);
// 2、写
String str = "Hello, buffer I/O";
for (int i = 0; i < 10; i++) {
bos.write(str.getBytes());
// 3、刷新
bos.flush();
}
// 4、关闭流
bos.close();
}
注意
-
bos.write(buf):过滤流内部使用节点流,将一定的字节数写到 8K 缓冲区 B 中。
public void write(byte b[]) throws IOException { write(b, 0, b.length); } public void write(byte b[], int off, int len) throws IOException { // 判断下标越界 for (int i = 0 ; i < len ; i++) { write(b[off + i]); } } public void write(int b) throws IOException { out.write(b); } -
bos.flush():过滤流内部使用节点流,将缓冲区中的数据写出
public synchronized void flush() throws IOException { flushBuffer(); out.flush(); } private void flushBuffer() throws IOException { if (count > 0) { out.write(buf, 0, count); count = 0; } } -
bos.close():关闭连接之前,会调用 flush() 刷新输出流。
public void close() throws IOException { try (OutputStream ostream = out) { flush(); } }
2.3.3、case:文件复制
-
创建流:节点流、过滤流
-
缓冲区
-
读写数据
-
关闭连接
public void copyFile(String source, String dest) throws IOException { // 1、 创建流 FileInputStream fis = new FileInputStream(source); FileOutputStream fos = new FileOutputStream(dest); BufferedInputStream bis = new BufferedInputStream(fis); BufferedOutputStream bos = new BufferedOutputStream(fos); // 2、缓冲区 byte[] buf = new byte[1024]; int len = 0; // 3、读写 while ((len = bis.read(buf)) != -1) { bos.write(buf, 0, len); bos.flush(); } // 4、关闭连接 bos.close(); bis.close(); }
2.4、对象流(!)
对象流是字节流的子类之一
属于过滤流,增强了以下功能
- 缓冲区
- 读写基本类型和字符串
- 读写对象
- 序列化:向流中写入一个对象
- 反序列化:从流中读取一个对象
2.4.1、Serializable 接口
对象序列化,必须实现 Serializable 接口。
-
Serializable 接口:标记接口。
-
序列化版本 ID:反序列化时使用,比较二进制文件的 ID 与 Class 文件得到 ID,判断是否为同一个类。
-
属性
- 序列化类的属性页必须实现 Serializable 接口。
- transient 关键字:标明该属性不被序列化。
- 静态属性不被序列化:因为静态属性在类加载时分配在方法区中,而非堆中。
-
序列化多个对象,可以借助集合来实现
// 示例 public class Person implements Serializable { private static final long serialVersionUID = 4620885805607038048L; private String name; private transient int age; // 构造器、toString()等 }
2.4.1、序列化
ObjectOutputStream
public void write(String location) throws IOException {
// 1、创建流
FileOutputStream fos = new FileOutputStream(location);
ObjectOutputStream oos = new ObjectOutputStream(fos);
// 2、序列化对象
Person p1 = new Person("Jaywee", 17);
oos.writeObject(p1);
oos.flush();
// 3、关闭流
oos.close();
}
2.4.2、反序列化
ObjectInputStream
public Person read(String name) throws IOException, ClassNotFoundException {
// 1、创建流
FileInputStream fis = new FileInputStream(name);
ObjectInputStream ois = new ObjectInputStream(fis);
// 2、读
Person p = (Person) ois.readObject();
// 3、关闭
ois.close();
return p;
}
3、字符流
注意:操作和字节流基本相同,但是操作的数据对象不同。
3.0、编码问题
字符编码方式
当编码格式与解码格式不一致时,会出现乱码。
| 编码字节 | 说明 | |
|---|---|---|
| UTF-8 | 可变长度编码 | 根据字符类型,单、双、三、四字节编码 |
| ASCII | 单字节编码 | 用 1 个字节表示英文字符,共 128 个 |
| ISO-8859-1 | 单字节编码 | 共 256 个字符 |
| GBK | 单、双字节编码 | 能表示 21003 个汉字 |
字节流是以字节为单位读取数据,而有些字符是采用多字节编码的方式。
如果使用字节流,对数据的逐个字节进行读取,就会导致乱码。
-
例:逐个读取字节
public static void read(String name) throws IOException { // 1、创建流 FileInputStream fis = new FileInputStream(name); // 2、逐个字节读取 int data = 0; // fis.read()返回数据的下一个字节 while ((data = fis.read()) != -1) { System.out.print((char) data); } // 3、关闭 fis.close(); } -
测试:输出乱码
解决方案
- 调用
read(byte[])重载方法,将数据批量读取到缓冲区中。 - 使用字符流。
3.1、抽象类
3.1.1、Reader
字符输入流
- read():从输入流读取数据的下一个字符,返回下一个字符
- read(char[]):从输入流读取数据的一些字符,存储到缓冲区,返回读取字符数
- read(char[], off, len):从输入流读取数据 len 个字符,从偏移 off 开始读取,存储到缓冲区,返回读取字符数
- close():关闭输入流,释放资源
3.1.2、Writer
字符输出流
- write(int):将指定字符写入输出流。
- write(byte[]):将指定的字符数组写入输出流。
- write(byte[], off, len):将指定的字符数组的 len 个字节,从偏移 off 开始写入到输出流。
- flush():刷新输出流,并将任何缓冲的字符写出。
- close():先调用 flush(),后关闭输出流,释放资源。
注:write() 仅将数据写入流中,flush() 才将数据刷新到磁盘文件中。
3.2、节点流
3.2.1、读文件
FileReader
-
逐个字符读取并打印,不会报错。
-
也可以调用
read(char[])重载方法,通过缓冲区读取。public void readFile(String fileName) throws IOException { // 1、创建流 FileReader fr = new FileReader(fileName); // 2、逐个字符读取 int data = 0; // fr.read()返回数据的下一个字符 while ((data = fr.read()) != -1) { System.out.print((char) data); } // 3、关闭 fr.close(); }
3.2.2、写文件
FileWriter
public void writeFile(String location) throws IOException {
// 1、创建流
FileWriter fw = new FileWriter(location);
// 2、写
String str = "Hello, I/O world!";
fw.write(str);
// 3、刷新
fw.flush();
// 4、关闭流
fw.close();
}
3.2.3、case:文件复制
与使用字节流进行文件复制的区别
-
使用的流不同(字节流、字符流)
-
缓冲区类型不同(byte、char)
-
文件类型不同
-
字节流可以复制任意类型。
-
字符流只能复制文本文件,不能复制图片等二进制文件。
public void copyFile(String source, String dest) throws IOException { // 1、创建流 FileReader fr = new FileReader(source); FileWriter fw = new FileWriter(dest); // 2、缓冲区 char[] buf = new char[1024]; int len = 0; // 3、读取 while ((len = fr.read(buf)) != -1) { fw.write(buf, 0, len); } // 4、关闭流:先开后关 fw.close(); fr.close(); }
-
3.3、缓冲流
缓冲流属于过滤流
相比字节缓冲流的不同点
- 支持输入换行符
- 使用字节流需要手动输入换行符:Linux 是
\n,Windows 是\r\n\ - 字符缓冲流提供
newLine()方法,根据操作系统自动写入换行符。
- 使用字节流需要手动输入换行符:Linux 是
- 支持读一行:提供
readLine()方法。
3.3.1、缓冲读
BufferedReader
演示逐行读取的方式
public void read(String name) throws IOException {
// 1、创建流
FileReader fr = new FileReader(name);
BufferedReader br = new BufferedReader(fr);
// 2、逐行读取
String line = null;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
// 3、关闭
br.close();
}
3.3.1、缓冲写
BufferedWriter
public void write(String location) throws IOException {
// 1、创建流
FileWriter fw = new FileWriter(location);
BufferedWriter bw = new BufferedWriter(fw);
// 2、写
String str = "Hello, buffer I/O";
for (int i = 0; i < 10; i++) {
bw.write(str);
// 换行符
bw.newLine();
bw.flush();
}
// 3、关闭
bw.close();
}
3.4、打印流
3.4.1、说明
打印流是 Writer 的子类之一
属于过滤流,增强了以下功能
-
封装
print()、println(),支持写入后换行。print()实际上还是调用write()println()相当于print()+ 换行
-
数据原样打印:如不会将数字转换为字符。
// print()部分重载方法 public void print(boolean b) { write(b ? "true" : "false"); } public void print(char c) { write(c); } public void print(int i) { write(String.valueOf(i)); } // println(String) public void println(String x) { synchronized (lock) { print(x); println(); } }
3.4.2、使用
public void write(String location) throws IOException {
// 1、创建流
FileWriter fw = new FileWriter(location);
PrintWriter pw = new PrintWriter(fw);
// 2、打印
pw.print(97);
pw.print(7.2);
pw.print('c');
pw.println("Jaywee");
pw.print(true);
// 3、刷新
pw.flush();
// 4、关闭
pw.close();
}
3.5、转换流
作用:将字节流转换为字符流,可显式设置或使用默认字符编码(解码)方式。
的体现
- 适配器类:InputStreamReader、InputStreamWriter
- 适配者:字符流,Reader、Writer
- 被适配者:字节流,InputStream、InputStream
3.5.1、读
InputStreamReader
-
将字节输入流转换为字符输入流
-
可显式设置解码方式(Decoder)
public void read(String name) throws IOException { // 1、创建流 FileInputStream fis = new FileInputStream(name); // 可指定解码方式:CharsetDecoder InputStreamReader reader = new InputStreamReader(fis); // 2、读 int data = 0; while ((data = reader.read()) != -1) { System.out.print((char) data); } // 3、关闭 reader.close(); }
3.5.2、写
OutputStreamWriter
-
将字节输出流转换为字符输出流
-
可显式设置解码方式(Encoder)
public void write(String location) throws IOException { // 1、创建流 FileOutputStream fos = new FileOutputStream(location); // 可指定解码方式:CharsetEncoder OutputStreamWriter writer = new OutputStreamWriter(fos); // 2、写 String str = "Hello, the bridge from byte streams to character stream"; writer.write(str); writer.flush(); // 3、关闭 writer.close(); }
4、File
使用 I/O 流实现对磁盘文件的读写操作,针对的是文件内容。
File:代表磁盘中的一个文件或文件夹,提供操作文件本身的方法。
4.1、常用属性和方法
4.1.1、属性
-
路径分隔符:
pathSeparator、pathSeparatorChar(;) -
名称分隔符:
Separator、SeparatorChar(\) -
抽象路径:
path,即创建文件时指定的路径// 抽象路径为myFile.txt File file1 = new File("myFile.txt"); // 抽象路径为F:\myFile.txt File file2 = new File("F:\\myFile.txt");
4.1.2、创建、删除
注:使用 new 关键字创建的 File 实例,仅在堆内存中存在,磁盘中未必有对应路径的文件。
- 创建文件:
createNewFile(),当指定路径的文件不存在时创建。 - 创建文件夹
mkdir():单级目录mkdirs():级联创建多级目录
- 删除文件(目录):若为多级目录则只删除最内层目录,当目录为空时才删除
delete():直接删除deleteOnExit:虚拟机终止时删除
4.1.3、文件信息
getPath():抽象路径getAbsolutePath:绝对路径getName():文件(目录)名getParent():文件所在目录(父级目录)getLenth():文件长度lastModified():文件最后修改时间
4.1.4、判断
- 类型:
isFile():是否为文件isDirectory:是否为目录
- 存在:
exists() - 隐藏:
isHidden()
4.2、文件夹操作
文件夹主要涉及 2 个操作
- 列出内容:包括文件和子目录
- 遍历
4.2.1、列出内容
File 类提供了两个方法,用于列出目录中的所有内容
list():返回一个字符串数组,包含文件夹中所有文件和子目录的文件名。listFiles():返回一个文件数组,包含文件夹中的所有文件和子目录的对应 File 对象。
4.2.2、文件过滤器
File 类的 list() 和 listFiles() 可以搭配文件过滤器,定义文件筛选规则
-
FileNameFilter(JDK 1.0)
- dir:使用该过滤器的文件夹对象
- name:文件夹中的文件(目录)名
-
FileFilter(JDK 1.2)
- pathname:文件夹中的文件(目录)
@FunctionalInterface public interface FilenameFilter { boolean accept(File dir, String name); } @FunctionalInterface public interface FileFilter { boolean accept(File pathname); }
测试
以 listFiles() 和 FileFilter 为例,列出 dir 中的 .jpg 文件名。
// 测试
@Test
public void testFilter() {
File dir = new File("目录路径");
File[] files = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().endsWith(".jpg");
}
});
for (File file : files) {
System.out.println(file);
}
}
匿名内部类简化写法:lambda表达式
// 简化
files = dir.listFiles((pathName) -> {
return pathName.getName().endsWith(".jpg");
});
// 更简化
files = dir.listFiles((pathName) -> pathName.getName().endsWith(".jpg"));
4.2.3、递归
思路:遍历当前文件夹,若为文件夹则递归调用,否则执行相应动作。
递归遍历
public void recursiveList(File dir) {
// 当前目录路径
System.out.println(dir.getAbsoluteFile());
File[] files = dir.listFiles();
if (files != null && files.length > 0) {
// 遍历
for (File file : files) {
if (file.isDirectory()) {
// !递归!
recursiveList(file);
} else {
System.out.println(file.getAbsolutePath());
}
}
}
}
递归删除
注意:递归删除子文件夹内容后,删除子文件夹本身。
public void recursiveDelete(File dir) {
// 当前目录路径
System.out.println("===== " + dir.getAbsoluteFile() + " =====");
File[] files = dir.listFiles();
if (files != null && files.length > 0) {
// 遍历
for (File file : files) {
if (file.isDirectory()) {
// !递归!
recursiveDelete(file);
// 删除子文件夹本身
file.delete();
} else {
System.out.println(file.getAbsolutePath() + " was deleted? " + file.delete());
}
}
}
}
5、小结
5.1、流
流:内存与存储设备之间传输数据的通道。
回顾前言
- 字节流和字符流的操作相同,只是操作的数据类型不同。
- 节点流和过滤流的关系,是 的体现。
- 转换流,是 的体现。
分类
- 方向:输入流、输出流
- 单位:字节流、字符流
- 功能:节点流、过滤流
5.2、字节流
Inputstream、OutputStream
- 节点流:FileInputstream、FileOutputStream
- 过滤流
- 缓冲流:BufferedInputStream、BufferedOutputStream
- 对象流:ObjectInputStream、ObjectOutputStream
- Serializable 接口、transient
- 序列化与反序列化
5.3、字符流
Reader、Writer
- 编码问题
- 节点流:FileReader、FileWriter
- 过滤流
- 缓冲流:BufferedFileReader、BufferedFileWriter
- 打印流:PrintWriter
- 转换流:InputStreamReader、InputStreamWriter()
5.4、File
- 使用 I/O 流实现对磁盘文件的读写操作,针对的是文件内容。
- 使用 File 操作文件本身。
- 常用属性和方法
- 分隔符
- 创建、删除
- 文件信息:路径、名称、父级目录、长度、最后修改时间
- 判断:类型、存在、隐藏
- 文件夹操作
- 列出内容、Filter 筛选
- 递归
总结 I/O 两句话
- 流的使用方式基本相同,一通百通。
- 结合设计模式学习 I/O,融会贯通。