Java nio的核心组成
Channel
Buffer
Selector
channel和buffer
概念: 在nio中,数据基本上都是从channel中开始,channel有些像流,数据即可以从channel读入到buffer中,同样也可以从buffer中写入到channel中
* java nio中channel的主要实现列表
1.FileChannel
2.DatagramChannel
3.socketChannel
4.ServerSocketChannel
这些类覆盖UDP和TCP网路IO,以及文件IO
* java nio中buffer的主要实现列表
1.ByteBuffer
2.CharBuffer
3.DoubleBuffer
4.FloatBuffer
5.IntBuffer
6.LongBuffer
7.ShortBuffer
8.MappedByteBuffer :内存映射文件
selector
selector允许单线程处理多个channel,要使用selector,你得先去向selector注册channel,然后调用它的select()方法,这个方法会一直阻塞到某个注册事件有事件就绪,一旦这个方法进行返回,那么线程就可以处理这些事件。
Buffer详解
Java Nio中buffer是用于与NIO通道进行交互的,数据从通道中读入到缓冲区,然后从缓冲区写入到通道中。缓冲区的本质就是一块能够写入数据,然后从中读取数据的内存。该块内存被封装成nio buffer对象,并提供了一组方法,用来方便的访问该块内存。
1.Buffer的基本用法 - 使用byteBuffer读写数据
(1)写数据到Buffer中 (相当于把数据写到内存中)
(2)调用flip()方法
(3)从Buffer中读取数据
(4)调用clear或者compact()方法
当向buffer写入数据时,buffer会记录下写了多少数据。一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式。在读模式下,可以读取之前写入到buffer的所有数据。一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用clear()或compact()方法
clear(): 清空整个缓冲区
compact(): 清空已经读过的数据,任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面
2.Buffer实现原理
buffer中有3个重要的概念
*position指针
*capacity指针
*limit指针
capacity: 作为一个内存块,Buffer有一个固定的大小值,只能往里面写capacity个byte、long、char等类型,一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。- 固定值
position:
(1)写数据状态
当你写数据到Buffer中时,position表示当前的位置,初始的position为0,当一个byte、long等数据写到Buffer后,position会向前移动到下一个可以插入数据的buffer单元,position最大值是 capacity – 1
(2)读数据状态
当读取数据时,也是从某个特定的位置开始读取,当将Buffer从写的模式切换到读的模式时,position会被重置成0,当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。
limit:
(1)在写模式下
Buffer的limit表示你最多能往Buffer里写多少数据。写模式下,limit等于Buffer的capacity
(2)切换到读的模式下
limit表示最多能读取到多少的数据。因此,当buffer切换到读的模式时,limit会设置成写模式下的position值
3.clear和compact方法实现原理
clear方法: 把position指向0,它并不会去清除里面的数据,所以无法分辨出哪些数据已经读取过,哪些数据未读取
compact方法: 把position设置到未读数据的前面,方便下次读取的时候继续进行。compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面,limit依然设置成capacity的值
package com.learn.nio; import org.junit.Test; import java.io.RandomAccessFile; import java.nio.channels.FileChannel; import java.nio.ByteBuffer; /** * Created by zhouxi on 2017/5/30 0030. * * @author xzhou */ public class LearnBuffer { @Test public void testBufferPosition() { try { RandomAccessFile rfile = new RandomAccessFile("E:\\code\\learn\\nio\\testBuffer.txt", "rw"); FileChannel channel = rfile.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(48); // 定义48个字节的buffer空间 int readByte = channel.read(buffer); // 把数据写入到buffer中 System.out.println("---------写的模式----------"); System.out.println("readByte的值为:" + readByte); System.out.println("position:" + buffer.position()); System.out.println("limit:" + buffer.limit()); System.out.println("capacity:" + buffer.capacity()); buffer.flip(); // 由写的模式转变成读的模式 System.out.println("---------读的模式-----------"); System.out.println("position:" + buffer.position()); System.out.println("limit:" + buffer.limit()); System.out.println("capacity:" + buffer.capacity()); while(buffer.hasRemaining()) { System.out.print((char)buffer.get()); } System.out.println("---------当读取值的时候-----------"); System.out.println("position:" + buffer.position()); System.out.println("limit:" + buffer.limit()); System.out.println("capacity:" + buffer.capacity()); buffer.rewind(); System.out.println("--------rewind()模式下-----------"); System.out.println("position:" + buffer.position()); System.out.println("limit:" + buffer.limit()); System.out.println("capacity:" + buffer.capacity()); buffer.clear(); // clear方法,它并不会清除buffer中的数据 System.out.println("--------clear()模式下-----------"); System.out.println("position:" + buffer.position()); System.out.println("limit:" + buffer.limit()); System.out.println("capacity:" + buffer.capacity()); System.out.println("获取第一个数: " + (char)buffer.get()); System.out.println("position:" + buffer.position()); buffer.put("123".getBytes()); System.out.println("position:" + buffer.position()); buffer.flip(); while(buffer.hasRemaining()) { System.out.print((char)buffer.get()); } buffer.clear(); buffer.position(3); System.out.println("\n-----------清除其中的数据-----------"); while(buffer.hasRemaining()) { System.out.print((char)buffer.get()); } buffer.compact(); System.out.println("---------capacity--------------------"); System.out.println("position: " + buffer.position()); } catch (Exception e) { e.printStackTrace(); } } }
scatter/gather(分散和聚集)
1.概念
(1)scatter(分散): 从Channel中读取是指在读的操作时,将读取的数据写入到多个buffer中,因此,Channel将从Channel读取的数据分散到多个buffer中。
(2)gather(聚集): 写入Channel是指在写的操作中,将多个buffer的数据同时写入到同一个Channel中,因为Channel将多个buffer的数据聚集再进行发送的使用场景。
package com.learn.nio; import org.junit.Test; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; /** * Created by zhouxi on 2017/5/31 0031. * * @author xzhou */ public class LearnScatterGather { /** * scatter和gather的使用 - 分散和聚合 */ @Test public void testScatterGather() throws IOException { // scatter分散读取到buffer ByteBuffer header = ByteBuffer.allocate(48); ByteBuffer body = ByteBuffer.allocate(128); ByteBuffer[] bufferArray = {header, body}; RandomAccessFile rfile = new RandomAccessFile("E:\\code\\learn\\nio\\testBuffer.txt", "rw"); FileChannel fileChannel = rfile.getChannel(); fileChannel.read(bufferArray); // gather聚集到channel中 ByteBuffer head = ByteBuffer.allocate(128); ByteBuffer buBody = ByteBuffer.allocate(1024); ByteBuffer[] array = {head, buBody}; fileChannel.write(buBody); // buffer向buffer数组写数据 } }
缓冲区分片和数据共享
buffer可以通过slice方法创建一个子缓冲区,即它创建了一个新的缓冲区,新缓冲区和原来的缓冲区共享一部分数据。接下来我们看下面代码例子,通过slice方法创建一个子缓冲区,然后修改子缓冲区的数据,看看原先的缓冲区数据会如何变化。
package com.learn.nio; import org.junit.Test; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; /** * 缓冲区分片 * * Created by zhouxi on 2017/6/1 0001. */ public class LearnSlice { @Test public void testSlice() { try { RandomAccessFile rfile = new RandomAccessFile("E:\\code\\learn\\nio\\testBuffer.txt", "rw"); FileChannel channel = rfile.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(48); // 定义48个字节的buffer空间 channel.read(buffer); // 把数据写入到channel中 buffer.flip(); while (buffer.hasRemaining()) { System.out.print((char)buffer.get()); } System.out.println(); // 数据分片的效果 buffer.position(3); buffer.limit(8); ByteBuffer newBuffer = buffer.slice(); // newBuffer变成子缓冲区 System.out.println("--------newBuffer开始分片数据列表-------"); while(newBuffer.hasRemaining()) { System.out.print((char)newBuffer.get()); } /* * 进行数据共享;子缓冲区共享数据 * */ newBuffer.flip(); System.out.println("\nnewBuffer position:" + newBuffer.position()); for(int i = 0; i < newBuffer.capacity(); i++) { byte b = newBuffer.get(i); b += 3; newBuffer.put(i, b); } while(newBuffer.hasRemaining()) { System.out.print((char)newBuffer.get()); } buffer.flip(); System.out.println(); buffer.limit(21); System.out.println("buffer position:" + buffer.position()); System.out.println("buffer limit:" + buffer.limit()); System.out.println("buffer capacity:" + buffer.capacity()); while(buffer.hasRemaining()) { System.out.print((char)buffer.get()); } } catch(Exception e) { e.printStackTrace(); } } }
只读缓冲区
只读缓冲区非常简单 ― 你可以读取它们,但是不能向它们写入,可以通过调用缓冲区的asReadOnlyBuffer() 方法,将任何常规缓冲区转换为只读缓冲区,这个方法返回一个与原缓冲区完全相同的缓冲区(并与其共享数据),只不过它是只读的。
只读缓冲区对于保护数据很有用;在将缓冲区传递给某个对象的方法时,你无法知道这个方法是否会修改缓冲区中的数据。创建一个只读的缓冲区可以保证该缓冲区不会被修改。不能将只读的缓冲区转换为可写的缓冲区!
channel通道之间的传输
可以通过方法将数据直接从一个channel传输到另外一个channel
package com.learn.nio; import org.junit.Test; import java.io.RandomAccessFile; import java.nio.channels.FileChannel; /** * Created by zhouxi on 2017/5/31 0031. * * @author xzhou */ public class LearnTransferFrom { @Test public void testTransfer() { try { RandomAccessFile rfile = new RandomAccessFile("E:\\code\\learn\\nio\\testBuffer.txt","rw"); FileChannel fromChannel = rfile.getChannel(); RandomAccessFile afile = new RandomAccessFile("E:\\code\\learn\\nio\\transfer.txt","rw"); FileChannel toChannel = afile.getChannel(); long position = 0; long count = fromChannel.size(); System.out.println("fromChannel size:....." + count); /* * transferFrom方法: 将数据从源通道传输到FileChannel * * position表示在toChannel在哪个位置进行开始插入数据 */ toChannel.transferFrom(fromChannel,2,10); // nio netty System.out.println("toChannel size:......" + toChannel.size()); /* * transferTo方法: FileChannel传输到其他的channel中 * * position是fromChannel中的从哪个位置截取数据进行插入 * */ System.out.println("toChannel的position的位置:" + toChannel.position()); fromChannel.transferTo(0,5,toChannel); System.out.println("toChannel size: " + toChannel.size()); } catch (Exception e) { e.printStackTrace(); } } }