----NIO简介----
为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络
IO操作的模式:
PIO(Programing IO):所有的IO操作由CPU处理,CPU占用率比较高
DMA(Direct Memory Access):CPU把IO操作控制权交给DMA控制器,只能以固定的方式读写,CPU空闲做其他工作
通道方式(Channel):能执行有限通道指令的IO控制器,代替CPU管理控制外设。通道有自己的指令系统,是一个协处理器,具有更强的独立处理数据输入和输出的能力
Java NIO由以下几个核心部分组成:
Buffer:缓冲区
Channel:通道
Selector:选择器(轮询器)
----Buffer的使用----
Java NIO中的Buffer用于和NIO通道进行交互,数据是从通道读入缓冲区,从缓冲区写入到通道中的
缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存,这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该快内存
Java NIO里关键的Buffer实现:
ByteBuffer CharBuffer DoubleBuffer FloatBuffer
IntBuffer LongBuffer ShortBuffer
1、Buffer的基本用法:
使用Buffer读写数据一般遵循以下四个步骤:
1)创建缓冲区,写入数据到Buffer
2)调flip()方法
3)从Buffer中读取数据
4)调用clear()方法或者comparct()方法
1.创建缓冲区,容量1024
ByteBuffer buffer = ByteBuffer.allocate(1024);
2.写入数据
buffer.put("abcde".getBytes());
3.把写入模式转换成读取模式
buffer.flip();
4.读取(limit,buffer里有多少可读数据值就是多少)
byte[] data = new byte[buffer.limit()];
buffer.get(data);
System.out.println(new String(data));
2、Buffer
capacity:容量
作为一个内存块,Buffer有一个固定大小值,只能往里写capacity个byte、long,char等类型,一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据
position:位置
当你写数据到Buffer中时,position表示当前的位置。初始的position值为0。当数据写入到Buffer后,position会向前移动到下一个可插入数据的Buffer单元。positoin最大可为capacity
当读取数据时,也是从某个特定位置读,当将Buffer从写模式切换到读模式,position会被重置为0,当从Buffer的position出读取数据时,position向前移动到下一个可读的位置
limit:限制
在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据,写模式下,limit等于capacity
当切换到读模式时,limit表示你最多能读到多少数据
3、常用方法:
put(); //放入数据
? get(); //获取数据
?
Buffer的分配:
想要获得一个Buffer对象首先要进行分配,每一个Buffer类都有一个allocate方法
ByteBuffer buf = ByteBuffer.allocate(1024); //创建简接缓冲区,大小为1024字节
ByteBuffer buf2 = ByteBuffer.allocateDirect(1024); //直接缓冲区
直接缓冲区和简接缓冲区的区别:
间接缓冲区:在堆中开辟,垃圾回收器可以回收,空间不大,读写文件速度较慢
直接缓冲区:在物理内存中开辟空间,空间较大,读写文件速度快,不受垃圾回收器控制,创建和销毁耗性能
flip()方法
将Buffer从写模式切换到读模式,调用flip方法会将position设回0,并将limit设置成之前position的值
position现在用于标记读的位置,limit表示之前写进了多少个数据
rewind()方法:返回
将position设回0,所以你可以重读Buffer中的所有数据。limit保持不变,仍表示能从Buffer中读取多少个元素
clear() 与 compact() 方法
clear(); //清空
? compact(); //清空,会保留未读取的数据
?
如果调用的是clear方法,position将设回0,limit将被设置为capacity的值,(清空),但是数据还在
如果Buffer中有未读完的数据,调用clear方法,数据将被遗忘
调用compact方法,所有未读数据拷贝到Buffer起始处,然后将position设到最后一个未读元素正后面
mark 与 reset 方法
mark(); //做标记
? reset(); //返回上一个标记
?
----Channel----(通道)
所有的IO在NIO中都从一个Channel开始,Channel有点像流,数据可以从Channel读到Buffer中,也可以从Buffer写到Channel
FileChannel的基本使用
FileCHannel是一个连接到文件的通道,可以通过文件通道读写文件
FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下
1、创建FileChannel
第一种:使用一个InputStream、OutputStream或RandomAccessFile来获取一个FileChannel对象
RandomAccessFile raf = new RandomAccessFile("a.txt","rw");
FileChannel fc = raf.getChannel();
第二种:jdk1.7后才能使用,FileChannel.open 方法
FileChannel fc = FileChannel.open(Paths.get("a.txt"),StandardOpenOption.READ);
2、从FIleChannel读取数据
首先,分配一个Buffer,从FileChannel中读取的数据将被读到Buffer中,然后调用FileChannel.read 方法,read返回int值表示有多少字节被读到了Buffer中,如果返回-1 表示到了文件尾
ByteBuffer buf = ByteBuffer.allocate(1024);
while (channel.read(buf)>0){
//切换为读取模式
buf.flip();
String data = new String(buf.array(),0,buf.limit());
System.out.println(data);
buf.clear()
}
3、向FileChannel写数据
使用FileChannel.write()方法向FileChannel写数据
ByteBuffer buf = ByteBuffer.allocate(1024);
buf.put("好好学习".getBytes());
channel.write(buf);
使用内存映射文件复制大文件:
FileChannel readChannel = FileChannel.open(Paths.get("aaa.avi"), StandardOpenOption.READ);
FileChannel writeChannel = FileChannel.open(Paths.get("bbb.avi"), StandardOpenOption.WRITE);
MappedByteBuffer map = readChannel.map(FileChannel.MapMode.READ_ONLY, 0, readChannel.size());
writeChannel.write(map);
----Selector和非阻塞网络编程----
传统的网络编程
TCP ServerSocket Socket( accept 阻塞式)
UDP DatagramSocket DatagramPacket ( receive )
1、ServerSocketChannel、SocketChannel实现阻塞式网络编程
ServerSocketChannel是一个基于通道的Socket监听器(服务器套接字),等同于ServerSocket类。
SocketChannel是一个基于通道的客户端套接字,等同于Socket类
1)、使用NIO实现TCP的服务器端(阻塞式)
//1创建ServerSocketChannel
ServerSocketChannel listener = ServerSocketChannel.open();
//2绑定端口号 SocketAddress 套接字地址(InetAddress+端口号)、InetAddress(ip地址,不包含端口号)
listener.bind(new InetSocketAddress("10.9.21.242",1234));
//3监听
SocketChannel socketChannel = listener.accept();
//4读取数据
ByteBuffer buf = ByteBuffer.allocate(1024*4);
while (socketChannel.read(buf)>0){
buf.flip();
String data = new String(buf.array(),0,buf.limit());
System.out.println(socketChannel.getRemoteAddress()+"说:"+data);
buf.clear();
}
//5关闭
socketChannel.close();
listener.close();
2)、使用NIO实现TCP的客户端(阻塞式)
//1创建客户端套接字通道
SocketChannel sc = SocketChannel.open(new InetSocketAddress("10.9.21.224",1234));
//2写入
ByteBuffer buf = ByteBuffer.allocate(1024*4);
buf.put("好久不见".getBytes());
buf.flip();
sc.write(buf);
//3关闭
sc.close();
2、Selector简介
要使用Selector,得向Selector注册Channel,然后调用它的Select()方法,这个方法会一直阻塞到某个注册的通道有事建就绪,一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。
?
Selector 允许单线程处理多个Channel。仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通道。事实上,可以只用一个线程处理所有的通道,这样会大量的减少线程之间上下文切换的开销。
选择器(Selector):Selector选择器类管理者一个被注册的通道集合的信息和它们的就绪状态,通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态
可选择通道(SelectableSchannel):这个抽象类提供了实现通道的可选择性所需要的公共方法。它是所有支持就绪检查的通道类的父类。因为FileChannel类没有继承SelectableChannel,因此它不是可选通道,而所有socket通道都是可选择的,SocketChannel和ServerSocketChannel是SelectableChannel的子类
选择键(SelectionKey):选择键封装了特定的通道与特定的选择器的注册关系,选择键对象被SelectableChannel.register()返回并提供一个表示这种注册关系的标记。选择键包含了两个比特集(以整数的形式进行编码),选择键支持四种操作类型:
Connect 连接
Accept 接收请求
Read 读
Write 写
Java中定义了四个常量来表示这四种操作类型:
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
1)使用一个线程处理多个客户端请求(使用非阻塞式网络编程)(服务器端)
//1创建ServerSocketChannel
ServerSocketChannel listener=ServerSocketChannel.open();
//2绑定地址
listener.bind(new InetSocketAddress("10.9.21.146", 9999));
//3设置为非阻塞式
listener.configureBlocking(false);
//4创建Selector(轮询器)
Selector selector=Selector.open();
//5注册轮询器
listener.register(selector, SelectionKey.OP_ACCEPT);
//6轮询处理
while(selector.select()>0){ //select();阻塞方法,当轮询器有事件发生则返回个数
//7获取所有的事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
while(it.hasNext()){
SelectionKey selectionKey = it.next();
//判断事件类型
if(selectionKey.isAcceptable()){ //新的客户连接
//8处理请求
SocketChannel socketChannel = listener.accept();
//9设置非阻塞模式
socketChannel.configureBlocking(false);
//10注册轮询器
socketChannel.register(selector, SelectionKey.OP_READ);
}else if(selectionKey.isReadable()){// 新的数据发送过来
//接受数据
//11获取发生读取事件的SocketChannel
SocketChannel sc = (SocketChannel) selectionKey.channel();
//12创建ByteBuffer
ByteBuffer buf=ByteBuffer.allocate(1024*4);
int len=-1;
//read()不会阻塞,没有数据返回0,有数据返回数据个数 ,如果客户端关闭或结束返回-1
while((len=sc.read(buf))>0){
buf.flip();//切换读取模式
String data=new String(buf.array(),0,buf.limit());
InetSocketAddress isa = (InetSocketAddress) sc.getRemoteAddress();
System.out.println(isa.getAddress()+"说:"+data);
buf.clear();
}
if(len==-1){
sc.close();//关闭SocketChannel
}
}
//把处理过的事件删除
it.remove();
}
}
2)客户端(使用非阻塞式网络编程)
//1创建SocketChannel
SocketChannel sc=SocketChannel.open(new InetSocketAddress("10.9.21.146", 9999));
//2设置为非阻塞
sc.configureBlocking(false);
//3写入数据
Scanner input=new Scanner(System.in);
ByteBuffer buffer=ByteBuffer.allocate(1024*4);
while(true){
String d=input.next();
buffer.put(d.getBytes());
buffer.flip();
sc.write(buffer);
buffer.clear();
if(d.equals("baibai")){
break;
}
}
//4关闭
sc.close();