java NIO几个重要的概念

java NIO几个重要的概念

在当下比较流行的分布式系统(中间件或者计算框架)中,底层高并发的基础实现都用到netty,netty和mina很类似,但是netty比mina稳定,虽然效率没有mina高,相对来说Netty的社区发展比较活跃,而且有丰富的文档供大家学习,所以netty发展比mina快很多。java NIO是Netty高并发的基础。在前面java IO小节中粗略的讲了一下什么是NIO,本篇将重点讲解几个NIO中几个重要的概念,缓冲区Buffer,通道channel和多路复用器selector。

缓冲区Buffer

在NIO库中,的Buffer用于和NIO通道进行交互,缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。java.nio.Buffer下有七个子类,这些子类缓冲区分别用于存储不同类型的数据。分别是:ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer ,都是针对基本数据类型,没有针对布尔型的。在实际中常用到的buffer函数如下几个:
所有缓冲区都有4个属性:capacity、limit、position、mark,并遵循:capacity>=limit>=position>=mark>=0,下表格是对着4个属性的解释:

属性描述
Capacity容量,即可以容纳的最大数据量;在缓冲区创建时被设定并且不能改变
Limit上界,缓冲区中当前数据量
Position位置,下一个要被读或写的元素的索引
Mark标记,调用mark()来设置mark=position,再调用reset()可以让position恢复到标记的位置即position=mark

使用Buffer读写数据一般遵循以下四个步骤:

  1. 写入数据到Buffer
  2. 调用flip()方法
  3. 从Buffer中读取数据
  4. 调用clear()方法或者compact()方法

当向buffer写入数据时,buffer会记录下写了多少数据。一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式。在读模式下,可以读取之前写入到buffer的所有数据。

一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用clear()或compact()方法。clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。

 SocketChannel sc = (SocketChannel)key.channel();
 ByteBuffer readbuffer = ByteBuffer.allocate(1024);
                try {
                    int readBytes = sc.read(readbuffer);
                    if(readBytes>0){
                        readbuffer.flip();
                        byte[] bytes = new byte[readbuffer.remaining()];
                        readbuffer.get(bytes);
                        String body = new String(bytes,"UTF-8");
                        System.out.println("the time server receive order :"+body);
                        String current = "QUERY TIME ORDER".equalsIgnoreCase(body)?new java.util.Date(System.currentTimeMillis()).toString():"BAD ORDER";
                        doWrite(sc,current);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }

以上是服务器端从buffer中获取数据的代码段,首先申请一个大小为1M的ByteBuffer,flip()重置当前的position为0;


Channel通道

Channel是一个通道,网络数据通过Channel读取和写入。通道是全双工的,所以比流更好的映射底层操作系统API。
Java NIO的通道类似流,但又有些不同:
既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。
通道可以异步地读写。
通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。

从通道中读取数据和写入数据流程如下图

这里写图片描述

正如上面所说,从通道读取数据到缓冲区,从缓冲区写入数据到通道。如下图所示:
这些是Java NIO中最重要的通道的实现:
FileChannel
DatagramChannel
SocketChannel
ServerSocketChannel
FileChannel 从文件中读写数据。
DatagramChannel 能通过UDP读写网络中的数据。
SocketChannel 能通过TCP读写网络中的数据。
ServerSocketChannel可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。
基本的Channel用例如下:

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();

ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {

System.out.println("Read " + bytesRead);
buf.flip();

while(buf.hasRemaining()){
System.out.print((char) buf.get());
}

buf.clear();
bytesRead = inChannel.read(buf);
}
aFile.close();

buf.flip() 的调用,首先读取数据到Buffer,然后反转Buffer,接着再从Buffer中读取数据。

selector多路复用器

Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。
仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通道。事实上,可以只用一个线程处理所有的通道。对于操作系统来说,线程之间上下文切换的开销很大,而且每个线程都要占用系统的一些资源(如内存)。因此,使用的线程越少越好。

在使用selector中通常包括一下几个步骤:
1、Selector的创建
通过调用Selector.open()方法创建一个Selector

Selector selector = Selector.open();

2、向Selector注册后通道
将Channel和Selector配合使用,必须将channel注册到selector上。通过SelectableChannel.register()方法来实现

channel.configureBlocking(false);
SelectionKey key = channel.register(selector,
    Selectionkey.OP_READ);

与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。
四种不同类型的事件:
Connect
Accept
Read
Write
通道触发了一个事件意思是该事件已经就绪。所以,某个channel成功连接到另一个服务器称为“连接就绪”。一个server socket channel准备好接收新进入的连接称为“接收就绪”。一个有数据可读的通道可以说是“读就绪”。等待写数据的通道可以说是“写就绪”。
3、实例代码

  while(!stop){
            try {
                selector.select(1000);
                Set<SelectionKey> selectKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectKeys.iterator();
                SelectionKey key = null;
                while (it.hasNext()){
                    key = it.next();
                    it.remove();
                    handleInput(key);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

在程序中都是通过循环不断的轮询哈找注册在其上的Channel,如果某个Channel发生了读或者写的事件,那么这个Channel就会处于就绪状态,会被selector轮询出来,然后通过SelectionKey可以获取就绪的Channel集合,进行后续的IO操作。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值