NIO
概述
- 称为No-Blocking IO或New IO,是从jdk1.4开始引进的一套新的IO,为所有基本类型(boolean除外)提供缓冲支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络
IO历史
- BIO->NIO->AIO
IO操作模式
-
PIO
- 所有IO操作由NICPU处理,CPU占用率比较高
-
DMA
- CPU将IO操作的控制权交给DMA控制器,只能以固定的方式读写,CPU空闲做其他工作
-
Channel
- 能执行有限通道指令的IO控制器,代替CPU管理控制外设
- 通道有自己的指令系统,是一个协处理器,具有更强的独立处理数据输入和输出的能力
组成
Buffer缓冲区
-
特点
- 用于和NIO通道交互
- 本质上是一块可以写入数据也可以读出数据的内存
-
子类
- ByteBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
- CharBuffer
-
核心方法
-
static ByteBuffer allocate(int capacity)
- 在堆中开辟空间,间接缓冲区
-
static ByteBuffer allocateDirect(int capcity)
- 在直接内存中开辟空间,直接缓冲区
-
ByteBuffer put(byte[] src)
- 通过Buffer的put()方法写到Buffer里
-
Buffer flip()
- 将Buffer从写模式切换到读模式。调用flip()方法会将position设为0,并将limit设为之前的position
-
ByteBuffer get(byte[] des)
- 从Buffer中读数据
-
Buffer clear()
- 将Buffer清空。将position设回为0,limit设为capacity
-
Buffer compact()
- 将所有未读的数据拷贝到缓冲区起始处。将position设为最后一个未读元素的后面,limit设为capacity
-
-
原理
- 0<=position<=limit<=capacity
- 初始化时position=0,limit=capacity
- 添加元素时h[position++]=e
- 切换模式时,limit=position,position=0
- 压缩时position=未读元素后一个,limit = capacity
- clear时,position=0,limit=capacity并没有将元素移除,数据处理遗留,装填元素后覆盖
-
基本使用
public class TestBuffer { public static void main(String[] args) { //1创建缓冲区,并使用Put方法存放数据 ByteBuffer buffer=ByteBuffer.allocate(1024); // byte[] 1024 //ByteBuffer byteBuffer=ByteBuffer.allocateDirect(1024); //2并使用Put方法存放数据 String data="我爱java,I love Java"; buffer.put(data.getBytes()); //3反转缓冲区(把写入模式转成读取模式) flip(); buffer.flip(); //4读取 //byte b = buffer.get(); byte[] buf=new byte[buffer.limit()]; buffer.get(buf); //System.out.println((char)b); System.out.println(new String(buf,0,buf.length)); //5清空 buffer.clear(); } }
Channel通道接口
-
类似流。数据可以从Buffer传到Channel,也可以从Channel传到Buffer
-
实现类
-
FileChannel
-
特点
- FileChannel是一个连接到文件的通道。可以通过通道读写文件
- FileChannel无法设置非阻塞模式
-
创建方式
-
使用文件字节流或RandomAccessFile类获得
FileChannel fileChannel = new FileOutputStream("d:\\channel").getChannel(); // FileChannel fileChannel = new RandomAccessFile("d:\\channel.txt", "rw").getChannel();
-
使用Channels工具类
FileChannel writableByteChannel = (FileChannel) Channels.newChannel(new FileOutputStream("d:\\channel.txt"));
-
jdk1.7后使用FileChannel的open方法
FileChannel channel=FileChannel.open(Paths.get("d:\\channel.txt"), StandardOpenOption.CREATE,StandardOpenOption.WRITE,StandardOpenOption.APPEND);
-
-
解决中文乱码问题
public static void read() throws Exception{ //1 创建通道 //FileChannel channel=FileChannel.open(Paths.get("d:\\channel.txt"),StandardOpenOption.READ); FileChannel channel = (FileChannel) Channels.newChannel(new FileInputStream("d:\\channel.txt")); //2 读取 ByteBuffer buffer=ByteBuffer.allocate(1024); //2.1创建解码器 CharsetDecoder charsetDecoder = Charset.forName("utf-8").newDecoder(); //2.2创建CharBuffer CharBuffer charBuffer=CharBuffer.allocate(1024); while(channel.read(buffer)>0){ buffer.flip(); //解码 charsetDecoder.decode(buffer, charBuffer, false); charBuffer.flip(); System.out.println(charBuffer.toString()); //buffer.clear(); buffer.compact(); charBuffer.clear(); } //3关闭 channel.close(); }
-
内存映射文件
public class MapFile { public static void main(String[] args) throws Exception{ //1创建FileChannel FileChannel read=FileChannel.open(Paths.get("d:\\001.wmv"), StandardOpenOption.READ); FileChannel write=FileChannel.open(Paths.get("d:\\002.wmv"),StandardOpenOption.CREATE,StandardOpenOption.WRITE); //2直接内存映射文件 MappedByteBuffer map1 = read.map(FileChannel.MapMode.READ_ONLY, 0, 1024*1024*100); //3内存映射文件写入文件 write.write(map1); MappedByteBuffer map2=read.map(FileChannel.MapMode.READ_ONLY, 1024*1024*100, read.size()-(1024*1024*100)); write.write(map2); //4关闭 read.close(); write.close(); System.out.println("复制完毕"); } }
-
-
ServerSocketChannel
-
SocketChannel
-
DatagramChannel
-
Selector选择器
-
特点
- Selector提供了询问通道是否已经准备好执行每个IO的能力
- Selector允许单线程处理多个Channel
- 仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通 道。这样会大量的减少线程之间上下文切换的开销
-
Selector类管理着一个被注册的通道集合的信息和他们的就绪状态。通道是和选择器一起注册的,并且使用选择器来更新通道的就绪状态
-
SelectionKey
-
选择键封装了特定通道和特定选择器的注册关系
-
选择键支持的四种操作类型
- SelectionKey.OP_CONNECT
- SelectionKey.OP_ACCEPT
- SelectionKey.OP_READ
- SelectionKey.OP_WRITE
-
与BIO的区别
NIO阻塞编程
public class Server {
public static void main(String[] args) throws Exception {
//1 创建服务器套接字通道
ServerSocketChannel listener = ServerSocketChannel.open();
//2 绑定地址和端口号
listener.bind(new InetSocketAddress("10.0.139.49", 9999));
//3 监听
System.out.println("服务器已启动...");
SocketChannel socketChannel = listener.accept();
//4读取
//4.1创建缓冲区
ByteBuffer buffer=ByteBuffer.allocate(1024);
//4.2读取
socketChannel.read(buffer);
//5处理数据
buffer.flip();
String data=new String(buffer.array(),0,buffer.limit());
System.out.println(socketChannel.getRemoteAddress()+"说:"+data);
buffer.clear();
//6关闭
socketChannel.close();
listener.close();
}
}
public class Client {
public static void main(String[] args)throws Exception {
//1创建SocketChannle
SocketChannel socketChannel=SocketChannel.open(new InetSocketAddress("10.0.139.49", 9999));
//2写入
//2.1创建缓冲区
ByteBuffer buffer=ByteBuffer.allocate(1024);
buffer.put("好久不见".getBytes());
buffer.flip();
//2.2写入
socketChannel.write(buffer);
buffer.clear();
//3关闭通道
socketChannel.close();
}
}
NIO非阻塞编程
public class ChatServer {
public static void main(String[] args) {
ServerSocketChannel listener=null;
try {
//1创建ServerSocketChannel
listener=ServerSocketChannel.open();
//2绑定地址
listener.bind(new InetSocketAddress("10.0.139.49", 8899));
//3设置为非阻塞式模式
listener.configureBlocking(false);
//4创建Selector(选择器)
Selector selector=Selector.open();
//5把通道注册到选择器,并指定注册的事件
listener.register(selector, SelectionKey.OP_ACCEPT);
//6轮询(select()阻塞方法,没有事件发送阻塞)
System.out.println("服务器已启动...");
while(selector.select()>0){
//7处理
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
while(it.hasNext()){
SelectionKey selectionKey = it.next();
//8判断selectionKey
if(selectionKey.isAcceptable()){
//表示有客户端请求,接收请求
SocketChannel socketChannel = listener.accept(); //不会阻塞
System.out.println(socketChannel.getRemoteAddress()+"进入了聊天室...");
//设置非阻塞式模式
socketChannel.configureBlocking(false);
//注册到selector
socketChannel.register(selector, SelectionKey.OP_READ);
}else if(selectionKey.isReadable()){
//读取数据
SocketChannel channel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer=ByteBuffer.allocate(1024);
int len=0;
try {
while((len=channel.read(buffer))>0){ //不会阻塞
buffer.flip();
String data=new String(buffer.array(),0,buffer.limit());
System.out.println(channel.getRemoteAddress()+"说:"+data);
buffer.clear();
}
//开启线程回复
if(len==-1){// 客户端关闭
System.out.println(channel.getRemoteAddress()+"退出了");
channel.close();
}
} catch (IOException e) {
System.out.println(channel.getRemoteAddress()+"异常退出了");
channel.close();
}
}
//已经处理过了键删除了
it.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
listener.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public class ChatClient {
public static void main(String[] args) throws Exception {
//1创建SocketChannel
SocketChannel socketChannel=SocketChannel.open(new InetSocketAddress("10.0.139.49", 8899));
//2设置为非阻塞模式
socketChannel.configureBlocking(false);
//3控制台录入并写出
Scanner input=new Scanner(System.in);
while(true){
String data=input.nextLine();
ByteBuffer buffer=ByteBuffer.allocate(1024);
buffer.put(data.getBytes());
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
if(data.equals("886")){
break;
}
}
//4关闭
socketChannel.close();
}
}