Java NIO 简介
java
Java NIO,即Java New IO,是Java IO的2.0版本,since from JDK1.4。JDK1.4之前提供的都是传统的缓存
IO,即咱们常常使用的InputStream/OutputStream/Reader/Writer等。对于传统IO,咱们能够利用流的app
装饰功能,使其具备Buffer功能,本质上是利用byte[]完成的。而Java NIO单独把Buffer的功能抽取出dom
来,并且还提供了不少特性,下面咱们一块儿来看看吧~ide
Buffer家族性能
看下java.nio.Buffer的子类:
学习
对于基本数据类型,基本上都有与之对应的Buffer类,而ByteBuffer最经常使用,ByteBuffer下面又有2个特殊的子类。this
ByteBufferspa
先来一段代码,有点感性认识吧:3d
public static void main(String[] args) { //分配Buffer缓冲区大小 其本质是指定byte[]大小 ByteBuffer buffer = ByteBuffer.allocate(10); printBuffer(buffer); buffer.put((byte)1); printBuffer(buffer); buffer.flip(); printBuffer(buffer); } public static void printBuffer(Buffer buffer){ System.out.println("--------"); System.out.println("position : " + buffer.position()); System.out.println("limit : " + buffer.limit()); System.out.println("capacity : " + buffer.capacity()); System.out.println("--------"); }
既然要使用ByteBuffer,必然要知道如何建立它!常见的建立ByteBuffer的方式有以下几种:
分配堆内存的方式
public static ByteBuffer allocate(int capacity) { if (capacity < 0) throw new IllegalArgumentException(); return new HeapByteBuffer(capacity, capacity); }
分配直接内存,即C HEAP的方式
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity); }
须要注意的是,DirectByteBuffer是MappedByteBuffer的子类!
直接包装byte[]造成ByteBuffer
public static ByteBuffer wrap(byte[] array, int offset, int length){ try { return new HeapByteBuffer(array, offset, length); } catch (IllegalArgumentException x) { throw new IndexOutOfBoundsException(); } }
因为ByteBuffer是atstract class,所以咱们使用的都是它的2个具体子类:HeapByteBuffer/MappedByteBuffer。
咱们能够跟踪下HeapByteBuffer/DirectByteBuffer的构造方法,发现它们其实就作了一件事:
初始化byte[]以及一些属性,好比mark,position,limit,capacity。
position vs limit vs capacity
Java NIO中ByteBuffer除了有byte[]以外,还提供了一些属性,这样相比传统IO,操做更加灵活方便。
首先来讲,capacity是byte[]的容量大小,通常是初始化好后,就不会在变化了的,而position,limit这2个属性,会随着对缓冲区的read/write操做而发生变化。
position:下一个应该读取的位置
limit:在byte[]中有效读取位置的最大值
下面,咱们来作一个例子具体说明:利用ByteBuffer来拷贝文件
public static void closeStream(Closeable... closeable){ for(Closeable c : closeable){ if(c != null){ try { c.close(); } catch (IOException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws IOException { FileInputStream srcFile = new FileInputStream("E:\\tmp\\Shell学习笔记.pdf"); FileOutputStream destFile = new FileOutputStream("E:\\tmp\\Shell学习笔记COPY.pdf"); ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 1024); FileChannel in = srcFile.getChannel(); FileChannel out = destFile.getChannel(); while(in.read(byteBuffer) != -1){ byteBuffer.flip(); out.write(byteBuffer); byteBuffer.clear(); } closeStream(srcFile,destFile); }
其实,经过上面的代码,咱们已经揭示了Java NIO的3个核心概念中的2个:缓冲区与通道
之前,对于传统IO,咱们面对的是流,操做的是一个个字节,而NIO,咱们面对的是缓冲区,操做的将是一个个块。
具体来讲,是这样的:
读取输入,好比读取文件,那么应该经过FileInputStream/RandomAccessFile进行获取通道;建立缓冲区buffer;而后调用通道的read操做,将数据读入buffer。写操做,则相反。
上面代码中,调用了buffer的2个重要方法:flip()/clear(),他们是干吗的呢?
直接看源码:
public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }
flip并无作什么,只是将limit的位置设置为position,而position的位置回到0
public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this; }
clear则更加简单,回到最初的状态!
为何要调用他们来改变limit/positon呢?
要知道,若是channel.read(buffer),这个操做,是要改变position的;若是咱们继续otherchannel.write(buffer),那么将写入的是未知数据。最好的方式,是将position如今所处的位置交给limit,而position置为0,这样就到达了将缓冲区的内容从新读出!而调用clear的目的,就更加单纯,就是但愿在read(buffer)的时候从0开始!
mark是来作什么的?
在buffer中,mark默认是被置为-1的。咱们先来看看与mark有直接关系的2个方法:
public final Buffer mark() { mark = position; return this; }
public final Buffer reset() { int m = mark; if (m < 0) throw new InvalidMarkException(); position = m; return this; }
经过mark()咱们利用mark记住了position,而经过reset()咱们将position的值还原到mark。
那么事实就清楚了,咱们能够先调用mark()记住当前POSITION的位置,而后咱们去作其余的一些事情,最后经过reset()在找回POSITION的位置开始下一步!
allocateDirect
allocateDirect方式建立的是一个DirectByteBuffer,直接内存,这是用来作什么的呢?
咱们能够先来看看常规的IO操做流程:
很显然,JVM只是普通的用户进程,可以和IO设备打交道的是KERNEL空间,JVM须要从KERNEL拷进INPUT DATA,拷出OUTPUT DATA到KERNEL。固然,频繁的拷进拷出操做是费时的。而DirectBuffer将跳过JVM拷进拷出这一层。
MappedByteBuffer:内存映射IO
咱们常常是在内存中分配一段空间,操做完毕后,写入磁盘;那么能不能在磁盘上直接分配一段空间,供咱们进行IO操做呢?MappedByteBuffer就是这样的,它会在磁盘上分配一段缓冲区,对缓存区的操做就是对磁盘的操做!
来看看“高性能”的拷贝文件方式:利用MappedByteBuffer
public static void main(String[] args) throws IOException { //FileInputStream fis = new FileInputStream("E:\\tmp\\Shell学习笔记.pdf"); //FileOutputStream fos = new FileOutputStream("E:\\tmp\\Shell学习笔记COPY.pdf"); RandomAccessFile fis = new RandomAccessFile("E:\\tmp\\Shell学习笔记.pdf","r"); RandomAccessFile fos = new RandomAccessFile("E:\\tmp\\Shell学习笔记COPY.pdf","rw"); FileChannel in = fis.getChannel(); FileChannel out = fos.getChannel(); long size = in.size(); ByteBuffer buffer = out.map(MapMode.READ_WRITE, 0, size); in.read(buffer); closeStream(fis,fos); }
能够看得出,先利用FileChannel的map方法获取一个可读、可写的position=0,大小为size的MappedByteBuffer,对这个buffer的操做就将直接反映到磁盘上!
【注意到,利用FileInputStream获取到的CHANNEL是只读的,利用FileOutputStream获取到的CHANNEL是只写的,而map获取BUFFER须要读写权限,所以要利用RandromAccessFile来进行读写设置!】
到这里,JAVA NIO就介绍了一部份内容了,我也从知道有NIO,到开始实践NIO了,HAPPY.....