Netty入门到超神系列-Java NIO 三大核心(selector,channel,buffer)

简介: 选择器,也叫多路复用器,Java的NIO通过selector实现一个线程处理多个客户端链接,多个channel可以注册到同一个Selector,Selector能够监测到channel上是否有读/写事件发生,从而获取事件和对事件进行处理,所以Selector切到哪个channel是由事件决定的。当线程从某个客户端通道未读取到数据时,可以把空闲时间用来做其他任务,性能得到了提升。

前言

上一章节我们认识了一下Java的三大IO,这一章节我们详细了解一下NIO的工作原理以及三大核心Selector,Channel,Buffer并尝试来做一些小案例。

Java NIO 模型

Java NIO有三个核心的组件: selector 选择器 , channel 通道 , buffer 缓冲区,模型如下:

Selector 多路复用器

选择器,也叫多路复用器,Java的NIO通过selector实现一个线程处理多个客户端链接,多个channel可以注册到同一个Selector,Selector能够监测到channel上是否有读/写事件发生,从而获取事件和对事件进行处理,所以Selector切到哪个channel是由事件决定的。当线程从某个客户端通道未读取到数据时,可以把空闲时间用来做其他任务,性能得到了提升。

Channel 通道

channel 通道是双向的,channel 可以往buffer写入数据,同时channel 也可以从buffer读取数据,它可以同时进行读写。它和BIO中的stream流很像,区别是stream流只能单向读或者写,而NIO总的channel可以双向读写,常用的Channel类有:

  • FileChannel:主要用于文件的IO操作
  • DatagramChannel:主要用于 UDP 的数据读写
  • ServerSocketChannel 和 SocketChannel:用于TCP数据读取

Buffer 缓冲区

buffer主要是和channel通道做数据交互,Channel 提供从文件或网络读取数据的渠道,但是数据读取到一个它稍后处理的buffer中,实现了IO的非阻塞。 每个channel通道都会对应一个buffer,buffer是一个内存块,底层有一个数组,NIO的buffer可以写如数据,也可以从中读取数据。在Java中封装了很多基于buffer的类,如:

  • ByteBuffer:存储字节数据到缓冲区
  • ShortBuffer:存储字符串数据到缓冲区
  • CharBuffer:存储字符数据到缓冲区
  • IntBuffer:存储整数数据到缓冲区
  • LongBuffer:存储长整型数据到缓冲区
  • FloatBuffer:存储小数到缓冲区
  • DoubleBuffer:存储小数到缓冲区
  • MappedByteBuffer:基于内存操作文件

Buffer 的理解和使用

buffer : 缓冲区,buffer主要是和channel通道做数据交互,可以把数据写入Buffer以及从Buffer读取数据,java.nio.Buffer源码,以及常用子类如下:

Buffer可以看做是有一个数组来存储元素,Buffer类提供了四个很重要的属性

  • capacity:Buffer所能够存放的最大容量,最多只能向 Buffer 写入 capacity 大小的字节
  • position:下一个被读或写的位置,随着不停的写入数据,position会向后移动,初始值是0,最大值是capacity - 1。
    当然在读数据的时候也需要知道读取的位置,当调用 flip 方法将 Buffer 从写模式转换为读模式时,position 被重新设置为 0 ,随着不停的读取,position会指向下个读取位置。
  • mark: 标记位置,用于记录某次读写的位置
  • limit: 对position的限制,在写模式下限制你能将多少数据写入Buffer中,limit等同于Buffer的容量(capacity)。
    当切换Buffer为读模式时,限制你最多能读取到多少数据。因此,当切换Buffer为读模式时,限制会被设置为写模式下的position值,即:你能读到之前写入的所有数据,限制被设置为已写的字节数,在写模式下就是position。

buffer使用数组存储元素,下面用 java.nio.ByteBuffer 来举例,源码如下:

publicabstractclassByteBufferextendsBufferimplementsComparable<ByteBuffer>{
// These fields are declared here rather than in Heap-X-Buffer in order to// reduce the number of virtual method invocations needed to access these// values, which is especially costly when coding small buffers.////存储数据的byte数组finalbyte[] hb;                  // Non-null only for heap buffersfinalintoffset;
booleanisReadOnly;                 // Valid only for heap buffers

这里看到了,ByteBuffer底层就是使用一个 final byte[] hb; 来存储元素,其他的Buffer是相同的道理。

创建容量为10字节的buffer: ByteBuffer byteBuffer = ByteBuffer.allocate(10);

写入三个字节的数据byteBuffer.put("aaa".getBytes());

调用读写转换方法 byteBuffer.flip();

读3个字节元素: byte[] bytes = new byte[4]; byteBuffer.get(bytes , 0 ,2);

Buffer API介绍

publicabstractclassBuffer {
//返回此缓冲区容量capacitypublicfinalintcapacity( )
//返回此缓冲区位置positionpublicfinalintposition( )
//设置缓冲区的位置position publicfinalBufferposition (intnewPositio)
//返回此缓冲区limitpublicfinalintlimit( )
//设置此缓冲区的限制limitpublicfinalBufferlimit (intnewLimit)
//在此缓冲区的位置设置标记publicfinalBuffermark( )
//将此缓冲区的位置重置为以前标记的位置//把position设置为markpublicfinalBufferreset()
//清除此缓冲区, 即将各个标记恢复到初始状态//把position设置为 0 ,limit = capacity;publicfinalBufferclear( )
//读写反转此缓冲区publicfinalBufferflip( )
//重置此缓冲区,position = 0;   mark = -1;publicfinalBufferrewind( )
//返回当前位置与限制之间的元素数publicfinalintremaining( )
//返回当前位置之后是否还有元素publicfinalbooleanhasRemaining( )
//告知此缓冲区是否为只读缓冲区publicabstractbooleanisReadOnly( );
//返回此缓冲区是否具有可访问的底层实现数组publicabstractbooleanhasArray();
//返回此缓冲区的底层实现数组publicabstractObjectarray();
//返回此缓冲区的底层实现数组中第一个缓冲区元素的偏移量publicabstractintarrayOffset();
//告知此缓冲区是否为直接缓冲区publicabstractbooleanisDirect();
}
publicabstractclassFileChannelextendsAbstractInterruptibleChannelimplementsSeekableByteChannel, GatheringByteChannel, ScatteringByteChannel{
//从通道读取数据并放到缓冲区中publicintread(ByteBufferdst) ;
//把缓冲区的数据写到通道中publicintwrite(ByteBuffersrc) ;
//从目标通道中复制数据到当前通道publiclongtransferFrom(ReadableByteChannelsrc, longposition, longcount);
//把数据从当前通道复制给目标通道publiclongtransferTo(longposition, longcount, WritableByteChanneltarget);
}

Bytebuffer 简单使用

@TestpublicvoidbyteBufferTest(){
//创建一个容量 1024 的bytebufferByteBufferbyteBuffer=ByteBuffer.allocate(1024);
//存储元素byteBuffer.putChar('哈');
byteBuffer.putInt(123);
byteBuffer.putShort((short) 123);
//读写转换byteBuffer.flip();
//获取元素System.out.println(byteBuffer.getChar());   //哈System.out.println(byteBuffer.getInt());    //123System.out.println(byteBuffer.getLong());   //BufferUnderflowException 缓冲区溢出异常}

使用buffer是需要注意,如果put的数据类型,和get是使用的类型不一致,可能会出现BufferUnderflowException 缓冲区溢出异常

写数据到文件

这个案例是通过Java把一段字符串写到磁盘的某个文件,它的大概流程示意图如下:

实现步骤如下:

  • 把数据写入一个ByteBuffer缓冲区
  • 创建一个FileOutputStream 输出流,目的是磁盘的一个文件
  • 通过FileOutputStream得到FileChannel通道
  • 调用channel.write,把ByteBuffer中的数据写入FileChannel,从而写到磁盘文件

实现代码如下:

//使用NIO向磁盘写一个文件@TestpublicvoidnioWriteTest() throwsIOException {
//文件输出流FileOutputStreamfileOutputStream=newFileOutputStream("d:/1.txt");
//获取通道FileChannelchannel=fileOutputStream.getChannel();
//构建一个 容量1024字节长度的缓冲取ByteBufferbyteBuffer=ByteBuffer.allocate(1024);
System.out.println(byteBuffer.getClass().getName());
//给buffer写入数据byteBuffer.put("你好NIO".getBytes());
//转换byteBuffer.flip();
//把buffer中的数据通过通道写入初盘文件channel.write(byteBuffer);
//关闭通道channel.close();
//关闭输出流fileOutputStream.close();
   }

从文件读数据

这个案例是通过Java把某个文件中数据读取到内存中,它的大概流程示意图如下:

实现步骤如下:

  • 创建一个FileInputStream,目的是读取磁盘的某个文件
  • 通过FileInputStream得到FileChannel
  • 创建一个ByteBuffer用来接收数据
  • 调用 channel.read 把数据写入bytebuffer
  • 再从bytebuffer中得到真实的数据

实现代码如下:

@TestpublicvoidnioReadTest() throwsIOException {
//文件输入流Filefile=newFile("d:/1.txt");
FileInputStreamfileInputStream=newFileInputStream(file);
//获取通道FileChannelchannel=fileInputStream.getChannel();
//创建一个buffer,容量为file的长度ByteBufferbyteBuffer=ByteBuffer.allocate((int)file.length());
//从通道中读取数据到bytebuffer中channel.read(byteBuffer);
//输出结果System.out.println(newString(byteBuffer.array()));
//关闭资源channel.close();
fileInputStream.close();
 }

使用NIO完成文件拷贝

这个案例是通过NIO实现文件拷贝,它的大概流程示意图如下:

实现步骤如下:

  • 创建一个FileInputStream,目的是读取磁盘的某个文件
  • 通过FileInputStream得到FileChannel
  • 创建一个ByteBuffer用来接收数据
  • 调用 channel.read 把数据写入bytebuffer
  • 创建FileOutputStream,目的是把数据写到另外一个文件
  • 通过FileOutputStream得到FileChannel通道
  • 调用channel.write,把ByteBuffer中的数据写入FileChannel,从而写到磁盘文件

实现代码如下:

//文件拷贝 1.txt 中的内容拷贝到2.txt@TestpublicvoidnioCopyTest() throwsIOException {
//文件对象Filefile=newFile("d:/1.txt");
//文件输入流FileInputStreamfileInputStream=newFileInputStream(file);
//得到通道FileChannelchannel=fileInputStream.getChannel();
//文件输出流FileOutputStreamfileOutputStream=newFileOutputStream("d:/2.txt");
//获取通道FileChanneloutChannel=fileOutputStream.getChannel();
//缓冲区,容量为file的长度ByteBufferbyteBuffer=ByteBuffer.allocate((int)file.length());
while(true){
//每次读取需要把缓冲区复位,否则当bytebuffer总的position等于limit的时候,//read的返回值是 0 ,用于不会走-1,就会死循环byteBuffer.clear();
//把数据读取到缓冲区intreadLenth=channel.read(byteBuffer);
System.out.println("redLength = "+readLenth);
//读取结果长度为-1说明读完了if(readLenth==-1){
break;
        }
//缓冲区读写交换byteBuffer.flip();
outChannel.write(byteBuffer);
    }
//关闭通道channel.close();
//关闭流fileInputStream.close();
//关闭通道outChannel.close();
//关闭流fileOutputStream.close();
}

使用transferFrom拷贝文件

FileChannel提供了 transferFrom方法可以实现通道和通道之间的数据拷贝,方法包括三个参数:

publicabstractlongtransferFrom(ReadableByteChannelsrc,
longposition, longcount)throwsIOException;
  • src : 源通道,即从哪个通道拷贝数据
  • position :拷贝的开始位置; 必须是非负数
  • count : 要拷贝的最大字节数; 必须是非负数

实现代码如下:

//文件拷贝 1.txt 中的内容拷贝到2.txt@TestpublicvoidnioCopyTest2() throwsIOException {
//读操作=================================================================================//文件对象Filefile=newFile("d:/1.txt");
//文件输入流FileInputStreamfileInputStream=newFileInputStream(file);
//得到通道FileChannelinputChannel=fileInputStream.getChannel();
//文件输出流FileOutputStreamfileOutputStream=newFileOutputStream("d:/2.txt");
//获取通道FileChanneloutChannel=fileOutputStream.getChannel();
//使用transferFrom拷贝数据,将inputChannel中数据拷贝到outChanneloutChannel.transferFrom(inputChannel, 0, inputChannel.size());
outChannel.close();
inputChannel.close();
fileInputStream.close();
fileOutputStream.close();
  }

HeapByteBufferR只读buffer的使用

HeapByteBuffer,只读Buffer,只允许从中读数据,不允许写数据,否则抛出ReadOnlyBufferException异常,案例如下:

/*** 只读buffer*/@TestpublicvoidnioOnlyRead() throwsIOException {
ByteBufferbyteBuffer=ByteBuffer.allocate(1024);
for (inti=0 ; i<10 ; i++){
byteBuffer.putInt(i);   //0123456789    }
//读写转换byteBuffer.flip();
//得到一个只读buffer ,使用的是java.nio.HeapByteBufferRByteBufferreadOnlyBuffer=byteBuffer.asReadOnlyBuffer();
//java.nio.HeapByteBufferRSystem.out.println(readOnlyBuffer.getClass().getName());
while(readOnlyBuffer.hasRemaining()){
System.out.print(readOnlyBuffer.getInt());  //0123456789    }
readOnlyBuffer.putInt(10);  //ReadOnlyBufferException ,不允许写}

MappedByteBuffer 的使用

nio中引入了一种基于MappedByteBuffer操作大文件的方式,其读写性能极高,它可以基于内存实现文件的修改,这里的内存指的是“堆外内存”。

我们来做过案例,使用MappedByteBuffer来修改一个文本内容:"helloworld"把 h和w修改为大写。

@TestpublicvoidmappedByteBuffer() throwsIOException {
//随机访问文件,RW代表支持而读写,文件内容为 :helloworldFilefile=newFile("d:/3.txt");
RandomAccessFilerandomAccessFile=newRandomAccessFile(file,"rw");
//通道FileChannelchannel=randomAccessFile.getChannel();
//得到MappedByteBuffer : mode:读写模式, position: 映射区域的起始位置 size: 映射区域大小MappedByteBuffermappedByteBuffer=channel.map(FileChannel.MapMode.READ_WRITE, 0, file.length());
//第1个字节修改为 大 HmappedByteBuffer.put(0,(byte)'H');
//第6个字节修改为 大 WmappedByteBuffer.put(5,(byte)'W');
randomAccessFile.close();
 }

总结

本篇文件介绍了一下Java NIO 三大核心:selector , channel , buffer ,重点讲了Buffer的底层原理和几个小案例。

目录
相关文章
|
3月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用
|
9天前
|
存储 网络协议 Java
Java NIO 开发
本文介绍了Java NIO(New IO)及其主要组件,包括Channel、Buffer和Selector,并对比了NIO与传统IO的优势。文章详细讲解了FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel及Pipe.SinkChannel和Pipe.SourceChannel等Channel实现类,并提供了示例代码。通过这些示例,读者可以了解如何使用不同类型的通道进行数据读写操作。
Java NIO 开发
|
2月前
|
Java
"揭秘Java IO三大模式:BIO、NIO、AIO背后的秘密!为何AIO成为高并发时代的宠儿,你的选择对了吗?"
【8月更文挑战第19天】在Java的IO编程中,BIO、NIO与AIO代表了三种不同的IO处理机制。BIO采用同步阻塞模型,每个连接需单独线程处理,适用于连接少且稳定的场景。NIO引入了非阻塞性质,利用Channel、Buffer与Selector实现多路复用,提升了效率与吞吐量。AIO则是真正的异步IO,在JDK 7中引入,通过回调或Future机制在IO操作完成后通知应用,适合高并发场景。选择合适的模型对构建高效网络应用至关重要。
39 2
|
2月前
|
微服务
成功解决:java.lang.NoSuchMethodError: reactor.netty.http.client.HttpClient.chunkedTransfer(Z)Lreactor/ne
这篇文章讲述了在微服务架构中整合gateway网关时遇到的`java.lang.NoSuchMethodError`错误的解决方法。问题主要是由于`spring-boot-starter-parent`的版本和`spring-cloud-starter-gateway`的版本不匹配所导致。文章提供了具体的版本不一致的错误配置,并给出了匹配的版本配置方案,以及成功测试的截图。
成功解决:java.lang.NoSuchMethodError: reactor.netty.http.client.HttpClient.chunkedTransfer(Z)Lreactor/ne
|
2月前
|
网络协议 C# 开发者
WPF与Socket编程的完美邂逅:打造流畅网络通信体验——从客户端到服务器端,手把手教你实现基于Socket的实时数据交换
【8月更文挑战第31天】网络通信在现代应用中至关重要,Socket编程作为其实现基础,即便在主要用于桌面应用的Windows Presentation Foundation(WPF)中也发挥着重要作用。本文通过最佳实践,详细介绍如何在WPF应用中利用Socket实现网络通信,包括创建WPF项目、设计用户界面、实现Socket通信逻辑及搭建简单服务器端的全过程。具体步骤涵盖从UI设计到前后端交互的各个环节,并附有详尽示例代码,助力WPF开发者掌握这一关键技术,拓展应用程序的功能与实用性。
69 0
|
2月前
|
Java 应用服务中间件 Linux
(九)Java网络编程无冕之王-这回把大名鼎鼎的Netty框架一网打尽!
现如今的开发环境中,分布式/微服务架构大行其道,而分布式/微服务的根基在于网络编程,而Netty恰恰是Java网络编程领域的无冕之王。Netty这个框架相信大家定然听说过,其在Java网络编程中的地位,好比JavaEE中的Spring。
|
2月前
|
存储 网络协议 Java
【Netty 神奇之旅】Java NIO 基础全解析:从零开始玩转高效网络编程!
【8月更文挑战第24天】本文介绍了Java NIO,一种非阻塞I/O模型,极大提升了Java应用程序在网络通信中的性能。核心组件包括Buffer、Channel、Selector和SocketChannel。通过示例代码展示了如何使用Java NIO进行服务器与客户端通信。此外,还介绍了基于Java NIO的高性能网络框架Netty,以及如何用Netty构建TCP服务器和客户端。熟悉这些技术和概念对于开发高并发网络应用至关重要。
51 0
|
3月前
|
安全 Java Linux
(七)Java网络编程-IO模型篇之从BIO、NIO、AIO到内核select、epoll剖析!
IO(Input/Output)方面的基本知识,相信大家都不陌生,毕竟这也是在学习编程基础时就已经接触过的内容,但最初的IO教学大多数是停留在最基本的BIO,而并未对于NIO、AIO、多路复用等的高级内容进行详细讲述,但这些却是大部分高性能技术的底层核心,因此本文则准备围绕着IO知识进行展开。
135 1
|
3月前
|
监控 网络协议 Java
Java面试题:解释Java NIO与BIO的区别,以及NIO的优势和应用场景。如何在高并发应用中实现NIO?
Java面试题:解释Java NIO与BIO的区别,以及NIO的优势和应用场景。如何在高并发应用中实现NIO?
51 0
|
6天前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
21 2
下一篇
无影云桌面