张坤的个人博客

  • 首页
  • 分类
  • 标签
  • 日志

  • 搜索
Jenkins RabbitMQ Zookeeper IDEA Logstash Kibana ELK NIO Netty Spring Cloud Golang DataX Elasticsearch React Native Mysql H2 Socket Spring Boot Kafka Mybatis Sqlmap Vue Postgresql Docker Vert.x Flutter Flink Redis

IO基础回顾

发表于 2020-06-18 | 分类于 Netty | 0 | 阅读次数 44

BIO

说到Java IO流,就会想到OutputStream(字节输出流)、InputStream(字节输入流)、Writer(字符输出流)、Reader(字符输入流) 这些概念。

image.png

IO操作提供了操作 本地文件、网络资源、数据库读写 等功能,这些都是需要和磁盘或者网络打交道的,这类操作叫做耗时操作,容易导致服务阻塞。当然像 ByteArrayInputStream 和 ByteArrayOutputStream 这种只操作内存的IO流除外。

所以BIO叫做 同步阻塞IO,可以通过线程池的方式解决,将每个IO操作单独分配给一个线程,这样主线程就不会被阻塞了。线程池的方式也有效避免了线程重复创建和销毁导致的资源消耗

NIO

JDK1.4以后为我们提供了NIO工具类,在 java.nio 包下
NIO可以在只有一个线程的情况下实现非阻塞操作,主要是通过 Buffer(缓冲区)、Channel(通道)、Selector(选择器) 来实现的。

image.png

客户端向服务端发起连接时会先获取到一个SocketChannel,Selector会用一个集合去存储绑定这个Channel的SelectionKey。客户端发数据不会直接将数据实时写入服务端,而是先将数据写入到本地Buffer中,Buffer就起到一个缓冲区的作用,如果缓冲区满了,就向Channel中写入数据,Selector监听到某个SelectionKey有事件发生,就通过这个SelectionKey获取到这个Channel,然后从Channel中读取数据。这样设计的好处就是,Server不需要一直和Client保持连接,而是Client需要Server去处理数据的时候Server再去处理。这样Server就可以同时处理多个IO操作,如果都没有IO请求,Server甚至还可以干自己的事情。

服务端在单线程情况下(也是可以用多线程的,Netty就是基于NIO的,Netty可以创建线程池去处理IO操作)使用一个Selector循环遍历存储SelectionKey的集合,如果某个SelectionKey有事件发生,Selector就处理这个SelectionKey对应的Channel中的数据。

NIO特点是非阻塞、基于事件监听的机制,所以NIO又叫 同步非阻塞IO

AIO

jdk1.7提供支持
简单来说,就叫 异步非阻塞IO,用多线程的方式处理IO。这样一看好像和NIO没啥区别嘛。NIO也是非阻塞的,搞个线程池不就是AIO了?

实际上他们区别大了,上面说NIO是通过Selector遍历所有的Channel去实现事件监听的,这么做其实是 伪事件监听 的做法。

就好比,在医院,5个小伙伴打吊针,他们分别在1~5楼,如果是NIO的方式,只需要一个护士小姐姐从一楼到五楼都走上一遍,看他们的点滴瓶子空了没,这还是个反复的过程。如果是BIO,一个还不够,需要5个小姐姐盯着自己对号的小伙伴。AIO在NIO的基础上更近一层,通常也叫NIO2.0。AIO实现了真正的事件监听,如果哪个小伙伴点滴瓶子空了,不需要小姐姐再过来检查了,而是会自己通知小姐姐过来。中间这段时间,小姐姐就不用再到处跑了,可以安心在自己办公室做个面膜。可以看得出来,AIO比NIO在对系统资源的调度方面更进了一步。

AIO事件通知需要操作系统的配合。

Buffer

在NIO中,Buffer起到了缓冲数据的作用,也是非阻塞实现的关键,Jdk为我们预定义了一些Buffer实现类
image.png
可以看到,是一些基本类型的实现。其中ByteBuffer通常可以用来传输音频,视频,图像等格式,CharBuffer可以传文本字符串

以IntBuffer为例

IntBuffer intBuffer = IntBuffer.allocate(5);

这样就构建了一个 Int类型的Buffer对象,大小为5,如果超出了5个,就会抛出 java.nio.BufferOverflowException

详细例子:

public static void main(String[] args) {
    IntBuffer intBuffer = IntBuffer.allocate(5);
    for (int i = 0; i < intBuffer.capacity(); ++i) {
    	intBuffer.put(i);
    }

    // 超出buffer容量,抛出异常 java.nio.BufferOverflowException
    // allocate.put(10);

    // 读写切换
    intBuffer.flip();

    while (intBuffer.hasRemaining()) {
    	System.out.println(intBuffer.get());
    }
}

需要注意,Buffer是半双工的,只能在同一时间读取或写入,不能同时读写,如果需要读写切换,请调用 flip 方法

Channel

继承关系如下,例举几个常用的:FileChannel(本地文件读写通道),SocketChannel(网络客户端读写通道),ServerSocketChannel(网络服务端通道)
image.png

可以直接通过Jdk原生的IO对象获取,比如 FileInputStream 对象调用 getChannel 方法获取 FileChannel

public static void main(String[] args) {
    String path = ChannelDemo.class.getClassLoader().getResource("in.txt").getPath();
    try {
        FileChannel channel = new FileInputStream(path).getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        channel.read(buffer);
        buffer.flip();
        System.out.println(new String(buffer.array(), 0, buffer.limit()));
    } catch (IOException e) {
        e.printStackTrace();
    }
}

读取resources目录的in.txt文件,获取Channel对象。创建一个Buffer,将数据读入Buffer中,然后打印在控制台上

调用Buffer的 flip 方法会将写入流转变成读取流,并将limit赋值为写入流最后的position位置(也就是最后写入字符的位置),不然的话会读取出一堆空字符串。

看flip源码

public final Buffer flip() {
    limit = position;	// 读取的时候只读到最后写入位置就行了
    position = 0;	// 读取的时候从第一个位置开始读
    mark = -1;
    return this;
}

BIO和NIO最主要的区别就是,BIO并没有通道和缓冲区的概念,只是单纯的将流中的数据读取到另一个写入流或者(字节数组/字符串中)

Selector

Selector中有个Set集合,用来存放所有与Channel绑定的SelectionKey
通过不断遍历这个Set,监听每个SelectionKey是否有事件发生
SelectionKey有4中事件类型

// 读事件
public static final int OP_READ = 1;
// 写事件
public static final int OP_WRITE = 4;
// 连接就绪,只有一次
public static final int OP_CONNECT = 8;
// 接收连接,包括OP_READ和OP_WRITE
public static final int OP_ACCEPT = 16;

注意Selector中的 select() 是阻塞的,它会一直等待直到有事件发生。
select(long timeout) 是非阻塞的,如果指定时间内没有接收到事件,则会退出。selectNow() 也是非阻塞的,他不会等待,如果没有事件,就直接退出。

# Jenkins # RabbitMQ # Zookeeper # IDEA # Logstash # Kibana # ELK # NIO # Netty # Spring Cloud # Golang # DataX # Elasticsearch # React Native # Mysql # H2 # Socket # Spring Boot # Kafka # Mybatis # Sqlmap # Vue # Postgresql # Docker # Vert.x # Flutter # Flink # Redis
Netty创建NioEventLoopGroup默认线程数量
Netty快速入门
  • 文章目录
  • 站点概览
会Coding的猴子

会Coding的猴子

57 日志
19 分类
28 标签
RSS
Github
Creative Commons
© 2021 会Coding的猴子
由 Halo 强力驱动
|
主题 - NexT.Gemini v5.1.4

湘ICP备18011740号