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



