java jdk10 文件多大_JDK 10 都发布了,NIO 你了解多少?

02

NIO快速入门

首先我们来看看IO和NIO的区别:

bceb6621e613cffe8e315f896d281f65.png

可简单认为:IO是面向流的处理,NIO是面向块(缓冲区)的处理

面向流的I/O 系统一次一个字节地处理数据。

一个面向块(缓冲区)的I/O系统以块的形式处理数据。

NIO主要有三个核心部分组成:

buffer缓冲区

Channel管道

Selector选择器

2.1 buffer缓冲区和Channel管道

在NIO中并不是以流的方式来处理数据的,而是以buffer缓冲区和Channel管道配合使用来处理数据。

简单理解一下:

Channel管道比作成铁路,buffer缓冲区比作成火车(运载着货物)

而我们的NIO就是通过Channel管道运输着存储数据的Buffer缓冲区的来实现数据的处理!

要时刻记住:Channel不与数据打交道,它只负责运输数据。与数据打交道的是Buffer缓冲区

Channel-->运输

Buffer-->数据

相对于传统IO而言,流是单向的。对于NIO而言,有了Channel管道这个概念,我们的读写都是双向的(铁路上的火车能从广州去北京、自然就能从北京返还到广州)!

2.1.1 buffer缓冲区核心要点

我们来看看Buffer缓冲区有什么值得我们注意的地方。

Buffer是缓冲区的抽象类:

1ee223326bc989fb2eb4c89e7c2d3c72.png

其中ByteBuffer是用得最多的实现类(在管道中读写字节数据)。

35d193872e83d45847fd5311913880ba.png

拿到一个缓冲区我们往往会做什么?很简单,就是读取缓冲区的数据/写数据到缓冲区中。所以,缓冲区的核心方法就是:

put()

get()

ff03570f882384b1202a09e2983dd42c.png

b8e5df68de14390e3a5cba2b15df5373.png

Buffer类维护了4个核心变量属性来提供关于其所包含的数组的信息。它们是:

容量Capacity

缓冲区能够容纳的数据元素的最大数量。容量在缓冲区创建时被设定,并且永远不能被改变。(不能被改变的原因也很简单,底层是数组嘛)

上界Limit

缓冲区里的数据的总数,代表了当前缓冲区中一共有多少数据。

位置Position

下一个要被读或写的元素的位置。Position会自动由相应的 get( )和 put( )函数更新。

标记Mark

一个备忘位置。用于记录上一次读写的位置。

72b5415a78d053630c44b824da7d2509.png

2.1.2 buffer 代码演示

首先展示一下是如何创建缓冲区的,核心变量的值是怎么变化的。

009b35253e117168698347d4bd5b6619.png

运行结果:

2f2cfedf4eba80f0f748ed79088577c8.png

现在我想要从缓存区拿数据,怎么拿呀??NIO给了我们一个flip()方法。这个方法可以改动position和limit的位置!

还是上面的代码,我们flip()一下后,再看看4个核心属性的值会发生什么变化:

035cd9a57061e142b3b56e95a44766fb.png

很明显的是:

limit变成了position的位置了

而position变成了0

看到这里的同学可能就会想到了:当调用完filp()时:limit是限制读到哪里,而position是从哪里读

一般我们称filp()为**“切换成读模式”**

每当要从缓存区的时候读取数据时,就调用filp()“切换成读模式”。

9a321d92ff97ede021e961311b2151f9.png

切换成读模式之后,我们就可以读取缓冲区的数据了:

d9ac396cea9a0060a85746360a8490f2.png

随后输出一下核心变量的值看看:

bfcf18e4a9d691a01a820c225a9c7244.png

读完我们还想写数据到缓冲区,那就使用clear()函数,这个函数会“清空”缓冲区:

数据没有真正被清空,只是被遗忘掉了

e101dc85cfd7d6c68dd3ba01f91e985d.png

2.1.3 FileChannel 通道核心要点

c0e3be8190de7ab2248b0e8e64554dcf.png

Channel通道只负责传输数据、不直接操作数据的。操作数据都是通过Buffer缓冲区来进行操作!

// 1. 通过本地IO的方式来获取通道

FileInputStream fileInputStream = new FileInputStream("F:3yBlogJavaEE常用框架Elasticsearch就是这么简单.md");

// 得到文件的输入通道

FileChannel inchannel = fileInputStream.getChannel();

// 2. jdk1.7后通过静态方法.open()获取通道

FileChannel.open(Paths.get("F:3yBlogJavaEE常用框架Elasticsearch就是这么简单2.md"), StandardOpenOption.WRITE);

使用FileChannel配合缓冲区实现文件复制的功能:

3b8d9593243b86b44de71a344a271503.png

使用内存映射文件的方式实现文件复制的功能(直接操作缓冲区):

59375214961bc31d3fb4f2aab061cc2d.png

通道之间通过transfer()实现数据的传输(直接操作缓冲区):

ceb39d298736b807e77176a3712870c6.png

2.1.4 直接与非直接缓冲区

非直接缓冲区是需要经过一个:copy的阶段的(从内核空间copy到用户空间)

直接缓冲区不需要经过copy阶段,也可以理解成--->内存映射文件,(上面的图片也有过例子)。

b062a21adaf258304422e5aca1687a4b.png

6393e50a1c193379c0fb7d4300aa87fb.png

使用直接缓冲区有两种方式:

缓冲区创建的时候分配的是直接缓冲区

在FileChannel上调用map()方法,将文件直接映射到内存中创建

860553663bfe20fd4a8be1e80fc104a4.png

2.1.5 scatter和gather、字符集

这个知识点我感觉用得挺少的,不过很多教程都有说这个知识点,我也拿过来说说吧:

分散读取(scatter):将一个通道中的数据分散读取到多个缓冲区中

聚集写入(gather):将多个缓冲区中的数据集中写入到一个通道中

0b9cbefeb3fa5f8897cabcb7146530c2.png

db216ba345790ba4433b41d55a47628c.png

分散读取

065c46e5366a7ec3661472413f86315a.png

聚集写入

b650f09f44234df013de05b4d891d278.png

字符集(只要编码格式和解码格式一致,就没问题了)

25440efec5e2564a9556348950f1d852.png

03

IO模型理解

文件的IO就告一段落了,我们来学习网络中的IO~~~为了更好地理解NIO,我们先来学习一下IO的模型~

根据UNIX网络编程对I/O模型的分类,在UNIX可以归纳成5种I/O模型:

阻塞I/O

非阻塞I/O

I/O多路复用

信号驱动I/O

异步I/O

3.0 学习I/O模型需要的基础

3.0.1 文件描述符

Linux 的内核将所有外部设备都看做一个文件来操作,对一个文件的读写操作会调用内核提供的系统命令(api),返回一个file deor(fd,文件描述符)。而对一个socket的读写也会有响应的描述符,称为socket fd(socket文件描述符),描述符就是一个数字,指向内核中的一个结构体(文件路径,数据区等一些属性)。

所以说:在Linux下对文件的操作是利用文件描述符(file deor)来实现的。3.0.2 用户空间和内核空间

为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分

一部分为内核空间。

一部分为用户空间。3.0.3 I/O运行过程

我们来看看IO在系统中的运行是怎么样的(我们以read为例)

a6407bb3ee889ecc88332fcc1a6338bf.png

可以发现的是:当应用程序调用read方法时,是需要等待的--->从内核空间中找数据,再将内核空间的数据拷贝到用户空间的。

这个等待是必要的过程!

下面只讲解用得最多的3个I/0模型:

阻塞I/O

非阻塞I/O

I/O多路复用

3.1 阻塞I/O模型

在进程(用户)空间中调用recvfrom,其系统调用直到数据包到达且被复制到应用进程的缓冲区中或者发生错误时才返回,在此期间一直等待。

5e7b930bbb61c3101afaa28a485b234b.png

3.2 非阻塞I/O模型

recvfrom从应用层到内核的时候,如果没有数据就直接返回一个EWOULDBLOCK错误,一般都对非阻塞I/O模型进行轮询检查这个状态,看内核是不是有数据到来。

6616860b2f1f35e947209338913dc942.png

3.3 I/O复用模型

前面也已经说了:在Linux下对文件的操作是利用文件描述符(file deor)来实现的。

在Linux下它是这样子实现I/O复用模型的:

调用select/poll/epoll/pselect其中一个函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。

比如poll()函数是这样子的:int poll(struct pollfd *fds,nfds_t nfds, int timeout);

其中 pollfd 结构定义如下:

70a6cd930495d99cdc57c6952bbb520c.png

4f51b1bc166ea0daa2c327319e827aca.png

d10d5d3a5f79d085f9d9856cad35e242.png

(1)当用户进程调用了select,那么整个进程会被block;

(2)而同时,kernel会“监视”所有select负责的socket;

(3)当任何一个socket中的数据准备好了,select就会返回;

(4)这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程(空间)。

所以,I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符其中的任意一个进入读就绪状态,select()函数就可以返回。

select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。

3.4 I/O模型总结

正经的描述都在上面给出了,不知道大家理解了没有。下面我举几个例子总结一下这三种模型:

阻塞I/O:

Java3y跟女朋友去买喜茶,排了很久的队终于可以点饮料了。我要绿研,谢谢。可是喜茶不是点了单就能立即拿,于是我在喜茶门口等了一小时才拿到绿研。

在门口干等一小时

非阻塞I/O:

Java3y跟女朋友去买一点点,排了很久的队终于可以点饮料了。我要波霸奶茶,谢谢。可是一点点不是点了单就能立即拿,同时服务员告诉我:你大概要等半小时哦。你们先去逛逛吧~于是Java3y跟女朋友去玩了几把斗地主,感觉时间差不多了。于是又去一点点问:请问到我了吗?我的单号是xxx。服务员告诉Java3y:还没到呢,现在的单号是XXX,你还要等一会,可以去附近耍耍。问了好几次后,终于拿到我的波霸奶茶了。

去逛了下街、斗了下地主,时不时问问到我了没有

I/O复用模型:

Java3y跟女朋友去麦当劳吃汉堡包,现在就厉害了可以使用微信小程序点餐了。于是跟女朋友找了个地方坐下就用小程序点餐了。点餐了之后玩玩斗地主、聊聊天什么的。时不时听到广播在复述XXX请取餐,反正我的单号还没到,就继续玩呗。~~等听到广播的时候再取餐就是了。时间过得挺快的,此时传来:Java3y请过来取餐。于是我就能拿到我的麦辣鸡翅汉堡了。

听广播取餐,广播不是为我一个人服务。广播喊到我了,我过去取就Ok了。04

使用NIO完成网络通信

4.1 NIO基础继续讲解

回到我们最开始的图:

3dcb5564806c4781e449610296cd7de3.png

NIO被叫为 no-blocking io,其实是在网络这个层次中理解的,对于FileChannel来说一样是阻塞。

我们前面也仅仅讲解了FileChannel,对于我们网络通信是还有几个Channel的~

e2a4b60f9dc179da5c6bdc090c72f150.png

所以说:我们通常使用NIO是在网络中使用的,网上大部分讨论NIO都是在网络通信的基础之上的!说NIO是非阻塞的NIO也是网络中体现的!

从上面的图我们可以发现还有一个Selector选择器这么一个东东。从一开始我们就说过了,nio的核心要素有:

Buffer缓冲区

Channel通道

Selector选择器

我们在网络中使用NIO往往是I/O模型的多路复用模型!

Selector选择器就可以比喻成麦当劳的广播。

一个线程能够管理多个Channel的状态

4ddc6e1fecadbd66e3df829d79e64a35.png

4.2 NIO阻塞形态

为了更好地理解,我们先来写一下NIO在网络中是阻塞的状态代码,随后看看非阻塞是怎么写的就更容易理解了。

是阻塞的就没有Selector选择器了,就直接使用Channel和Buffer就完事了。

客户端:

public class BlockClient {

public static void main(String[] args) throws IOException {

// 1. 获取通道

SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 6666));

// 2. 发送一张图片给服务端吧

FileChannel fileChannel = FileChannel.open(Paths.get("X:UsersozcDesktop新建文件夹1.png"), StandardOpenOption.READ);

// 3.要使用NIO,有了Channel,就必然要有Buffer,Buffer是与数据打交道的呢

ByteBuffer buffer = ByteBuffer.allocate(1024);

// 4.读取本地文件(图片),发送到服务器

while (fileChannel.read(buffer) != -1) {

// 在读之前都要切换成读模式

buffer.flip();

socketChannel.write(buffer);

// 读完切换成写模式,能让管道继续读取文件的数据

buffer.clear();

}

// 5. 关闭流

fileChannel.close();

socketChannel.close();

}

}

服务端:

public class BlockServer {

public static void main(String[] args) throws IOException {

// 1.获取通道

ServerSocketChannel server = ServerSocketChannel.open();

// 2.得到文件通道,将客户端传递过来的图片写到本地项目下(写模式、没有则创建)

FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);

// 3. 绑定链接

server.bind(new InetSocketAddress(6666));

// 4. 获取客户端的连接(阻塞的)

SocketChannel client = server.accept();

// 5. 要使用NIO,有了Channel,就必然要有Buffer,Buffer是与数据打交道的呢

ByteBuffer buffer = ByteBuffer.allocate(1024);

// 6.将客户端传递过来的图片保存在本地中

while (client.read(buffer) != -1) {

// 在读之前都要切换成读模式

buffer.flip();

outChannel.write(buffer);

// 读完切换成写模式,能让管道继续读取文件的数据

buffer.clear();

}

// 7.关闭通道

outChannel.close();

client.close();

server.close();

}

}

结果就可以将客户端传递过来的图片保存在本地了:

此时服务端保存完图片想要告诉客户端已经收到图片啦:

41da90b1a702adf061c2222a6c04487e.png

客户端接收服务端带过来的数据:

a1e985dd17f98d155f81137cdc267d6c.png

如果仅仅是上面的代码是不行的!这个程序会阻塞起来!

因为服务端不知道客户端还有没有数据要发过来(与刚开始不一样,客户端发完数据就将流关闭了,服务端可以知道客户端没数据发过来了),导致服务端一直在读取客户端发过来的数据。

进而导致了阻塞!

于是客户端在写完数据给服务端时,显式告诉服务端已经发完数据了!

18fea99559153a62d86b0c80a63eb8c0.png

4.3 NIO非阻塞形态

如果使用非阻塞模式的话,那么我们就可以不显式告诉服务器已经发完数据了。我们下面来看看怎么写:

客户端:

public class NoBlockClient {

public static void main(String[] args) throws IOException {

// 1. 获取通道

SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 6666));

// 1.1切换成非阻塞模式

socketChannel.configureBlocking(false);

// 2. 发送一张图片给服务端吧

FileChannel fileChannel = FileChannel.open(Paths.get("X:UsersozcDesktop新建文件夹1.png"), StandardOpenOption.READ);

// 3.要使用NIO,有了Channel,就必然要有Buffer,Buffer是与数据打交道的呢

ByteBuffer buffer = ByteBuffer.allocate(1024);

// 4.读取本地文件(图片),发送到服务器

while (fileChannel.read(buffer) != -1) {

// 在读之前都要切换成读模式

buffer.flip();

socketChannel.write(buffer);

// 读完切换成写模式,能让管道继续读取文件的数据

buffer.clear();

}

// 5. 关闭流

fileChannel.close();

socketChannel.close();

}

}

服务端:

b010df9e345336b9de8c40902471ac27.png

3e84c06002e9db9fb962aa1efabf0957.png

还是刚才的需求:服务端保存了图片以后,告诉客户端已经收到图片了。

在服务端上只要在后面写些数据给客户端就好了:

7ffb00152f7ab1ca33900d871cb1c18a.png

在客户端上要想获取得到服务端的数据,也需要注册在register上(监听读事件)!

cb887614fad71e55fd766f7e9c9bdb02.png

a497840582ea14c869e26a99597453c5.png

测试结果:

3ef824a16ecf7694ffe59afccea68e00.png

下面就简单总结一下使用NIO时的要点:

将Socket通道注册到Selector中,监听感兴趣的事件

当感兴趣的时间就绪时,则会进去我们处理的方法进行处理

每处理完一次就绪事件,删除该选择键(因为我们已经处理完了)

4.4 管道和 DataGramChannel

这里我就不再讲述了,最难的TCP都讲了,UDP就很简单了。

UDP:

5af58cbe4e090365ea6cf323b999fbfd.png

1e04666ee2b8495b9f0d0ee5fe6457e4.png

管道:

0d230689bf7b996ce0a3f1a6496bd892.png

2ebcfb03470b05703f0c7470a5e4b15b.png

05

总结

总的来说NIO也是一个比较重要的知识点,因为它是学习netty的基础~

想以一篇来完全讲解NIO显然是不可能的啦,想要更加深入了解NIO可以点击阅读原文继续学习~返回搜狐,查看更多

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值