Java IO系列 | 经典 IO
前言
Android 开发中不可避免会接触到IO,本篇作为系列中的第一篇,将和大家一同花费5分钟的时间,梳理 经典IO 的知识。
重点内容脑图如下,文章亦按照此结构展开,如已熟练掌握,可跳过
作者按:在一些文章、资料中,将JDK1.4前的IO内容,称为 经典IO
、 BIO
、 标准IO
、 Blocking IO
、传统IO
、 Java IO
。 都有一定的道理,但注意 BIO
、Blocking IO
此类称谓可能引起混淆。本系列中以 经典IO
指代JDK1.4 前的IO
JAVA 经典IO 概述
流模型与分类
在经典IO中,流(stream)是对数据传输的总称。流代表数据的流动。
- 按
操作单位
,可以分为字节流
,操作对象为字节字符流
,操作对象为字符,涉及到编码
- 按
数据流方向
, 可以分为:输入流
, 输入流代表从某个地方(硬盘、内存、网络等)读入内存输出流
, 输出流代表从内存写出到某个地方
- 按
功能类型
, 可以分为:节点流
, 节点流代表数据的源头和终点,常见的有文件流、数组流和管道流等处理流
, 处理流是连接在节点流之上,为节点流提供某种额外的功能,如缓冲、转换等
优点
不难想象,IO的 场景
和 处理过程
是多而繁杂的,因此需要 加以抽象
、并 建立类簇
,以满足程序设计的通用性和扩展性,而 流
的抽象非常贴切,包含了最基础的操作API。
对于调用方而言非常友好。
局限性
经典IO 设计中采用 同步阻塞机制
。以读为例,调用 read()
方法时,如果数据尚未就绪,线程会阻塞。这会降低程序的运行效率,在网络IO中更加明显。
对于IO负荷较小的客户端程序,尚可接受,但对于 高负载
、高并发
的服务器环境,经典IO存在明显的瓶颈,并不能充分发挥硬件性能和带宽。
字节流
按照数据流方向,JDK中设计有两个基类:
InputStream
读取字节数据的抽象父类OutputStream
写出字节数据的抽象父类
UML
JDK中主要的类UML图如下:
作者按:每个子类的具体作用不再展开,如您尚不清楚部分类的作用,可直接阅读JDK中的注释
FileInputStream
和FileOutputStream
:用于读写文件BufferedInputStream
和BufferedOutputStream
:带缓冲的文件流,可以提高读写效率DataInputStream
和DataOutputStream
:用于读写基本数据类型ByteArrayInputStream
和ByteArrayOutputStream
:操作内存中的字节数组ObjectInputStream
和ObjectOutputStream
:用于读写可序列化的对象
当下,应用中 ObjectInputStream
、ObjectOutputStream
使用相对较少,一般会将其序列化为JSON、XML存储或映射到数据库。
对象类必须满足以下条件:
- 实现
java.io.Serializable
接口。 - 该类
除transient关键字修饰
的所有属性必须是可序列化的。
class Demo {
public static void main(String[] args) throws Exception{
// 写对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
oos.writeObject(new Person("张三", 25));
oos.close();
// 读对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
Person p = (Person) ois.readObject();
ois.close();
System.out.println(p.getName() + ", " + p.getAge());
// 张三, 25
}
}
字符流
按照数据流的方向,JDK中设计有两大基类:
Reader
:读取字符数据的抽象父类。Writer
:写出字符数据的抽象父类。
UML
JDK中主要的类UML图如下:
FileReader
和FileWriter
:用于读取和写入字符文件BufferedReader
和BufferedWriter
:带缓冲的文件字符流,可以提高读写效率CharArrayReader
和CharArrayWriter
:读取和写入内存字符数组PipedReader
和PipedWriter
:pipe输入流、输出流,线程间通信InputStreamReader
和OutputStreamWriter
:将字节流转化为字符流,作为桥梁使用
不再展示使用方式
缓冲流
缓冲流内部有一个缓冲区,可以减少实际读取数据的次数,从而提高流的读取、写入效率。
缓冲流是一种加强的流, 内部拥有一个 缓冲区
,可以 减少系统调用的次数,从而提高读写效率 。
Java 提供了以下的缓冲流:
- 字节缓冲流
BufferedInputStream
和BufferedOutputStream
: 对FileInputStream
和FileOutputStream
加以缓冲 - 字符缓冲流
BufferedReader
和BufferedWriter
: 对FileReader
和FileWriter
加以缓冲
打印流
PrintStream
和 PrintWriter
可以打印各种数据类型,PrintWriter
的打印格式更加丰富一些,支持字符串的格式化输出。
两者主要区别:
PrintStream
是字节流,PrintWriter
是字符流。PrintStream
不支持写入文件之外的其他sink, 但PrintWriter
支持。- 在API丰富性上,
PrintStream
只有print()
和println()
两个打印API,PrintWriter
更丰富,且支持格式化输出。 PrintWriter
可能抛出IOException
。
PrintStream
存在少许性能优势,但在大多数情况下 PrintWriter 比 PrintStream 更实用。
随机访问流
RandomAccessFile
可以随机访问文件中的任意位置,它既可以作为输入流也可以作为输出流。
它具有两个构造方法:
RandomAccessFile(File file, String mode)
RandomAccessFile(String name, String mode)
mode参数
指定打开文件的模式,有以下几种:
- r: 只读模式
- rw: 读写模式
- rws: 读写同步模式
- rwd: 读写,同步元数据
rws
,比rw的写多了同步,同步写入指的是将文件内容和元数据同步写入磁盘,保证数据不丢失。例如,我们写入一些数据,关闭流后,数据实际上还在操作系统的缓冲区,还没有真正写入磁盘,如果系统出现异常,缓冲区数据会丢失。
rwd
,比rw多了同步元数据,元数据是描述数据属性和结构的信息,文件系统会维护每个文件的元数据,如:
- 文件大小
- 创建时间
- 最后修改时间
- 文件权限
- 文件类型等 rwd模式会在修改文件内容后自动更新文件的元数据,保证元数据的同步性
在文件断点续传需求场景下,RandomAccessFile
具有极高适用性,结合切片处理的思路,可以充分发挥带宽和多核多线程优势。
结语
本篇文章简单梳理了Java 经典IO的基础知识,并未深入,应对客户端中的需求场景已基本够用,下一篇,我们将继续梳理Java NIO的基础知识。