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.....