NIO主要三个部分:
- Channels 通道
- Buffers 缓冲区
- Selectors 选择器
方法简介:
与IO不同的是在NIO中都是基于一个Channel开始。类似流,可以从channel中写到channel中,也可以反写
Channels(覆盖UDP、TCP、文件IO):
- FileChannel ---文件io
- DatagramChannel ---- udp
- SocketChannel ----- 网络IO
- ServerSocketChannel ---- 监听链接
读: int byteRead =channel.read(buf) ---这里特别注明一个transferTo()方法,会比一般的缓冲流复制文件快,跳转相关测试
写: int bytesWritten =channel.write(buf);
如配置Channel为阻塞或者非阻塞模式,以及如何注册到Selector,移步下方Selectors
Buffer(byte,char,short,int,long,float,double)
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
其三个属性:
capacity:
含义与模式无关;Buffer的一个固定的大小值;Buffer满了需要将其清空才能再写;
ByteBuffer.allocate(48);该buffer的capacity为48byte
CharBuffer.allocate(1024);该buffer的capacity为1024个char
position:
含义取决于Buffer处在读模式还是写模式(初始值为0,写或者读操作的当前位置)
写数据时,初始的position值为0;其值最大可为capacity-1
将Buffer从写模式切换到读模式,position会被重置为0
limit
含义取决于Buffer处在读模式还是写模式(写limit=capacity;读limit等于最多可以读取到的数据)
写模式下,limit等于Buffer的capacity
flip()方法
将Buffer从写模式切换到读模式,取决于当前状态
调用flip()方法会将position设回0,并将limit设置成之前position的值。
rewind()方法
将position设回0,所以你可以重读Buffer中的所有数据
limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)
get() 方法,put()方法
一般是调用返回第一个字节
mark()方法,reset()方法
可以标记Buffer中的一个特定position。之后可以通过调用reset()恢复到Buffer.mark()标记时的position
position()、limit() 相当于读取的开始和结束字节下标,如果limit没有限制,postion多少字节,就空出多少字节
clear()方法
一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入,清空整个缓冲区,position归0,limit设回capacity(自己设置的容量)
compact()方法
只会清除已经读过的数据;任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。
将position设到最后一个未读元素正后面,limit被设置成 capacity的值
Selector
Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在一个聊天服务器中。 要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等
创建:
Selector selector = Selector.open();
注册:
channel.configureBlocking(false); //boolean为设置是否阻塞 tip: 阻塞即等待IO处理完后才返回
与Selector一起使用时,Channel必须处于非阻塞模式,这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式(而套接字通道都可以)
SelectionKey key = channel.register(selector, Selectionkey.OP_READ);
第二个参数表明Selector监听Channel时对什么事件感兴趣
①SelectionKey.OP_CONNECT 连接
②SelectionKey.OP_ACCEPT 接收
③SelectionKey.OP_READ 读取
④SelectionKey.OP_WRITE 循环
SelectionKey (包含了interest集合 、ready集合 、Channel 、Selector 、附加的对象(可选))
int interestSet = key.interestOps();可以进行类似interestSet & SelectionKey.OP_CONNECT的判断
使用:
select():阻塞到至少有一个通道在你注册的事件上就绪了
selectNow():不会阻塞,不管什么通道就绪都立刻返回
selectedKeys():访问“已选择键集(selected key set)”中的就绪通道
close():使用完selector需要用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效
应用实例:
一、buffer结合channel 基本操作 BIO
tip:
CharSet
用于构建String 和ByteBuffer,以及编码的的一个转换类
构建:
Charset charSet = Charset.forName("gbk");//window系统默认txt文本是gbk
charSet.decode(butBf) , 用于byteBuffer to String
charSet.encode("测试下") 用于String to byteBuffer
/** * @Title: Fastcopy * @Description: 快速复制 * @throws FileNotFoundException * @throws IOException 设定文件 * @return void 返回类型 * @throws */ private static void Fastcopy() throws FileNotFoundException, IOException { //打开输入流 FileInputStream fis = new FileInputStream("E:/家园游戏.txt"); //打开输出流 FileOutputStream fos = new FileOutputStream("C:/Users/Administrator/Desktop/直播平台数据/game.txt"); try{ //获取通道 FileChannel readchannel = fis.getChannel(); FileChannel writechannel = fos.getChannel(); readchannel.transferTo(0, readchannel.size(), writechannel); }catch(IOException e){ e.printStackTrace(); }finally { fis.close(); fos.close(); } } /** * @Title: readFile * @Description: 读取文件 * @throws FileNotFoundException * @throws IOException 设定文件 * @return void 返回类型 * @throws */ private static void readFile() throws FileNotFoundException, IOException { //打开输入流 FileInputStream fis = new FileInputStream("E:/家园游戏.txt"); try{ //获取通道 FileChannel channel = fis.getChannel(); //设置容量 ByteBuffer allocate = ByteBuffer.allocate(1024); channel.read(allocate); //window系统的默认编码 Charset charset = Charset.forName("gbk"); //修改为读模式 allocate.flip(); System.out.println(charset.decode(allocate)); }catch(IOException e){ e.printStackTrace(); }finally { fis.close(); } }
二、结合selector基本操作网络IO(套接字) NIO
对某一端口进行监控
public static void main(String[] args) throws Exception { ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //设置为非阻塞 serverSocketChannel.configureBlocking(false); //绑定IP和接口 serverSocketChannel.socket().bind(new InetSocketAddress("192.168.1.212", 8080)); Selector selector = Selector.open(); // 注册感兴趣事件到selector serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); //判断是否存在通道 while(selector.select()!=0){ Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { //循环选中事件 SelectionKey selecttionKey = iterator.next(); //删除已经处理的 iterator.remove(); if (selecttionKey.isAcceptable()) { // 返回注册该事件时的channel ,即SelectableChannel ServerSocketChannel channel = (ServerSocketChannel) selecttionKey.channel(); // 有连接事件来了, 可以处理接收请求了,注意如果不进行accept,select.select()一直能轮询到东西 // 接收后返回了个socketchannel,开始配置 SocketChannel socketChannel = channel.accept(); // 也配置成非阻塞处理 socketChannel.configureBlocking(false); // 复用同一个selector上注册感兴趣的事件,并注册感兴趣的可读事件 socketChannel.register(selector, selecttionKey.OP_READ); } // 如果来可以可读事件 if (selecttionKey.isReadable()) { // 返回注册该事件时的channel ,即实现了SelectableChannel的 SocketChannel socketChannel = (SocketChannel) selecttionKey.channel(); // 后面就都是通过byteBuffer和channel来读操作了 ByteBuffer byteBf = ByteBuffer.allocate(1024); socketChannel.read(byteBf); Charset charset = Charset.forName("utf-8"); byteBf.flip(); System.out.println("clinet :" + charset.decode(byteBf)); // socket是双通道,故也可以直接返回东西了 socketChannel.write(charset.encode("test only")); socketChannel.close(); } } } }
什么时候用IO,什么时候用NIO呢?
大量链接涌入的时候,传的数据比较少,然后处理时间比较长,的时候适合NIO(偏向IO密集型)
如果传入的链接比较少,然后传输数据量大,比如文件上传之类,适合BIO
NIO : 用一个thread(selector)做服务接收,和链接的维持
IO : 用一个thread做服务接收,其它每个链接都用一条线程保持
压力测试 ------------跳转ab压力测试指引