Java IO系列 | 经典 IO

前言

Android 开发中不可避免会接触到IO,本篇作为系列中的第一篇,将和大家一同花费5分钟的时间,梳理 经典IO 的知识。

重点内容脑图如下,文章亦按照此结构展开,如已熟练掌握,可跳过

desc

作者按:在一些文章、资料中,将JDK1.4前的IO内容,称为 经典IOBIO标准IOBlocking IO传统IOJava IO。 都有一定的道理,但注意 BIOBlocking IO 此类称谓可能引起混淆。本系列中以 经典IO 指代JDK1.4 前的IO

JAVA 经典IO 概述

流模型与分类

desc

在经典IO中,流(stream)是对数据传输的总称。流代表数据的流动。

  • 操作单位 ,可以分为
    • 字节流 ,操作对象为字节
    • 字符流 ,操作对象为字符,涉及到编码
  • 数据流方向 , 可以分为:
    • 输入流 , 输入流代表从某个地方(硬盘、内存、网络等)读入内存
    • 输出流 , 输出流代表从内存写出到某个地方
  • 功能类型 , 可以分为:
    • 节点流 , 节点流代表数据的源头和终点,常见的有文件流、数组流和管道流等
    • 处理流 , 处理流是连接在节点流之上,为节点流提供某种额外的功能,如缓冲、转换等

优点

不难想象,IO的 场景处理过程 是多而繁杂的,因此需要 加以抽象 、并 建立类簇,以满足程序设计的通用性和扩展性,而 的抽象非常贴切,包含了最基础的操作API。 对于调用方而言非常友好

局限性

经典IO 设计中采用 同步阻塞机制 。以读为例,调用 read() 方法时,如果数据尚未就绪,线程会阻塞。这会降低程序的运行效率,在网络IO中更加明显。

对于IO负荷较小的客户端程序,尚可接受,但对于 高负载高并发 的服务器环境,经典IO存在明显的瓶颈,并不能充分发挥硬件性能和带宽。

字节流

按照数据流方向,JDK中设计有两个基类:

  • InputStream 读取字节数据的抽象父类
  • OutputStream 写出字节数据的抽象父类

UML

JDK中主要的类UML图如下:

作者按:每个子类的具体作用不再展开,如您尚不清楚部分类的作用,可直接阅读JDK中的注释

desc desc
  • FileInputStreamFileOutputStream:用于读写文件
  • BufferedInputStreamBufferedOutputStream:带缓冲的文件流,可以提高读写效率
  • DataInputStreamDataOutputStream:用于读写基本数据类型
  • ByteArrayInputStreamByteArrayOutputStream:操作内存中的字节数组
  • ObjectInputStreamObjectOutputStream:用于读写可序列化的对象

当下,应用中 ObjectInputStreamObjectOutputStream 使用相对较少,一般会将其序列化为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图如下:

desc desc
  • FileReaderFileWriter :用于读取和写入字符文件
  • BufferedReaderBufferedWriter :带缓冲的文件字符流,可以提高读写效率
  • CharArrayReaderCharArrayWriter :读取和写入内存字符数组
  • PipedReaderPipedWriter :pipe输入流、输出流,线程间通信
  • InputStreamReaderOutputStreamWriter:将字节流转化为字符流,作为桥梁使用

不再展示使用方式

缓冲流

缓冲流内部有一个缓冲区,可以减少实际读取数据的次数,从而提高流的读取、写入效率。

缓冲流是一种加强的流, 内部拥有一个 缓冲区 ,可以 减少系统调用的次数,从而提高读写效率

Java 提供了以下的缓冲流:

  • 字节缓冲流 BufferedInputStreamBufferedOutputStream : 对 FileInputStreamFileOutputStream 加以缓冲
  • 字符缓冲流 BufferedReaderBufferedWriter : 对 FileReaderFileWriter 加以缓冲

打印流

PrintStreamPrintWriter 可以打印各种数据类型,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的基础知识。