java.io 核心概念是流,即面向流的編程,在java中一個流只能是輸入流或者輸出流,不能同時具備兩個概念。java
java.nio核心是 selector、Channel、Buffer ,是面向緩衝區(buffer)或者面向塊block。編程
1、Buffer 數組
Buffer自己是一個內存塊,底層是數組,數據的讀寫都是經過Buffer類實現的。即同一個Buffer便可以寫數據也能夠讀數據,經過intBuffer.flip()方法進行Buffer位置狀態的翻轉。JAVA中的8中基本類型都有各自對應的Buffer。app
緩衝區buffer主要是和通道數據交互,即從通道中讀入數據到緩衝區,和從緩衝區中把數據寫入到通道中,經過這樣完成對數據的傳輸。 它經過幾個變量來保存這個數據的當前位置狀態。dom
Buffer中的四個核心變量jvm
get()方法從緩衝區中讀取數據寫入到輸出通道,這會致使position的增長而limit保持不變,但position不會超過limit的值。函數
flip()方法 把limit設置爲當前的position值 而且把position設置爲 0性能
clear()方法將Buffer恢復到初始化狀態this
public class BufferTest { public static void main(String[] args) throws IOException { ByteBufferTest(); } private static void ByteBufferTest(){ //分配新的byte緩衝區,參數爲緩衝區容量 //新緩衝區的當前位置將爲零,其界限(限制位置)將爲其容量,它將具備一個底層實現數組,其數組偏移量將爲零。 ByteBuffer byteBuffer=ByteBuffer.allocate(10); output("初始化緩衝區:",byteBuffer); for(int i=0;i<byteBuffer.capacity()-1;i++){ byteBuffer.put(Byte.parseByte(new SecureRandom().nextInt(20)+"")); } output("寫入緩衝區9個byte:",byteBuffer); byteBuffer.flip(); output("使用flip重置元素位置:",byteBuffer); while (byteBuffer.hasRemaining()){ System.out.print(byteBuffer.get()+"|"); } System.out.print("\n"); output("使用get讀取元素:",byteBuffer); byteBuffer.clear(); output("恢復初始化態clear:",byteBuffer); } private static void output(String step, Buffer buffer) { System.out.println(step + " : "); System.out.print("capacity: " + buffer.capacity() + ", "); System.out.print("position: " + buffer.position() + ", "); System.out.println("limit: " + buffer.limit()); System.out.println("mark: " + buffer.mark()); System.out.println(); } } 初始化緩衝區: : capacity: 10, position: 0, limit: 10 mark: java.nio.HeapByteBuffer[pos=0 lim=10 cap=10] 寫入緩衝區9個byte: : capacity: 10, position: 9, limit: 10 mark: java.nio.HeapByteBuffer[pos=9 lim=10 cap=10] 使用flip重置元素位置: : capacity: 10, position: 0, limit: 9 mark: java.nio.HeapByteBuffer[pos=0 lim=9 cap=10] 讀取元素:1|讀取元素:16|讀取元素:12|讀取元素:0|讀取元素:17|讀取元素:5|讀取元素:4|讀取元素:13|讀取元素:18| 使用get讀取元素後: : capacity: 10, position: 9, limit: 9 mark: java.nio.HeapByteBuffer[pos=9 lim=9 cap=10] 恢復初始化態clear: : capacity: 10, position: 0, limit: 10 mark: java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
ByteBuffer.wrap( array ):將一個現有的數組,包裝爲緩衝區對象spa
buffer.slice():建立子緩衝區,子緩衝區與原緩衝區是數據共享的
buffer.position( 3 ); buffer.limit( 7 ); ByteBuffer slice = buffer.slice();
只讀緩衝區:ByteBuffer readonly = buffer.asReadOnlyBuffer();
只讀緩衝區很是簡單,能夠讀取它們,可是不能向它們寫入數據。能夠經過調用緩衝區的asReadOnlyBuffer()方法,將任何常規緩衝區轉 換爲只讀緩衝區,這個方法返回一個與原緩衝區徹底相同的緩衝區,並與原緩衝區共享數據,只不過它是隻讀的。若是原緩衝區的內容發生了變化,只讀緩衝區的內容也隨之發生變化。
若是嘗試修改只讀緩衝區的內容,則會報ReadOnlyBufferException異常。只讀緩衝區對於保護數據頗有用。建立一個只讀的緩衝區能夠保證該緩衝區不會被修改。只能夠把常規緩衝區轉換爲只讀緩衝區,而不能將只讀的緩衝區轉換爲可寫的緩衝區。
2、直接緩衝區 DirectByteBuffer
直接在堆外分配一個內存(即,native memory)來存儲數據,程序經過JNI直接將數據讀/寫到堆外內存中。由於數據直接寫入到了堆外內存中,因此這種方式就不會再在JVM管控的堆內再分配內存來存儲數據了,也就不存在堆內內存和堆外內存數據拷貝的操做了。這樣在進行I/O操做時,只須要將這個堆外內存地址傳給JNI的I/O的函數就行了。底層的數據實際上是維護在操做系統的內存中,而不是jvm裏,DirectByteBuffer裏維護了一個引用address指向了數據,從而操做數據。實現zero copy(零拷貝)。
間接內存HeapByteBuffer:對於HeapByteBuffer,數據的分配存儲都在jvm堆上,當須要和io設備打交道的時候,會將jvm堆上所維護的byte[]拷貝至堆外內存,而後堆外內存直接和io設備交互。外設之因此要把jvm堆裏的數據copy出來再操做,不是由於操做系統不能直接操做jvm內存,而是由於jvm在進行gc(垃圾回收)時,會對數據進行移動,一旦出現這種問題,外設就會出現數據錯亂的狀況。
直接緩衝區的建立:ByteBuffer buffer = ByteBuffer.allocateDirect( 1024 );
DirectByteBuffer的初始化:
DirectByteBuffer(int cap) { // package-private super(-1, 0, cap, cap); boolean pa = VM.isDirectMemoryPageAligned(); int ps = Bits.pageSize(); long size = Math.max(1L, (long)cap + (pa ? ps : 0)); // 保留總分配內存(按頁分配)的大小和實際內存的大小 Bits.reserveMemory(size, cap); long base = 0; try { // 經過unsafe.allocateMemory分配堆外內存,並返回堆外內存的基地址 base = unsafe.allocateMemory(size); } catch (OutOfMemoryError x) { Bits.unreserveMemory(size, cap); throw x; } unsafe.setMemory(base, size, (byte) 0); if (pa && (base % ps != 0)) { // Round up to page boundary address = base + ps - (base & (ps - 1)); } else { address = base; } // 構建Cleaner對象用於跟蹤DirectByteBuffer對象的垃圾回收,以實現當DirectByteBuffer被垃圾回收時,堆外內存也會被釋放 cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); att = null; }
// Used only by direct buffers // NOTE: hoisted here for speed in JNI GetDirectBufferAddress //address就是堆外內存建立好後返回給JVM的地址,JVM內存須要維護的只是DirectByteBuffer對象,而具體數據的管理是由操做系統來管理的 long address;
兩種方式的效率比較:
private static void directByteBufferTest()throws IOException{ long start=System.currentTimeMillis(); FileInputStream is=new FileInputStream("F:\\logs\\1g.rar"); FileOutputStream fos=new FileOutputStream("F:\\logs\\2g.rar"); FileChannel fcIs,fcOut; fcIs=is.getChannel(); fcOut=fos.getChannel(); ByteBuffer directByteBuffer= ByteBuffer.allocateDirect(2048); while (fcIs.read(directByteBuffer)!=-1){ directByteBuffer.flip(); fcOut.write(directByteBuffer); directByteBuffer.clear(); } is.close(); fos.close(); long end=System.currentTimeMillis(); System.out.println("DirectByteBuffer須要時間:"+(end-start)); } private static void heapByteBufferTest()throws IOException{ long start=System.currentTimeMillis(); FileInputStream is=new FileInputStream("F:\\logs\\1g.rar"); FileOutputStream fos=new FileOutputStream("F:\\logs\\3g.rar"); FileChannel fcIs,fcOut; fcIs=is.getChannel(); fcOut=fos.getChannel(); ByteBuffer directByteBuffer= ByteBuffer.allocate(2048); while (fcIs.read(directByteBuffer)!=-1){ directByteBuffer.flip(); fcOut.write(directByteBuffer); directByteBuffer.clear(); } is.close(); fos.close(); long end=System.currentTimeMillis(); System.out.println("HeapByteBuffer須要時間:"+(end-start)); }
17行輸出:DirectByteBuffer須要時間:30456
35行輸出:HeapByteBuffer須要時間:45285
3、內存映射文件I/O MappedByteBuffer
內存映射文件I/O是一種讀和寫文件數據的方法,它能夠比常規的基於流或者基於通道的I/O快的多。內存映射文件I/O是經過使文件中的數據出現爲 內存數組的內容來完成的,這其初聽起來彷佛不過就是將整個文件讀到內存中,可是事實上並非這樣。通常來講,只有文件中實際讀取或者寫入的部分纔會映射到內存中。
FileChannel提供了map方法來把文件影射爲內存映像文件: MappedByteBuffer map(int mode,long position,long size); 能夠把文件的從position開始的size大小的區域映射爲內存映像文件,映射內存緩衝區是個直接緩衝區,繼承自ByteBuffer,但相對於ByteBuffer,它有更多的優勢 讀取快 寫入快 隨時隨地寫入;
mode指出了 可訪問該內存映像文件的方式:
一、READ_ONLY,(只讀): 試圖修改獲得的緩衝區將致使拋出 ReadOnlyBufferException.(MapMode.READ_ONLY)
二、READ_WRITE(讀/寫): 對獲得的緩衝區的更改最終將傳播到文件;該更改對映射到同一文件的其餘程序不必定是可見的。 (MapMode.READ_WRITE)
三、PRIVATE(專用): 對獲得的緩衝區的更改不會傳播到文件,而且該更改對映射到同一文件的其餘程序也不是可見的;相反,會建立緩衝區已修改部分的專用副本。 (MapMode.PRIVATE)
MappedByteBuffer 中的三個方法:
a. fore();緩衝區是READ_WRITE模式下,此方法對緩衝區內容的修改強行寫入文件
b. load()將緩衝區的內容載入內存,並返回該緩衝區的引用
c. isLoaded()若是緩衝區的內容在物理內存中,則返回真,不然返回假
使用MappedByteBuffer 將數據寫入文件:
private static void mappedOutFile()throws IOException{ String str="I Love MappedByteBuffer"; RandomAccessFile raf = new RandomAccessFile( filePath, "rw" ); FileChannel fc = raf.getChannel(); byte [] msg=str.getBytes("UTF-8"); MappedByteBuffer mbb = fc.map( FileChannel.MapMode.READ_WRITE, 0, msg.length); mbb.put(msg); fc.write(mbb); raf.close(); }