BIO/NIO/AIO知识点总结

  • 1. Java IO和流
  • 1.1 流的分类
  • 2. Java IO
  • 2.1 InputStream和OutputStream用例
  • 2.2 Writer和Reader用例
  • 3. IO的同步/异步和阻塞/非阻塞
  • 4. BIO,NIO,AIO
  • 参考材料


1. Java IO和流

IO的全程是Input/Output,也就是输入输出。

在Java当中,数据的处理都是以流(Stream)的方式来进行,流在这里是一个抽象概念,指代所有可以产出数据的对象或者是数据源(像是键盘,文件,网络等都是流)。通过流的形式Java可以简化数据源的模型来进行数据处理。

1.1 流的分类

按来源区分:

  • 输入流:程序打开一个数据源,按顺序从数据源中读取数据到程序中。
  • 输出流:程序打开一个目的源,按顺序把数据写到目的源中。

按数据单元区分:

  • 字节流:读取的单位是字节,也就是每次只读取1byte长度的数据。
  • 字符流:读取的单位是字符,在Java中1字符的长度是2字节。

按流的角色区分:

  • 节点流:有连接到实际的数据源的流。
  • 处理流:对已有的流进行连接和封装,再进行数据处理的流。也被称作高级流。

2. Java IO

IO的类基本都在Java.io包下面,基本按数据单元分为两个大类

  • 字节流:用InputStream和OutputStream
  • 字符流:Reader和Writer

2.1 InputStream和OutputStream用例

首先,这两个类是抽象类,不能直接使用,只能通过它的子类来使用。这里就用FileInputStream和FileOutputStream。

public class StreamDemo {
    public static void main(String[] args) {
        String path = "test.txt";
        createFile(path);
        output(path);
        input(path);
    }

    // 读取文件的内容
    public static void input(String path){
        FileInputStream inputStream = null;

        byte[] content = new byte[1024];
        try{
            // 实例化文件输入流
            inputStream = new FileInputStream(path);
            // 读取文件,由于字节流的单位长度是byte,所以用byte数组来存储
            int len = inputStream.read(content);
            // 输出数据。不能直接转,会有无效字符,要填上区间。
            System.out.println(new String(content, 0, len));

			inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 往文件写入内容
    public static void output(String path){
        String content = "hello Java IO";

        FileOutputStream outputStream = null;

        try{
            // 实例化文件输出流
            outputStream = new FileOutputStream(path);
            // 写内容,由于字节流的单位长度是Byte,所以要转成Byte。
            outputStream.write(content.getBytes());

            outputStream.close();


        }catch (IOException e) {
            e.printStackTrace();
        }finally {

        }
    }

    public static void createFile(String path){
        File file = new File(path);
        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

2.2 Writer和Reader用例

Writer和Reader也是抽象类,要用它的子类实例化,这里以FileWriter和FileReader为例。

public class WriterDemo {
    public static void main(String[] args) {
        String path = "test2.txt";
        createFile(path);
        write(path);
        read(path);
    }

    public static void write(String path){
        String content = "hello Java IO";

        FileWriter fileWriter = null;
        try {
            // 实例化流
            fileWriter = new FileWriter(path);
            // 对于字符流而言,输出用char,源码会将String转成char。
            fileWriter.write(content);

            fileWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void read(String path){
        FileReader fileReader = null;

        char[] content = new char[1024];
        try {
            // 实例化流
            fileReader = new FileReader(path);
            // 对于字符流而言,用char数组直接接收
            int len = fileReader.read(content);
            System.out.println(new String(content, 0, len));

            fileReader.close();

        }catch (IOException e){
            e.printStackTrace();
        }
    }

    public static void createFile(String path){
        File file = new File(path);
        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

3. IO的同步/异步和阻塞/非阻塞

同步异步是被调用一方的概念,具体体现在被调用一方的通知方式上。

  • 同步:A调用了B,然后B在完成任务(或失败任务)后通过原有的调用通知A。
  • 异步:A调用了B,然后B在完成任务(或失败任务)后通过状态,监听等别的方式通知A。

阻塞和非阻塞是调用一方的概念,具体体现在调用一方在等待结果的过程中是否继续工作。

  • 阻塞:A调用了B,在B完成任务的时间内,A啥事都不干就在这白等,直到B完成任务通知A后A才继续工作。
  • 非阻塞:A调用了B,在B完成任务的时间内,A直接去做别的事。

阻塞非阻塞和同步异步两两组合有四种结果:

组合方式

性能分析

同步阻塞(BIO)

最常用的一种用法,使用也是最简单的,但是 I/O 性能一般很差,CPU 大部分在空闲状态。

同步非阻塞(NIO)

提升 I/O 性能的常用手段,就是将 I/O 的阻塞改成非阻塞方式,尤其在网络 I/O 是长连接,同时传输数据也不是很多的情况下,提升性能非常有效。 这种方式通常能提升 I/O 性能,但是会增加CPU 消耗,要考虑增加的 I/O 性能能不能补偿 CPU 的消耗,也就是系统的瓶颈是在 I/O 还是在 CPU 上。

异步阻塞

这种方式在分布式数据库中经常用到,例如在网一个分布式数据库中写一条记录,通常会有一份是同步阻塞的记录,而还有两至三份是备份记录会写到其它机器上,这些备份记录通常都是采用异步阻塞的方式写 I/O。异步阻塞对网络 I/O 能够提升效率,尤其像上面这种同时写多份相同数据的情况。

异步非阻塞(AIO)

这种组合方式用起来比较复杂,只有在一些非常复杂的分布式情况下使用,像集群之间的消息同步机制一般用这种 I/O 组合方式。如 Cassandra 的 Gossip 通信机制就是采用异步非阻塞的方式。它适合同时要传多份相同的数据到集群中不同的机器,同时数据的传输量虽然不大,但是却非常频繁。这种网络 I/O 用这个方式性能能达到最高。

4. BIO,NIO,AIO

  • BIO:Blocking IO,即同步阻塞IO。我们平时用到的InputStream和OutputStream还有Reader、Writer就是BIO。
  • NIO:Non-blocking IO或者New IO,即同步非阻塞IO。由于BIO的效率不高,JDk1.4引入了java.nio包,里面有Nio相关的类,性能会比BIO好。
  • AIO:Asynchronous IO,JDK1.7后加入的异步非阻塞IO。

参考材料

从同步阻塞聊到Java三种IO方式 | Daryl’s Blog