与IO不同的是在NIO中都是基于一个Channel开始。类似流,可以从channel中写到channel中,也可以反写
读: int byteRead =channel.read(buf) ---这里特别注明一个transferTo()方法,会比一般的缓冲流复制文件快,跳转相关测试
写: int bytesWritten =channel.write(buf);
如配置Channel为阻塞或者非阻塞模式,以及如何注册到Selector,移步下方Selectors
其三个属性:
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允许单线程处理多个 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 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压力测试指引