09.IO流
IO流
用于读写文件或网络中的数据, 以程序为参照
字节流
InputStream
FileInputStream
-
创建字节输入流对象
- 文件不存在直接报错
-
读数据
-
一次读一个字节, 读出的数据为在ASCII上对应的数字
-
读到文件末尾了, read方法返回-1
-
循环读取
1
2
3
4
5
6
7
8
9
10
11
12// 循环读取一个字节数据
int b;
while((b = fis.read()) != -1) {
sout((char) b);
}
// 循环读取一个容量大小的字节数据
int len;
byte[] bytes = new byte[1024 * 1024 * 5];
while((len = fis.read(bytes)) != -1) {
sout(new String(bytes, 0, len));
}
-
-
释放资源
- 每次使用完流后都要释放资源
OutputStream
FileOutputStream
-
创建字节输出流对象
- 参数是字符串表示的路径或者是File对象都是可以的
- 如果文件不存在会创建一个新的文件, 但是要保证父级路径是存在的
- 如果文件已经存在, 则会清空文件, 可以通过第二个参数打开续写
-
写数据
- write方法的参数是整数, 但是实际上写到本地文件中的是对应的ASCII字符
- 可以写出字节数组
- 换行符
\r\n
-
释放资源
- 每次使用完流后都要释放资源
字符集
-
ASCII: 英文字符, 一个字节
- 编码规则: 前面补0, 补齐8位
- 解码规则: 直接转成十进制
-
GBK: 简体中文版Windows默认使用
- 英文用一个字节存储, 完全兼容ASCII
- 编码规则: 前面补0, 补齐8位
- 汉字两个字节存储, 高位字节二进制一定以1开头, 转成十进制之后是一个负数
- 编码规则: 不需要变动
- 解码规则: 直接转成十进制
- 英文用一个字节存储, 完全兼容ASCII
-
Unicode
-
UTF-8编码规则: 用1~4个字节保存
-
-
避免乱码
- 不要用字节流读取文本文件
- 编码解码时使用同一个码表, 同一个编码方式
Java中编码和解码方法
-
编码
1
2public byte[] getBytes() // 使用默认方式编码
public byte[] getBytes(String charsetName) // 使用指定方式编码 -
解码
1
2String(byte[] bytes) // 使用默认方式解码
String(byte[] bytes, String charsetName) // 使用指定方式解码
字符流
字符流 = 字节流 + 字符集
Reader
FileReader
-
创建字符输入流对象
- 底层: 关联文件, 并创建缓冲区(长度为8192的字节数组)
-
读取数据
-
底层:
-
判断缓冲区中是否有数据可以读取
-
缓冲区没有数据
- 就从文件中获取数据, 装到缓冲区中, 每次尽可能装满缓冲区
- 如果文件中也没有数据了, 返回-1
-
缓冲区有数据: 从缓冲区中读取
-
-
按字节进行读取, 遇到中文, 一次读多个字节, 读取后解码, 返回整数
1
2
3
4
5
6
7// 读取数据, 读到末尾返回-1
// 一次读取一个字节, 遇到中文一次读多个字节, 把字节解码并转成十进制返回
public int read()
// 读取多个数据, 读到末尾返回-1, 返回值是读出的长度
// 把读取字节, 解码, 强转三步合并, 强转之后的字符放到数组中
public int read(char[] buffer) -
-
释放资源
Writer
FileWriter
-
创建字符输出流对象
- 底层: 也会创建长度为8192的缓冲区
- 参数是字符串表示的路径或者File对象都是可以的
- 如果文件不存在会创建一个新的文件, 但要保证父级路径是存在的
- 如果文件已经存在, 则会清空文件, 如果不想清空可以打开续写开关
-
写数据
- 底层: 填满缓冲区, 手动flush, close都会写入数据
- 如果
write
方法的参数是整数, 实际写到文件中的是对应的ASCII字符 - 根据字符集的编码方式进行编码, 把编码之后的数据写到文件中
-
释放资源
字节流和字符流的使用场景
-
字节流: 拷贝任意类型的文件
-
字符流: 读取纯文本文件中的数据, 往纯文本文件中写出数据
缓冲流
字节缓冲流
-
在构造方法时其实就是将基本流进行包装
1
2public BufferedInputStream(InputStream is)
public BufferedOutputStream(OutputStream os) -
关流时只要关闭缓冲流
字符缓冲流
-
由于字符流已有缓冲区, 故字符缓冲流对效率影响不大
-
特殊方法
1
2
3
4
5
6
7public BufferedReader(Reader r)
// 在读取时, 一次读一整行, 遇到回车换行结束, 但不会读入回车换行, 遇到末尾返回null
public String readLine()
public BufferedWriter(Writer r)
// 跨平台的换行
public void newLine()
转换流
字符流和字节流之间的桥梁
1 | // 字符转换输入流 |
-
当字节流想要使用字符流中的方法时可以使用
-
指定字符集读写数据(JDK11后淘汰)
序列化流
可以把java中的对象写到本地文件中
1 | // 把基本流包装成高级流 |
-
要给对象实现Serializable接口
- 这个接口里没有抽象方法, 是标记型接口
- 一旦实现该接口, 就表示当前对象可以被序列化
反序列化流
可以把序列化到本地文件中的对象读取到程序中
1 | // 把基本流变成高级流 |
序列化流和反序列化流细节汇总
-
使用序列化流将对象写到文件时, 需要让javaBean类实现Serializable接口, 否则会出现
NotSerializableException
异常 -
序列化流写到文件中的数据是不能修改的, 一旦修改就无法再次读回来了
-
序列化对象后, 修改了javaBean类, 再次反序列化会出现问题, 会抛出
InvalidClassException
异常- 解决方案: 给JavaBean类添加serialVersionUID(序列号, 版本号)
-
如果一个对象中的某个成员变量的值不想被序列化, 可以给该成员变量添
transient
关键字修饰, 该关键字标记的成员变量不参与序列化过程 -
规范下将对象放入集合中读写, 前提集合实现Serializable接口
打印流
-
只有输出流, 只能操作文件目的地, 不操作数据源
-
特有的写出方法可以实现, 数据原样写出
-
特有的写出方法, 可以实现自动刷新, 自动换行
- 打印一次数据 = 写出 + 换行 + 刷新
-
PrintStream ps = System.out;
- 获取打印流对象, 此打印流在虚拟机启动的时候, 由虚拟机创建, 默认指向控制台
- 特殊的打印流, 系统中的标准输出流, 不能关闭, 在系统中是唯一的
字节打印流
-
字节流底层没有缓冲区, 构造方法时开不开自动刷新都一样
-
构造方法:
public PrintStream(...)
特有方法
1 | // 打印任意数据, 自动刷新, 自动换行 |
字符打印流
-
字符流底层有缓冲区, 想要自动刷新需要开启
-
构造方法:
public PrintWriter(...)
特有方法
-
与字节打印流的特有方法一样
解压缩流
-
解压的本质: 把压缩包里面的每一个文件或文件夹读取出来, 按照层级拷贝到目的地中
-
Java中压缩包中的文件(夹)实际上是一个个
ZipEntry
对象
1 | // 创建一个解压缩流用来读取压缩包中的数据 |
压缩流
1 | /* |
常用工具类
Commons-io
Hutool
Properties配置文件
-
properties是一个双列结合, 拥有Map集合所有的特点
-
拥有IO特性, 特有的 store 和 load 方法, 处理 .properties 文件