title | shortTitle | category | tag | description | head | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Java NIO 快速入门(buffer缓冲区、Channel管道、Selector选择器) |
Java NIO快速入门 |
|
|
Java程序员进阶之路,小白的零基础Java教程,Java NIO 快速入门(buffer缓冲区、Channel管道、Selector选择器) |
|
首先我们来看看IO和NIO的区别:
-
可简单认为:IO是面向流的处理,NIO是面向块(缓冲区)的处理
-
面向流的I/O 系统一次一个字节地处理数据。
-
一个面向块(缓冲区)的I/O系统以块的形式处理数据。
NIO主要有三个核心部分组成:
- buffer缓冲区
- Channel管道
- Selector选择器
在NIO中并不是以流的方式来处理数据的,而是以buffer缓冲区和Channel管道配合使用来处理数据。
简单理解一下:
- Channel管道比作成铁路,buffer缓冲区比作成火车(运载着货物)
而我们的NIO就是通过Channel管道运输着存储数据的Buffer缓冲区的来实现数据的处理!
-
要时刻记住:Channel不与数据打交道,它只负责运输数据。与数据打交道的是Buffer缓冲区
-
Channel-->运输
-
Buffer-->数据
相对于传统IO而言,流是单向的。对于NIO而言,有了Channel管道这个概念,我们的读写都是双向的(铁路上的火车能从广州去北京、自然就能从北京返还到广州)!
我们来看看Buffer缓冲区有什么值得我们注意的地方。
Buffer是缓冲区的抽象类:
其中ByteBuffer是用得最多的实现类(在管道中读写字节数据)。
拿到一个缓冲区我们往往会做什么?很简单,就是读取缓冲区的数据/写数据到缓冲区中。所以,缓冲区的核心方法就是:
- put()
- get()
Buffer类维护了4个核心变量属性来提供关于其所包含的数组的信息。它们是:
-
容量Capacity
-
缓冲区能够容纳的数据元素的最大数量。容量在缓冲区创建时被设定,并且永远不能被改变。(不能被改变的原因也很简单,底层是数组嘛)
-
上界Limit
-
缓冲区里的数据的总数,代表了当前缓冲区中一共有多少数据。
-
位置Position
-
下一个要被读或写的元素的位置。Position会自动由相应的
get( )
和put( )
函数更新。 -
标记Mark
-
一个备忘位置。用于记录上一次读写的位置。
首先展示一下是如何创建缓冲区的,核心变量的值是怎么变化的。
public static void main(String[] args) {
// 创建一个缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 看一下初始时4个核心变量的值
System.out.println("初始时-->limit--->"+byteBuffer.limit());
System.out.println("初始时-->position--->"+byteBuffer.position());
System.out.println("初始时-->capacity--->"+byteBuffer.capacity());
System.out.println("初始时-->mark--->" + byteBuffer.mark());
System.out.println("--------------------------------------");
// 添加一些数据到缓冲区中
String s = "沉默王二";
byteBuffer.put(s.getBytes());
// 看一下初始时4个核心变量的值
System.out.println("put完之后-->limit--->"+byteBuffer.limit());
System.out.println("put完之后-->position--->"+byteBuffer.position());
System.out.println("put完之后-->capacity--->"+byteBuffer.capacity());
System.out.println("put完之后-->mark--->" + byteBuffer.mark());
}
运行结果:
现在我想要从缓存区拿数据,怎么拿呀??NIO给了我们一个flip()
方法。这个方法可以改动position和limit的位置!
还是上面的代码,我们flip()
一下后,再看看4个核心属性的值会发生什么变化:
很明显的是:
- limit变成了position的位置了
- 而position变成了0
看到这里的同学可能就会想到了:当调用完filp()
时:limit是限制读到哪里,而position是从哪里读
一般我们称filp()
为**“切换成读模式”**
- 每当要从缓存区的时候读取数据时,就调用
filp()
“切换成读模式”。
切换成读模式之后,我们就可以读取缓冲区的数据了:
// 创建一个limit()大小的字节数组(因为就只有limit这么多个数据可读)
byte[] bytes = new byte[byteBuffer.limit()];
// 将读取的数据装进我们的字节数组中
byteBuffer.get(bytes);
// 输出数据
System.out.println(new String(bytes, 0, bytes.length));
随后输出一下核心变量的值看看:
读完我们还想写数据到缓冲区,那就使用clear()
函数,这个函数会“清空”缓冲区:
- 数据没有真正被清空,只是被遗忘掉了
Channel通道只负责传输数据、不直接操作数据的。操作数据都是通过Buffer缓冲区来进行操作!
// 1. 通过本地IO的方式来获取通道
FileInputStream fileInputStream = new FileInputStream("F:\\3yBlog\\JavaEE常用框架\\Elasticsearch就是这么简单.md");
// 得到文件的输入通道
FileChannel inchannel = fileInputStream.getChannel();
// 2. jdk1.7后通过静态方法.open()获取通道
FileChannel.open(Paths.get("F:\\3yBlog\\JavaEE常用框架\\Elasticsearch就是这么简单2.md"), StandardOpenOption.WRITE);
使用FileChannel配合缓冲区实现文件复制的功能:
使用内存映射文件的方式实现文件复制的功能(直接操作缓冲区):
通道之间通过transfer()
实现数据的传输(直接操作缓冲区):
- 非直接缓冲区是需要经过一个:copy的阶段的(从内核空间copy到用户空间)
- 直接缓冲区不需要经过copy阶段,也可以理解成--->内存映射文件,(上面的图片也有过例子)。
使用直接缓冲区有两种方式:
- 缓冲区创建的时候分配的是直接缓冲区
- 在FileChannel上调用
map()
方法,将文件直接映射到内存中创建
这个知识点我感觉用得挺少的,不过很多教程都有说这个知识点,我也拿过来说说吧:
- 分散读取(scatter):将一个通道中的数据分散读取到多个缓冲区中
- 聚集写入(gather):将多个缓冲区中的数据集中写入到一个通道中
分散读取
聚集写入
字符集(只要编码格式和解码格式一致,就没问题了)
参考链接:https://www.zhihu.com/question/29005375/answer/667616386,整理:沉默王二
最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关 等等等等……详情戳:可以说是2022年全网最全的学习和找工作的PDF资源了
微信搜 沉默王二 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 111 即可免费领取。