Java NIO(Netty,Redis,Zookeeper高并发实战整理)

Java NIO

NIO与OIO的对比

1.OIO事面向流的,NIO是面向缓冲区的。OIO是面向字节流或字符流的,在一般的OIO操作中,一流式的方法顺序地从一个流中读取一个或多个字节,因此,不能随意地改变读取指针的位置。NIO中引入了Channel(通道)和Buffer(缓冲区)的概念。读取和写入,只需要从通道中读取数据到缓冲区中,或将数据从缓冲区中写入到Channel中。可以随意地读取Buffer中任意位置的数据。
2.OIO的操作是阻塞的,而NIO的操作是非阻塞的。
3.OIO没有选择器的概念,而NIO有选择器的概念。

Buffer缓冲区

应用程序与Channel主要的交互操作,就是进行数据的read读取和write写入。通道的读取就是将数据从通道读取到缓冲区;通道的写入就是将数据从缓冲区写入到通道中。

NIO的Buffer(缓冲区)本质是一个内存块,既可以写入数据,也可以从中读取数据。NIO的Buffer类,是一个抽象类,位于java.nio包中,其内部类是一个内存块(数组)。

Buffer类是一个非线程安全类。

Buffer类是一个抽象类,对应与java的主要数据类型,在NIO中有8种缓冲区类型。分别是:ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer,MappedByteBuffer.

前7种Buffer类型,覆盖了能在IO中传输的所有java基本数据类型。第8种类型MappedByteBuffer是专门用于内存映射的一种ByteBuffer.

Buffer类属性

Buffer类在其内部有一个byte[]数组内存块,作为内存缓冲区。为了记录读写的状态和位置,提供了四个成员属性:capacity(容量),position(读写位置),mark(标记)

capacity属性

Buffer类的capacity属性,表示内部容量的大小。一旦写入的对象数量超过了capacity,缓冲区就满了,不能在写入了。

capacity容量一旦初始化,就不能再改变。capacity不是指内存块byte[]数组的字节数量,是指写入数据对象的数量。

position属性

Buffer类的position属性,表示当前的位置。position属性与缓冲区的读写模式有关。

在写入模式下,position的值变化规则如下:

1.当缓冲区刚开始进入到读模式时,position会被重置为0.
2.当从缓冲区读取时,也是从position位置开始读。读取数据后,position向前移动到下一个可读的位置。
3.position的最大值为最大可读上限limit,当position到达limit时,表明缓冲区就没有空间可以写了。

在读模式下,position的值变化规则如下:

1.当缓冲区剋是进入到读模式时,position会被重置为0.
2.当从缓冲区读取时,也是从position位置开始读。读取数据后,position先前移动到下一个可读的位置。
3.position最大的值为最大可读上限limit,当position达到limit时,表明缓冲区已经无数据可读。

limit属性

Buffer类的limit属性,表示读写的最大上限。limit属性,也与缓冲区的读写模式有关。在不同的模式下,limit的值的含义时不同的。

在写模式下,limit属性值的含义为可以写入的数量最大上限。在刚进入到写模式时,limit的值会被设置成缓冲区的capacity容量值,表示可以一直将缓冲区的容量写满。

在读模式下,limit的值含义为最多能从缓冲区中读取到多少数据。

Buffer 类的重要方法

allocate()创建缓冲区

在使用Buffer(缓冲区)之前,首先需要获取Buffer子类的实例对象,并且分配内存空间。为了获取一个Buffer实例对象,不能使用子类构造器new创建一个实例对象,而时调用子类的allocate()方法。

put()写入到缓冲区

在调用allocate方法分配内存,返回了实例对象后,缓冲区实例对象处于写模式,可以写入对象。要写入缓冲区,需要调用put()方法。put方法只有一个参数,即为所需要的对象。写入的数据类型要求与缓冲区的类型一致。

filp()翻转

向缓冲区写入数据后,不能直接从缓冲区中读取数据,需要调用filp()方法将写入模式转成读取模式。

get()从缓冲区读取

调用flip方法,将缓冲区切换成读取模式。在调用get方法就可以,每次从position的位置读取一个数据,并且进行相应的缓冲区属性的调整。

rewind()倒带

已经读完的数据,如果需要在读一遍,可以调用rewind()方法。rewind()方法,主要是调整了缓冲区的position属性,规则如下:

1.position重置为0,所有可以重读缓冲区中的所有数据。
2.limit保持不变,数据量还是一样的。
3.nark标记被清理,表示之前的临时位置不能在用了。

mark()和reset()设置position位置

Buffer.mark()方法的作用是将当前position的值保存器来,放在mark属性中,让mark属性记住这个临时位置。Buffer.reset()方法将mark的值恢复到position中。

clear()清空缓冲区

在读取模式下,调用clear()方法将缓冲区切换成写入模式。此方法会将position清零,limit设置为capaity最大容量值,可以一直写下去,知道缓冲区写满。

通道(Channel)

在OIO中,同一个网络连接会关联到两个流:一个输入流,另一个是输出流。通过这两个流。不断地进行输入和输出操作。

在NIO中,同一个网络连接使用一个通道表示,所有的NIO的IO操作tong都是从通道开始的。一个通道类似与OIO中两个流的结合体,既可以从通道读取,也可以向通道写入。

常用的Channel有四种具体实现:FileChannel,SocketChannel,ServerSocketChannel,DataGramChannel.

FileChannel:文件通道,用于文件的数据读写。
SocketChannel:套接字通道,用于Socket套接字TCP连接的数据读写
ServerSocketChannel:服务器嵌套接字通道,允许监听TCP连接请求,为每个监听到的请求,创建一个SocketChannel
DatagramChannel:数据报通道,用于UDP协议的数据读写。

FileChannel 文件通道

FileChannel是专门操作文件的通道。通过FileChannel,可以从一个文件读取数据,也可以将数据写入到文件中。FileChannel是阻塞模式,不能设置为非阻塞模式。

获取FileChannel通道

可以通过文件的输入流,输出流获取FileChannel文件通道。也可以通过RandomAccessFile文件随机访问类,获取FileChannel文件通道。

读取FileChannel通道

从通道读取数据都会调用通道的int read(ByteBuffer buf) 方法,从通道读取到数据写入到ByteBuffer缓冲区,并且返回读取到的数据量

注意:对于Channel来说是读取数据,但是对于ByteBuffer缓冲区来说是写入数据,这时候ByteBuffer缓冲区处于写入模式。

写入FileChannel

写入数据到通道,都会调用通道的int write(ByteBuffer buf) write方法的作用是,从ByteBuffer缓冲区中读取数据,然后写入到通道自身,而返回值是写入成功的字节数。

注意:此时的ByteBuffer缓冲区要求的是可读的,处于读模式下。

关闭通道

当通道使用完成后,必须将器关闭。调用close方法即可。

强制刷新到磁盘

将缓冲区写入通道是,由于性能原因,操作系统不可能每次都实时将数据写入磁盘。如果需要保证写入通道的缓冲数据,最终都真正的写入磁盘。可以调用FileChannel的force()方法。

SocketChannel和ServerSocketChannel套接字通道

在NIO中,涉及网络连接的通道有两个,一个是SocketChannel复制连接传输,另一个是ServerSocketChannel负责连接的监听。

ServerSocketSocket应用与服务器端,而SocketChannel同时处于服务器端和客户端。

无论是ServerSocketChannel,还是SocketChannel,都支持阻塞和非阻塞两种模式。设置方法如下:

1.socketChannel.configureBlocking(false)//设置为非阻塞模式
2.socketChannel.configureBlocking(true)

阻塞模式下,SocketChannel通道的connect连接,read,write都是同步和阻塞时的,与OIO相同。所有下面的以非阻塞的特点。

获取SocketChannel传输通道

客户端

通过SocketChannel静态方法open()获得一个套接字传输通道;然后将socket设置成为非阻塞的;最后通过connect()实例化方法,对服务器IP和端口发起连接。

SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("127.0.0.1",80));
while(! socketChannel.finishConnect()){
    //不断自旋 等待。或者做一些其他的事情
}

服务端

当新连接事件到来时,在服务器端的ServerSocketChannel能够成功查询出一个新连接事件,并且通过调用服务器端ServerSocketChannel监听套接字accpet()方法,来获取连接套接字通道。

  ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        SocketChannel accept = serverSocketChannel.accept();
        accept.configureBlocking(false);
读取SocketChannel传输通道

当SocketChannel可读时,可以从SocketChannel读取数据,调用read方法将数据读入缓冲区

 ByteBuffer buffer = ByteBuffer.allocate(1024);
        int read = accept.read(buffer);

非阻塞的读取数据请开Selector章节

写入SocketChannel传输通道

调用读方法

buffer.flip();//将缓冲区变成读取模式
socketChannel.write(buffer)
关闭SocketChannel传输通道

在关闭SockerChannel传输通道签,如果传输通道用来写入数据,则建议一次shutdownOutput()种植输出方法,向对方发送一个输出结束标志(-1).然后调用socketChannel.close()方法,关闭套接字连接。

DatagramChannel数据报通道

DatagramChannel是UDP传输的。只需要知道对方的IP和端口就可传输数据

获取DatagramChannel
  DatagramChannel channel = DatagramChannel.open();
  channel.configureBlocking(false);
  channel.socket().bind(new InetSocketAddress(15555));

读取DatagramChannel

阻塞状态:需要调用SocketAddress receive(ByteBuffer buf)

ByteBuffer buffer = ByteBuffer.allocate(1222);
SocketAddress socketAddress = channel.receive(buffer);

非阻塞状态见Select选择器

写入DatagramChannel

调用send(方法)

  buffer.flip();
  channel.send(buffer,socketAddress);
  buffer.clear();

Selector选择器

为了实现IO多路复用,首先把Channel注册到Selector选择器中,然后通过选择器内部机制,可以查询select这些注册的通道是否有已经就绪的IO事件。

与OIO相比,使用选择器的最大优势: 系统开销小,系统不必为每一个网络连接(文件描述符)创建进程/线程,从而大大减小了系统的开销。

一个单线程处理一个Selector选择器,一个选择器可以监控很多Channel。通过Selector,一个线程可以处理数百,数千,数万,甚至更多的通道。

Channel和Selecotr之间的关系,通过register的方法完成。调用Channel的register(Selector sel,int ops)方法

可以选择Selelctor的Channel的IO事件类型,包括以下四种:

1.可读:SelectionKey.OP_READ
2.可写:SelectionKey.OP_WRITE
3.连接:SelectionKey.OP_CONNECT
4.接收:SelectionKey.OP_ACCEPT

事件类型定义在SlectionKey类中。如果选择器要监控Channel的多种事件,可以用“|”来实现

int key = SelectionKey.OP_READ | SelectorKey.OP_WRITE
SelectableChannel 可选通道

不是所有的Channel都能被Selelctor选择器监控的。只有继承了SelectableChannel,才可以被选择。

SelectionKey选择键

Channel和Selector的监控关系注册后,就可以选择就绪事件。具体的选择工作,和调用选择器Selector的select()方法来完成。通过select方法,选择器可以不断地选择Channel中所发生操作的就绪状态,返回注册过的感兴趣的那些IO事件。

SelectionKey是那些被Selector选中的IO事件。一个IO事件发生后,如果之前在Selector中注册过 ,就会被Selector选中,并放入SelelctorKey集合中;如果没有注册过,即使发生了IO事件,也不会被Selector选中。

Selector使用流程

步骤如下:

1. 获取选择器实例
2.将通道注册到选择器中;
3.轮询注册的IO就绪事件

Selector 的类方法open()的内部,是向选择器SPI(SelecotrProvider)发出请求,通过默认的SelectorProvide对象,获取一个新的Selector实例。Java中SPI全称为(Service Provider Interface,服务提供者接口),是jdk的一种可以扩展的服务提供和发现机制。

Selector selector = Selector.open();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(99999));
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        while(selector.select()>0){
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while(iterator.hasNext()){
                SelectionKey selectionKey = iterator.next();
                if (selectionKey.isAcceptable()){
                    //io事件 ServerSocketChannel服务器监听通道有新连接
                }else if (selectionKey.isConnectable()){
                    //IO事件 传输通道连接成功
                }else if (selectionKey.isReadable()){
                    //IO事件 传输通道可读
                }else if (selectionKey.isWritable()){
                    //IO事件 传输通道可读
                }
                iterator.remove();
            }
        }

参考书籍
《Netty,Redis,Zookeeper高并发实战》

热门文章

暂无图片
编程学习 ·

Shell编程_echo/printf

目录一、Shell echo/printf 命令1、Shell显示命令-echo2、printf 命令操作常用的一些格式化字符二、test命令一、Shell echo/printf 命令Shell echo/printf 命令1、Shell显示命令-echo打印普通字符串[root@master ~]# echo "hello shell" hello shell创建和清空文件1…
暂无图片
编程学习 ·

二、21【设计模式】之状态模式

今天的博客主题设计模式 ——》 设计模式之状态模式状态模式 SP (State Pattern)定义允许对象在内部状态发生改变时改变它的行为,看起来好像修改了它的类。类的行为是由状态决定的,不同的状态下该类有不同的行为。就是一个对象在其内部改变的时候,它的行为也随之改变。核心…
暂无图片
编程学习 ·

718. 最长重复子数组

给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。目录1、题目分析2、解题分析3、代码示例 1:输入: A: [1,2,3,2,1] B: [3,2,1,4,7] 输出: 3 解释: 长度最长的公共子数组是 [3, 2, 1]。1、题目分析求两个数组公共的子数组的长度,那么可以用较短的那个…
暂无图片
编程学习 ·

Tuxera NTFS for Mac在Mac教你快速进行安全传输文件教程

Mac系统在办公性能上更加高效快捷。但是Mac电脑在U盘读取上具有局限性。它并不能读取到NTFS格式的硬盘,那么我们可以用NTFS for Mac这款神器编辑读取。具体的安装步骤 1、双击下载好的安装包(.dmg)文件,会跳出安装会话框,点击"Install Tuxera NTFS"开始安装软件…
暂无图片
编程学习 ·

如何更好的使用大数据

在互联网时代,依靠大数据是未来的发展趋势。大数据分析现在非常流行,但是我们需要知道的是,大数据的价值体现在有效而正确的分析中。只有通过正确有效的分析工具和分析方法来解释现有的大数据,大数据才能为我们带来有价值的结果。今天,中琛魔方将教您如何有效运用大数据。…
暂无图片
编程学习 ·

希尔排序的原理,图解,java代码实现

希尔排序希尔排序就是一种插入排序,又称“缩小增量排序”,是插入排序算法的一种更高效的改进版本。排序原理排序原理:选定一个增长量h,按照曾长亮h作为分组的依据,对数据进行分组。 对分好组的每一组数据完成插入排序。 减少增长量,最小减为1,重复第二步操作。举例排序过…
暂无图片
编程学习 ·

【漏洞通告】Treck TCP/IP协议库“ Ripple20”漏洞通告

【漏洞通告】Treck TCP/IP协议库“ Ripple20”漏洞通告 威胁对抗能力部 [绿盟科技安全情报](javascript:void(0)😉 昨天 通告编号:NS-2020-0039 2020-06-30TA****G: Treck、TCP/IP协议库、Ripple20漏洞危害: 攻击者利用此类漏洞,可造成拒绝服务、远程代码执行等。版本: 1…
暂无图片
编程学习 ·

mysql学习总结

连接数据库语句:mysql -h 服务器主机地址 -u 用户名 -p用户密码 基本的数据库操作命令: update user set password=password(‘123456’)where user=‘root’; 修改密码 flush privileges; 刷新数据库 show databases; 显示所有数据库 use dbname;打开某个数据库 show table…
暂无图片
编程学习 ·

mongoDB采坑

mongoDB采坑 安装问题没有权限参考 https://blog.csdn.net/qq_20084101/article/details/82261195
暂无图片
编程学习 ·

json从立地到成佛

文章目录诞生于JavaScript,json的前世今生json含义json诞生搞清json兄弟姐妹,看清区别json VS xml小小翻译官,json的应用前端ajax+json异步传输:跨平台webservice:非关系数据库存储(Nosql)拒绝四不像,json语法有要求json语法规则json名称/值json对象json数组JavaScript对象…
暂无图片
编程学习 ·

linux重要知识清单:进程管理

相关的系统调度fork(): 用于创建进程,Linux中进程的创建者与被创建者是父子关系clone():克隆,真正实现创建进程的操作,fork()是不带参数的,clone()是带参数的,通过不同的参数后,可以不仅仅做fork(),也能做其他的创建进程的方式exec():加载外部存储器的一段代码,把一个可…
暂无图片
编程学习 ·

集合类

Collection集合: 集合是Java提供的一种容器,可以用来存储多个数据。数组也是容器,但是数组的长度是固定的,集合的长度是可变的 数组中存储的都是同一类型的元素,可以存储基本数据类型的值;集合存储的都是对象,而且对象的类型可以不一致。 在开发中,一般对象多的时候,使…
暂无图片
编程学习 ·

内网渗透 -- 获取内网浏览器历史记录等相关信息

“我喜欢你,做我女朋友可以吗?”电话的那头没有反应,男生沉不住气了,小心翼翼地问着,“你在干嘛呀?”“我在点头。”---- 网易云热评环境:小攻:Kali 2020,ip:192.168.1.133小受:win7 x86,ip:192.168.1.137一、生成木马及监听主机参考上篇文章:二、获取浏览器历史…
暂无图片
编程学习 ·

五人分

五人分🐟 """ A、B、C、D、E 五人在某天夜里合伙去捕鱼,到第二天凌晨时都疲惫不堪,于是各自找地方睡觉。 日上三杆,A 第一个醒来,他将鱼分为五份,把多余的一条鱼扔掉,拿走自己的一份。 B 第二个醒来,也将鱼分为五份,把多余的一条鱼扔掉拿走自己的一份…
暂无图片
编程学习 ·

pyspark_聚合操作groupby_sum

print(*****************整体变化:) print(DF_temp.groupby().agg({deposit_increase:sum}).collect()) print(***************存款人均变化:) print(DF_temp.groupby().agg({deposit_increase:mean}).collect())
暂无图片
编程学习 ·

清华大学计算机研究生机试真题 问题 A: 输出梯形

问题 A: 输出梯形题目描述输入一个高度h,输出一个高为h,上底边为h的梯形。输入一个整数h(1<=h<=1000)。输出h所对应的梯形。样例输入5样例输出******************************** *************C++实现一开始只尝试读取一个h,出现WRONGANSWER50,后来用while进行循环读…
暂无图片
编程学习 ·

Z字型变换(Go,LeetCode)

目录题目描述解决方案代码代码走读传送门题目描述将一个给定字符串根据给定的行数,以从上往下、从左到右进行Z字形排列。比如输入字符串为 LEETCODEISHIRING ,行数为3时,排列如下:L C I R E T O E S I I G E D H N之后,你的输出需要从左往右逐行读取,产生出一…