java的NIO主要有3个特性Channel、buffer、selector来保证I/O高可复用性,其中最重要的是buffer和selector操作。详细教材查看 jakob jenkov教材:http://tutorials.jenkov.com/java-nio/index.html
a、 channel:有点像流的管道,NIO从channel里面获取、发送数据。java的I/O已经从底层被NIO实现了一次,所以性能上和纯粹的NIO中使用channel没有太大的区别。
channel的类型主要就下面几种:
FileChannel //文件 DatagramChannel //UDP SocketChannel //socket ServerSocketChannel //socket服务
b、 Buffer:就是在内存开辟的空间,用来临时存放数据。这里的buffer是以bytebuffer为父类实现的HeapByteBuffer。
它有2个特点:1、读写控制、2、按着单个byte位来操作。
一个Buffer有3个中有的参数:
position:当前位置
1、写:从当前可以写的地址开始(第一次从0开始),随着写入的增大。写的时候最大为capacity-1。
2、读:从0开始、随着读开始移动增大。最大读取到limit
limit:写模式下为capacity。当转换为读模式,则limit=position(写入的个数)
capacity:一个buffer的固定大小。
如下示意图
例子:
#有一个Buffer是70字节 1、buffer.allocate(70):capacity=70、position=0、limit=70 1、写如40个字节:capacity=70、position=40、limit=70 2、写转化为读:buffer.flip();capacity=70,position=0,limit=40 3、读30字节:capacity=70、position=30、limit=40 4、读转写: buffer.clear():所有剩余数据都清空。capacity=70,position=0,limit=70 buffer.compact():将剩余的所有数据复制到buffer起始。capacity=70,position=10,limit=70
一个fileChannel的例子
public static void main(String[] args) { try { RandomAccessFile aFile = new RandomAccessFile("D:/project/test/nio/1.txt", "rw"); FileChannel fileChannel = aFile.getChannel(); // buffer ByteBuffer buf = ByteBuffer.allocate(48); while (fileChannel.read(buf) != -1) { buf.flip();// 写模式切换成读模式 while (buf.hasRemaining()) { System.out.println(buf.get()); } buf.clear(); } aFile.close();//从写切换到读 } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
c、buffer的分类:
buffer默认是在JVM开辟空间、而NIO比BIO在数据处理方面有一个有点:不会将数据从逻辑主存复制到JVM主存
直接从内核态的数据区读取数据,不用在copy到jvm堆内存。
多个数据Buffer不用组合成一个,直接就程序处理了。
发送的时候,直接发送。
bytebuffer开辟空间的两个方法。第二个方法直接在主存开辟空间、不需要在JVM中操作
//直接在JVM开辟空间 public static ByteBuffer allocate(int capacity) { if (capacity < 0) throw new IllegalArgumentException(); return new HeapByteBuffer(capacity, capacity); } //直接在内存开辟空间 public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity); }
普通的I/O调用都会阻塞等待,直到文件数据准备就行才能使用。而NIO则是通过一个单独的线程不对的去询问系统I/O数据是否准备好了。准备好后,就可以通过存放在Selector线程中的key(处理线程的引用)来处理。通过这种主动启动线程的方式,避免了掉多线程同时启动,通过CPU切换切换询问状态的方式,节约了CPU的开销。
a、开启Selector
Selector selector = Selector.open();
b、注册channel到selector
channel.configureBlocking(false);//设置非阻塞。也就是说不能和FileChannel一起使用了 SelectionKey key=channel.register(selector,SelecotionKey.OP_READ);
SelectionKey:是channel在selector上的注册标签。当I/O事件准备好的时候,就会返回需要事件类型:
事件类型 | 注册类型 | 类型判断 |
监听:accept(服务器) | SelectionKey.OP_ACCEPT | SelctionKey.isAcceptable() |
连接:connect(客服端、服务器) | SelectionKey.OP_CONNECT | SelctionKey.isConnectable() |
读:read(客服端、服务器) | SelectionKey.OP_READ | SelctionKey.isReadable() |
写:write(客服端、服务器) | SelectionKey.OP_WRITE | SelctionKey.isWritable() |
selectionKey可以获取channel、selector,以及添加和获取附加对象
//这个就是获取channel、处理数据的方式。 Channel channel = selectionKey.channel(); //这个就是获取selector,用来处理完事件后重新注册 Selector selector = selectionKey.selector(); //添加、获取附加对象。 selectionKey.attch(theObject); Object attachObj = selectionKey.attachment();
(slectionKey可以看作是一个存放channel、附加对象的容,和我们每次注册到selector中需要处理的事件方式。形成了一个映射关系。只要事件达成我们就可以继续处理)
c、从selector中获取事件
while(true){ //第一步:获取事件,只有当有事件处理的时候,selecotr会返回一个大于0的值 int readyEvents = selector.select(); if(readyEvents==0) continue; //第二步:获取事件标签 Set<SelectionKey> keys = slector.slectionKeys(); //第三步:处理事件 Iterator keyIteraotrs = keys.interator(); while(keyIterators.hasNext()){ SelectionKey selectionKey = keyIterators.next(); //当获取事件的时候,需要从selector删掉。 keys.remove(selectionKey); if(selectionKey.isAcceptable()){ //do something 。。。 //注册 }else if(selectionKey.isConnectable()){ //do something 。。。 //注册 }else if(selectionKey.isReadable()){ //do something 。。。 //注册 }else if(selectionKey.isWritable()){ //do something 。。。 //注册 } } }
a、文件
try { RandomAccessFile fromFile = new RandomAccessFile("D:/project/test/nio/1.txt", "rw"); FileChannel fromFileChannel = fromFile.getChannel(); RandomAccessFile toFile = new RandomAccessFile("D:/project/test/nio/2.txt", "rw"); FileChannel toFileChannel = toFile.getChannel(); //不同channel的数据传送 toFileChannel.transferFrom(fromFileChannel, 0, fromFileChannel.size()); // buffer // ByteBuffer buf = ByteBuffer.allocate(48); // while (fromFileChannel.read(buf) != -1) { // // buf.flip();// 写模式切换成读模式 // while (buf.hasRemaining()) { // System.out.println(buf.getChar()); // } // // buf.clear();// 从写切换到读 // } fromFile.close(); toFile.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
b、serverSocket
// 1.开启Selector Selector selector = Selector.open(); ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 2、设置channel的模式(阻塞-false、非阻塞-true) serverSocketChannel.socket().bind(new InetSocketAddress(80)); serverSocketChannel.configureBlocking(false); // 2、注册channel到Selector serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { int readyChannel = selector.select(); if (readyChannel == 0) continue; Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> keyIterators = keys.iterator(); while (keyIterators.hasNext()) { SelectionKey selectionKey = keyIterators.next(); keys.remove(selectionKey); SocketChannel socketChannel = null; if (selectionKey.isAcceptable()) { // 访问事件(这里需要获取的serverSocketChannel) serverSocketChannel = (ServerSocketChannel) selectionKey.channel(); socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ | SelectionKey.OP_CONNECT); } else if (selectionKey.isConnectable()) { socketChannel = (SocketChannel) selectionKey.channel(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ); } else if (selectionKey.isReadable()) { socketChannel = (SocketChannel) selectionKey.channel(); socketChannel.configureBlocking(false); ByteBuffer buff = ByteBuffer.allocateDirect(1024); while (socketChannel.read(buff) != -1) { buff.flip(); while (buff.hasRemaining()) { System.out.println(buff.getChar()); } buff.clear(); } buff=null; socketChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_WRITE); } else if (selectionKey.isWritable()) { socketChannel = (SocketChannel) selectionKey.channel(); socketChannel.configureBlocking(false); ByteBuffer buff = ByteBuffer.allocateDirect(1024); buff.put(new String("hello , i am Nio !").getBytes()); buff.flip(); socketChannel.write(buff); buff=null; socketChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_READ); } } }
c、socket
// 1.开启Selector Selector selector = Selector.open(); SocketChannel socketChannel = SocketChannel.open(); // 2、设置channel的模式(阻塞-false、非阻塞-true) socketChannel.configureBlocking(false); socketChannel.connect(new InetSocketAddress("http://localhost/", 80)); // 2、注册channel到Selector socketChannel.register(selector, SelectionKey.OP_CONNECT); while (true) { // 返回事件 int readyChannels = selector.select(); if (readyChannels == 0) continue; // 有事件发生 // 返回的key集合(返回的是一个channel集合) Set<SelectionKey> keys = selector.selectedKeys(); // 对每一个channel进行处理 Iterator<SelectionKey> keyIterators = keys.iterator(); while (keyIterators.hasNext()) { SelectionKey selectionKey = keyIterators.next(); keys.remove(selectionKey); // 连接发生了 if (selectionKey.isConnectable()) { // 需要将key(channel)移除、因为selector是条件触发,如果不删除。下次事件来了会发生问题 // 从key中获取channel socketChannel = (SocketChannel) selectionKey.channel(); socketChannel.configureBlocking(false); // 重新注册(前面删除了注册) socketChannel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ); } else if (selectionKey.isReadable()) { // 读取数据 socketChannel = (SocketChannel) selectionKey.channel(); socketChannel.configureBlocking(false); ByteBuffer buff = ByteBuffer.allocateDirect(1024); while (socketChannel.read(buff) != -1) { buff.flip(); while (buff.hasRemaining()) { System.out.println(buff.getChar()); } buff.clear(); } buff=null; socketChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_WRITE); } else if (selectionKey.isWritable()) { // 写入数据 socketChannel = (SocketChannel) selectionKey.channel(); socketChannel.configureBlocking(false); ByteBuffer buff = ByteBuffer.allocateDirect(1024); buff.put(new String("hello , i am Nio !").getBytes()); buff.blip(); socketChannel.write(buff); buff=null; socketChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_READ); } } }
d、Pipe:两个线程之间的数据传送。传送用sink通道、接受用source通道
public void writeToPipeChannel() throws IOException { Pipe pipe = Pipe.open(); Pipe.SinkChannel sinkChannel = pipe.sink(); String newData = "New String to write to file ... " + System.currentTimeMillis(); ByteBuffer buf = ByteBuffer.allocate(48); buf.clear(); buf.put(newData.getBytes()); buf.flip(); while (buf.hasRemaining()) { sinkChannel.write(buf); } } public void readFromPipeChannel() throws IOException { Pipe pipe = Pipe.open(); Pipe.SourceChannel sourceChannel = pipe.source(); ByteBuffer buf = ByteBuffer.allocateDirect(48); buf.clear(); while (sourceChannel.read(buf) != -1) { buf.flip(); while (buf.hasRemaining()) { System.out.println(buf.getChar()); } buf.clear(); } }
Pipe原理图示