NIO初体验 ╥﹏╥

NIO的三个主要组成部分:Buffer(缓冲区)、Channel(通道)、Selector(选择器)

NIO与IO的区别

Java NIO是从java1.4版本开始引入的一个新的IO API,可以替代标准的Java IOAPI。NIO和IO有同样的作用和目的,但是使用的方式完全不同。
普通的IO是面向流的,系统一次一个字节地处理数据,一个输入流产生一个字节的数据,一个输出流消费一个字节的数据,面向流的I/O速度非常慢
NIO支持面向缓冲区的,基于通道的IO操作。NIO将以更加高效的方式进行文件的读写。

IO NIO
面向流 面向缓冲区
阻塞IO 非阻塞IO
选择器

阻塞和非阻塞,同步和异步的概念

举个例子,比如我们去照相馆拍照,拍完照片之后,商家说需要30分钟左右才能洗出来照片

  • 同步+阻塞(BIO)

    这个时候如果我们一直在店里面啥都不干,一直等待商家面前等待它洗完照片,这个过程就叫同步阻塞。

  • 同步+非阻塞(NIO)

    当然大部分人很少这么干,更多的是大家拿起手机开始看电视,看一会就会问老板洗完没,老板说没洗完,然后我们接着看,再过一会接着问(轮询),直到照片洗完,这个过程就叫同步非阻塞。

  • 异步+非阻塞(AIO)

    当然实际情况是,大家可能会直接先去逛街或者吃饭做其他的活动,同时等待老板打电话,这样以来两不耽误,这个过程就叫异步非阻塞。

总结
    从上面的描述中我们其实能够看到阻塞和非阻塞通常是指客户端在发出请求后,在服务端处理这个请求的过程中,客户端本身是否直接挂起等待结果(阻塞),还是继续做其他的任务(非阻塞)。
而异步和同步,则是对于请求结果的获取是客户端主动等待获取(同步),还是由服务端来通知消息结果(异步)。
    从这一点来看同步和阻塞其实描述的两个不同角度的事情,阻塞和非阻塞指的一个是客户端等待消息处理时的本身的状态,是挂起还是继续干别的。同步和异步指的对于消息结果的获取是客户端主动获取,还是由服务端间接推送。

	阻塞:等待结果

	非阻塞:可以做别的事情

	同步:主动获取结果

	异步:等待服务器通知结果

Buffer(缓冲区)

缓冲区概念
Java NIO中的Buffer主要用于与NIO通道(Channel)进行交互,数据是从通道读入缓冲区,从缓冲区写入通道中的。

Buffer就像一个数组,可以保存多个相同类型的数据。根据数据类型的不同,有以下Buffer常用子类:

  • ByteBuffer(常用)
  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

创建ByteBuffer对象

方式一:在堆中创建缓冲区:allocate(int capacity)

  • 在堆中创建缓冲区称为:间接缓冲区
public static void main(String[] args) {
	//创建堆缓冲区
	ByteBuffer byteBuffer = ByteBuffer.allocate(10);
}

方式二:在系统内存创建缓冲区:allocatDirect(int capacity)

  • 在系统内存创建缓冲区称为:直接缓冲区
public static void main(String[] args) {
	//创建直接缓冲区
	ByteBuffer byteBuffer = ByteBuffer.allocateDirect(10);
}

方式三:通过数组创建缓冲区:wrap(byte[] arr)

  • 在系统内存创建缓冲区称为:直接缓冲区
public static void main(String[] args) {
	byte[] byteArray = new byte[10];
	ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);
}
  • 间接缓冲区的创建和销毁效率要高于直接缓冲区
  • 间接缓冲区的创建和销毁效率要高于直接缓冲区

缓冲区的基本属性

  • 容量(capacity):Buffer所能够包含的元素的最大数量。定义了Buffer后,容量是不可变的。

  • 限制(limit):第一个不应该读取或写入元素的index索引。就是limit后的数据补课读写。缓冲区的限制(limit)不能为负,并且不能大于容量.

  • 位置(position):当前可写入的索引。位置不能小于0,并且不能大于"限制".

  • 标记(mark)与重置(reset):标记是一个索引,通过Buffer中的mark()方法指定Buffer中一个特定的position,之后可以通过reset()方法恢复到这个position。

在这里插入图片描述


Channel(通道)

一、概念
    通道(Channel):用于源节点和目标节点的连接。在Java nio中负责缓冲区中数据的传输。
Channel本身不存储数据,因此需要配合缓冲区进行传输。

二、通道的主要实现类
java.nio.channels.Channel 接口

  • FileChannel:从文件读取数据的
  • DatagramChannel:读写UDP网络协议数据
  • SocketChannel:读写TCP网络协议数据
  • ServerSocketChannel:可以监听TCP连接

三、获取通道
    1.Java针对支持通道的类提供了getChannel()方法
        本地IO:
            FileInputStream、FileOutputStream
            RandomAccessFile
        网络IO:
            Socket
            ServerSocket
            DatagramSocket
2、在JDK1.7中的NIO.2针对各个通道提供了静态方法open()
3、在JDK1.7中的NIO.2的File工具类的newByteChannel()


Selector(选择器)

多路复用的概念
选择器Selector是NIO中的重要技术之一。它与SelectableChannel联合使用实现了非阻塞的多路复用。使用它可以节省CPU资源,提高程序的运行效率。
"多路"是指:服务器端同时监听多个“端口”的情况。每个端口都要监听多个客户端的连接。服务器端的非多路复用效果
如果不使用“多路复用”,服务器端需要开很多线程处理每个端口的请求。如果在高并发环境下,造成系统性能下降。
服务器端的多路复用效果使用了多路复用,只需要一个线程就可以处理多个通道,降低内存占用率,减少CPU切换时间,在高并发、高频段业务环境下有非常重要的优势

选择器Selector
Selector被称为:选择器,也被称为:多路复用器,它可以注册到很多个Channel上,监听各Channel上发生的事件,并且能够根据事件情况决定Channel读写。这样,通过一个线程管理多个Channel,就可以处理大量网络连接了。
有了Selector,我们就可以利用一个线程来处理所有的Channels。线程之间的切换对操作系统来说代价是很高的,并且每个线程也会占用一定的系统资源。所以,对系统来说使用的线程越少越好。

如何创建一个Selector
在这里插入图片描述
register()方法的第二个参数:是一个int值,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听
四种不同类型的事件,而且可以使用SelectionKey的四个常量表示:

  1. 连接就绪–常量:SelectionKey.OP_CONNECT
  2. 接收就绪–常量:SelectionKey.OP_ACCEPT (ServerSocketChannel在注册时只能使用此项)
  3. 读就绪–常量:SelectionKey.OP_READ
  4. 写就绪–常量:SelectionKey.OP_WRITE
    注意:对于ServerSocketChannel在注册时,只能使用OP_ACCEPT,否则抛出异常。

示例:下面的例子,服务器创建3个通道,同时监听3个端口,并将3个通道注册到一个选择器中

import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
public class Server {
    public static void main(String[] args) throws Exception {
		//创建3个通道,同时监听3个端口
        ServerSocketChannel channelA = ServerSocketChannel.open();
        channelA.configureBlocking(false);
        channelA.bind(new InetSocketAddress(7777));
        ServerSocketChannel channelB = ServerSocketChannel.open();
        channelB.configureBlocking(false);
        channelB.bind(new InetSocketAddress(8888));
        ServerSocketChannel channelC = ServerSocketChannel.open();
        channelC.configureBlocking(false);
        channelC.bind(new InetSocketAddress(9999));
		//获取选择器
        Selector selector = Selector.open();
		//注册三个通道
        channelA.register(selector, SelectionKey.OP_ACCEPT);
        channelB.register(selector, SelectionKey.OP_ACCEPT);
        channelC.register(selector, SelectionKey.OP_ACCEPT);
    }
}

接下来,就可以通过选择器selector操作三个通道了。

在这里插入图片描述

发布了30 篇原创文章 · 获赞 39 · 访问量 2050

猜你喜欢

转载自blog.csdn.net/weixin_44564242/article/details/105250554