资讯专栏INFORMATION COLUMN

Java IO之NIO

pingink / 857人阅读

摘要:上篇说了最基础的五种模型,相信大家对相关的概念应该有了一定的了解,这篇文章主要讲讲基于多路复用的。

上篇说了最基础的五种IO模型,相信大家对IO相关的概念应该有了一定的了解,这篇文章主要讲讲基于多路复用IO的Java NIO。

背景

Java诞生至今,有好多种IO模型,从最早的Java IO到后来的Java NIO以及最新的Java AIO,每种IO模型都有它自己的特点,详情请看我的上篇文章[Java IO初探](),而其中的的Java NIO应用非常广泛,尤其是在高并发领域,比如我们常见的Netty,Mina等框架,都是基于它实现的,相信大家都有所了解,下面让我们来看看Java NIO的具体架构。

Java NIO架构

其实Java NIO模型相对来说也还是比较简单的,它的核心主要有三个,分别是:Selector、Channel和Buffer,我们先来看看它们之间的关系:

它们之间的关系很清晰,一个线程对应着一个Selector,一个Selector对应着多个Channel,一个Channel对应着一个Buffer,当然这只是通常的做法,一个Channel也可以对应多个Selector,一个Channel对应着多个Buffer。

Selector

个人认为Selector是Java NIO的最大特点,之前我们说过,传统的Java IO在面对大量IO请求的时候有心无力,因为每个维护每一个IO请求都需要一个线程,这带来的问题就是,系统资源被极度消耗,吞吐量直线下降,引起系统相关问题,那么Java NIO是如何解决这个问题的呢?答案就是Selector,简单来说它对应着多路IO复用中的监管角色,它负责统一管理IO请求,监听相应的IO事件,并通知对应的线程进行处理,这种模式下就无需为每个IO请求多带带分配一个线程,另外也减少线程大量阻塞,资源利用率下降的情况,所以说Selector是Java NIO的精髓,在Java中我们可以这么写:

// 打开服务器套接字通道
ServerSocketChannel ssc = ServerSocketChannel.open();
// 服务器配置为非阻塞
ssc.configureBlocking(false);
// 进行服务的绑定
ssc.bind(new InetSocketAddress("localhost", 8001));

// 通过open()方法找到Selector
Selector selector = Selector.open();
// 注册到selector,等待连接
ssc.register(selector, SelectionKey.OP_ACCEPT);
...
Channel

Channel本意是通道的意思,简单来说,它在Java NIO中表现的就是一个数据通道,但是这个通道有一个特点,那就是它是双向的,也就是说,我们可以从通道里接收数据,也可以向通道里写数据,不用像Java BIO那样,读数据和写数据需要不同的数据通道,比如最常见的Inputstream和Outputstream,但是它们都是单向的,Channel作为一种全新的设计,它帮助系统以相对小的代价来保持IO请求数据传输的处理,但是它并不真正存放数据,它总是结合着缓存区(Buffer)一起使用,另外Channel主要有以下四种:

FileChannel:读写文件时使用的通道

DatagramChannel:传输UDP连接数据时的通道,与Java IO中的DatagramSocket对应

SocketChannel:传输TCP连接数据时的通道,与Java IO中的Socket对应

ServerSocketChannel: 监听套接词连接时的通道,与Java IO中的ServerSocket对应

当然其中最重要以及最常用的就是SocketChannel和ServerSocketChannel,也是Java NIO的精髓,ServerSocketChannel可以设置成非阻塞模式,然后结合Selector就可以实现多路复用IO,使用一个线程管理多个Socket连接,具体使用可以参数上面的代码。

Buffer

顾名思义,Buffer的含义是缓冲区,它在Java NIO中的主要作用就是作为数据的缓冲区域,Buffer对应着某一个Channel,从Channel中读取数据或者向Channel中写数据,Buffer与数组很类似,但是它提供了更多的特性,方便我们对Buffer中的数据进行操作,后面我也会主要分析它的三个属性capacity,position和limit,我们先来看一下Buffer分配时的类别(这里不是指Buffer的具体数据类型)即Direct Buffer和Heap Buffer,那么为什么要有这两种类别的Buffer呢?我们先来看看它们的特性:

Direct Buffer:

直接分配在系统内存中;

不需要花费将数据库从内存拷贝到Java内存中的成本;

虽然Direct Buffer是直接分配中系统内存中的,但当它被重复利用时,只有真正需要数据的那一页数据会被装载到真是的内存中,其它的还存在在虚拟内存中,不会造成实际内存的资源浪费;

可以结合特定的机器码,一次可以有顺序的读取多字节单元;

因为直接分配在系统内存中,所以它不受Java GC管理,不会自动回收;

创建以及销毁的成本比较高;

Heap Buffer:

分配在Java Heap,受Java GC管理生命周期,不需要额外维护;

创建成本相对较低;

根据它们的特性,我们可以大致总结出它们的适用场景:

如果这个Buffer可以重复利用,而且你也想多个字节操作,亦或者你对性能要求很高,可以选择使用Direct Buffer,但其编码相对来说会比较复杂,需要注意的点也更多,反之则用Heap Buffer,Buffer的相应创建方法:

//创建Heap Buffer
ByteBuffer heapBuffer = ByteBuffer.allocate(1024);

//创建Direct Buffer
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);

下面我们来看看它的三个属性:

Capacity:顾名思义它的含义是容量,代表着Buffer的最大容量,与数组的Size很类似,初始化不可更改,除非你改变的Buffer的结构;

Limit:顾名思义它的含义是界限,代表着Buffer的目前可使用的最大限制,写模式下,一般Limit等于Capacity,读模式下需要你自己控制它的值结合position读取想要的数据;

Position:顾名思义它的含义是位置,代表着Buffer目前操作的位置,通俗来说,就是你下次对Buffer进行操作的起始位置;

接下来我会用一个图解的列子帮助大家理解,现在我们假设有一个容量为10的Buffer,我们先往里面写入一定字节的数据,然后再根据编码规则从其中读取我们需要的数据:

1.初始Buffer:

ByteBuffer buffer = ByteBuffer.allocate(10);

2.向Buffer中写入两个字节:

buffer.put("my".getBytes());

3.再Buffer中写入四个字节:

buffer.put("blog".getBytes());

4.现在我们需要从Buffer中获取数据,首先我们先将写模式转换为读模式:

  buffer.flip();

我们来看看flip()方法到底做了什么事?

public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}

从源码中可以看出,flip方法根据Buffer目前的相应属性来修改对应的属性,所以flip()方法之后,Buffer目前的状态:

5.接着我们从Buffer中读取数据

从Buffer中读取数据有多种方式,比如get(),get(byte [])等,相关的具体方法使用可以参考Buffer的官方API文档,这里我们用最简单的get()来获取数据:

  byte a = buffer.get();
  byte b = buffer.get();

此时Buffer的状态如下图所示:

我们可以按照这种方式读取完我们所需数据,最终调用clear()方法将Buffer置为初始状态。

总结

这篇文章主要讲解了Java NIO中重要的三个组成部分,在实际使用过程也是比较重要的,掌握它们之间的关系,可以让你对Java NIO的整个架构更加熟悉,理解相对来说也会更加深刻,并分析了这种模式是如何与多路复用IO模型的映射,了解Java NIO在高并发场景下优势的原因。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70632.html

相关文章

  • 关于Java IO与NIO知识都在这里

    摘要:从通道进行数据写入创建一个缓冲区,填充数据,并要求通道写入数据。三之通道主要内容通道介绍通常来说中的所有都是从通道开始的。从中选择选择器维护注册过的通道的集合,并且这种注册关系都被封装在当中停止选择的方法方法和方法。 由于内容比较多,我下面放的一部分是我更新在我的微信公众号上的链接,微信排版比较好看,更加利于阅读。每一篇文章下面我都把文章的主要内容给列出来了,便于大家学习与回顾。 Ja...

    Riddler 评论0 收藏0
  • Java NIO 概览

    摘要:线程之间的切换对于操作系统来说是昂贵的。因此,单线程可以监视多个通道中的数据。当方法返回后,线程可以处理这些事件。 一 NIO简介 Java NIO 是 java 1.4 之后新出的一套IO接口,这里的的新是相对于原有标准的Java IO和Java Networking接口。NIO提供了一种完全不同的操作方式。 NIO中的N可以理解为Non-blocking,不单纯是New。 它支持面...

    chemzqm 评论0 收藏0
  • 动力节点JavaNIO教程,轻松攻破Java NIO技术壁垒

    摘要:学习和掌握技术已经不是一个攻城狮的加分技能,而是一个必备技能。是双向的,不仅可以读取数据还能保存数据,程序不能直接读写通道,只与缓冲区交互为了让大家不被高并发与大量连接处理问题所困扰,动力节点推出了高效处理模型应用教程。 大家肯定了解Java IO, 但是对于NIO一般是陌生的,而现在使用到NIO的场景越来越多,很多技术框...

    ralap 评论0 收藏0
  • Netty 源码分析 一 揭开 Bootstrap 神秘的红盖头 (客户端)

    摘要:目录源码分析之番外篇的前生今世的前生今世之一简介的前生今世之二小结的前生今世之三详解的前生今世之四详解源码分析之零磨刀不误砍柴工源码分析环境搭建源码分析之一揭开神秘的红盖头源码分析之一揭开神秘的红盖头客户端源码分析之一揭开神秘的红盖头服务器 目录 Netty 源码分析之 番外篇 Java NIO 的前生今世 Java NIO 的前生今世 之一 简介 Java NIO 的前生今世 ...

    zhaot 评论0 收藏0
  • Java NIO Channel(通道)

    摘要:通道可以异步读写。使用的方法读取数据创建一个读数据缓冲区对象从通道中读取数据使用的方法写入数据创建一个写数据缓冲区对象写入数据关闭完成使用后,您必须关闭它。五提供了一种被称为的新功能,也称为本地矢量。功能是通道提供的并不是。 历史回顾: Java NIO 概览 Java NIO 之 Buffer(缓冲区) 其他高赞文章: 面试中关于Redis的问题看这篇就够了 一文轻松搞懂redis集...

    piglei 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<