1.什麼是NIOhtml
NIO即New IO,這個庫是在JDK1.4中才引入的。NIO和IO有相同的做用和目的,但實現方式不一樣,NIO主要用到的是塊,因此NIO的效率要比IO高不少。java
在Java API中提供了兩套NIO,一套是針對標準輸入輸出NIO,另外一套就是網絡編程NIOandroid
更多介紹與全面概述,請參見:http://www.iteye.com/magazines/132-Java-NIO#579編程
2.NIO與IO的主要區別數組
// 原先的IO是面向流的,相似水流,是單向的;如今的NIO是面向緩衝的雙向的,相似鐵路,提供一個運輸的通道,實際運輸的是火車(緩衝區)網絡
簡而言之,通道(channel)負責傳輸(打開通往文件的通道),緩衝區(Buffer)負責存儲app
其它特性將會在後續陸續介紹dom
3.NIO的主要內容ide
1.緩衝區的數據存取 工具
初始化分配緩衝區:——經過兩個靜態方法
數據存取:——主要經過put和get方法
要操做緩衝區,必須理解其中四個重要屬性——位於父類Buffer中:
capacity:容量——緩衝區最大存儲數據容量,一旦聲明不能改變(底層數組的限制)
limit:界限——緩衝區中可操做數據的大小(limit後的數據不能操做)
position:位置——緩衝區中正在操做數據的位置
mark:標記——記住當前position的位置,能夠經過reset()恢復到position位置
// 注意這三個屬性都是 Int 類型,表明的是位置(能夠理解爲相似數組下標)
圖解以下:
測試以上幾個屬性以下:
初始狀態:
@Test public void test1() { // 分配一個指定大小的字節緩衝區 ByteBuffer buf = ByteBuffer.allocate(1024); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); }
結果:
使用put存數據:
@Test public void test1() { // 分配一個指定大小的字節緩衝區 ByteBuffer buf = ByteBuffer.allocate(1024); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); String str = "i love the world!"; buf.put(str.getBytes()); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); }
讀數據模式:
@Test public void test1() { // 分配一個指定大小的字節緩衝區 ByteBuffer buf = ByteBuffer.allocate(1024); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); String str = "i love the world!"; // 存數據模式 buf.put(str.getBytes()); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); // 讀數據模式——flip() buf.flip(); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); }
結果:
使用get讀取數據:
@Test public void test1() { // 分配一個指定大小的字節緩衝區 ByteBuffer buf = ByteBuffer.allocate(1024); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); String str = "i love the world!"; // 存數據模式put() buf.put(str.getBytes()); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); // 讀數據模式——flip() buf.flip(); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); // 利用get()讀取緩衝區數據 byte[] dst = new byte[buf.limit()]; buf.get(dst); System.out.println(new String(dst, 0, dst.length)); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); }
結果:
//讀數據的時候遊標position也是會移動過去的,就像讀模式遊標自動到首位
使用rewind()重讀數據(將get讀取時致使的position的位置偏移改回來,使position回到初始位置0)
@Test public void test1() { // 分配一個指定大小的字節緩衝區 ByteBuffer buf = ByteBuffer.allocate(1024); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); String str = "i love the world!"; // 存數據模式put() buf.put(str.getBytes()); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); // 讀數據模式——flip() buf.flip(); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); // 利用get()讀取緩衝區數據 byte[] dst = new byte[buf.limit()]; buf.get(dst); System.out.println(new String(dst, 0, dst.length)); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); // rewind()進行重讀 buf.rewind(); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); }
結果:
使用clear()清空數據緩衝區
@Test public void test1() { // 分配一個指定大小的字節緩衝區 ByteBuffer buf = ByteBuffer.allocate(1024); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); String str = "i love the world!"; // 存數據模式put() buf.put(str.getBytes()); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); // 讀數據模式——flip() buf.flip(); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); // 利用get()讀取緩衝區數據 byte[] dst = new byte[buf.limit()]; buf.get(dst); System.out.println(new String(dst, 0, dst.length)); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); // rewind()進行重讀 buf.rewind(); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); // clear()進行緩衝區清空 buf.clear(); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); }
結果:(位置清零,限制置爲容量,回到最初狀態,但並非真正清空緩衝區,只是相應屬性被重置,緩衝區數據處於「被遺忘」狀態)
使用reset()重置到mark()標記的位置:
@Test public void test1() { // 分配一個指定大小的字節緩衝區 ByteBuffer buf = ByteBuffer.allocate(1024); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); String str = "i love the world!"; // 存數據模式put() buf.put(str.getBytes()); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); // 讀數據模式——flip() buf.flip(); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); // 利用get()讀取緩衝區數據 byte[] dst = new byte[buf.limit()]; buf.get(dst, 0, 6); System.out.println(new String(dst, 0, 6)); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); // 經過mark()標記position位置 buf.mark(); // 繼續讀取,改變position位置 buf.get(dst, 6,4); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); System.out.println(new String(dst, 6, 4)); // 使用reset()恢復position到mark位置 buf.reset(); System.out.println("position:"+buf.position()+" limit:"+buf.limit()+" cap:"+buf.capacity()); }
結果:
//其它例如查看緩衝區可操做數據的方法 hasRemaining()等參見API
2.直接緩衝區與非直接緩衝區
概念:
非直接緩衝區:經過 allocate() 方法分配緩衝區,將緩衝區創建在 JVM 的內存中
直接緩衝區:經過 allocateDirect() 方法分配直接緩衝區,將緩衝區創建在物理內存中。能夠提升效率
關於這方面的詳細介紹,請參見:http://www.cnblogs.com/androidsuperman/p/7083049.html
API中也有相關的判斷直接緩衝區的方法:isDirect()
3.通道(channel)
概念:
通道(Channel):由 java.nio.channels 包定義 的。Channel 表示 IO 源與目標打開的鏈接。 Channel 相似於傳統的「流」。只不過 Channel 自己不能直接訪問數據,Channel 只能與 Buffer 進行交互。
通道是訪問IO服務的導管,負責緩衝區數據的傳輸,配合緩衝區傳輸數據。經過通道,咱們能夠以最小的開銷來訪問操做系統的I/O服務
通道相關的介紹,請參見 風同樣的碼農 的隨筆:http://www.cnblogs.com/chenpi/p/6481271.html
分類:主要是紅箭頭所指處的實現類
獲取通道:——打開通道,打開一條向哪一個文件的通道
getChannel()
靜態方法 open()——1.7後的NIO.2
Files工具類的 newByteChannel()——1.7後的NIO.2
使用通道:
使用方式一進行文件複製:
@Test public void test1() { FileInputStream fis = null; FileOutputStream fos = null; FileChannel fisChannel = null; FileChannel fosChannel = null; try { fis = new FileInputStream(new File("D:\\test\\1.jpg")); fos = new FileOutputStream(new File("D:\\test\\2.jpg")); // 獲取通道 fisChannel = fis.getChannel(); fosChannel = fos.getChannel(); // 分配非直接緩衝區進行數據存取 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // 與IO流相似的讀取思路 while ((fisChannel.read(byteBuffer)) != -1) { // 將讀到的數據進行存儲,必定要切換爲讀模式! byteBuffer.flip();// 切換讀模式 fosChannel.write(byteBuffer); byteBuffer.clear();// 清空緩衝區 } System.out.println("複製完成!"); } catch (IOException e) { e.printStackTrace(); } finally { // 4個平行的if即便某個出現異常,後續的也會關閉。並且後續1.7後獲取通道將會簡化 if (fosChannel != null) { try { // 顯示關閉流 fosChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if (fisChannel != null) { try { // 顯示關閉流 fisChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if (fos != null) { try { // 顯示關閉流 fos.close(); } catch (IOException e) { e.printStackTrace(); } } if (fis != null) { try { // 顯示關閉流 fis.close(); } catch (IOException e) { e.printStackTrace(); } } } }
// 也不須要緩衝流包裝之類了
使用方式二經過內存映射進行文件複製:
@Test public void test2() { FileChannel inChnnel = null; FileChannel outChnnel = null; try { // NIO.2的方式獲取讀通道 inChnnel = FileChannel.open(Paths.get("D:\\test\\2.jpg"), StandardOpenOption.READ); // 因爲outMappedBuf爲讀寫模式,故這裏給outChannel加讀寫兩個模式(CREATE_NEW爲建立新文件) outChnnel = FileChannel.open(Paths.get("D:\\test\\3.jpg"), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE,StandardOpenOption.READ); // 使用內存映射建立直接緩衝區(只有byteBuffer支持),與allocateDirect()原理一致 MappedByteBuffer inMappedBuf = inChnnel.map(FileChannel.MapMode.READ_ONLY, 0, inChnnel.size()); MappedByteBuffer outMappedBuf = outChnnel.map(FileChannel.MapMode.READ_WRITE, 0, inChnnel.size()); // 如今往緩衝區中放數據就直接放到文件中了,無需經過通道讀寫 byte[] dst = new byte[inMappedBuf.limit()]; inMappedBuf.get(dst); outMappedBuf.put(dst); } catch (IOException e) { e.printStackTrace(); } finally { if (outChnnel != null) { try { outChnnel.close(); } catch (IOException e) { e.printStackTrace(); } } if (inChnnel != null) { try { inChnnel.close(); } catch (IOException e) { e.printStackTrace(); } } } }
關於其中得MappedByteBuffer,請參見:http://blog.csdn.net/z69183787/article/details/53695590
事實上,NIO提供了通道之間傳輸數據得方式:
@Test public void test3() { FileChannel inChannel = null; FileChannel outChannel = null; try { // NIO.2的方式獲取讀通道 inChannel = FileChannel.open(Paths.get("D:\\test\\3.jpg"), StandardOpenOption.READ); // 因爲outMappedBuf爲讀寫模式,故這裏給outChannel加讀寫兩個模式 outChannel = FileChannel.open(Paths.get("D:\\test\\4.jpg"), StandardOpenOption.CREATE, StandardOpenOption.WRITE,StandardOpenOption.READ); // 通道之間傳送數據 inChannel.transferTo(0, inChannel.size(), outChannel); } catch (IOException e) { e.printStackTrace(); } finally { if (outChannel != null) { try { outChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if (inChannel != null) { try { inChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } }
FileChannel經常使用方法:
4.分散和彙集
分散讀取(Scattering Reads):
將通道中的數據分散到各個緩衝區中
@Test public void test4() { RandomAccessFile raf = null; FileChannel rafChannel = null; try { raf = new RandomAccessFile("D:\\test\\hello.txt", "rw"); rafChannel = raf.getChannel(); // 分配緩衝區 ByteBuffer byteBuffer1 = ByteBuffer.allocate(400); ByteBuffer byteBuffer2 = ByteBuffer.allocate(201); ByteBuffer byteBuffer3 = ByteBuffer.allocate(800); ByteBuffer[] bufs = {byteBuffer1, byteBuffer2, byteBuffer3}; // 分散讀取 rafChannel.read(bufs); // 切換讀模式,讀取緩衝區數據 for (ByteBuffer buf : bufs) { buf.flip(); System.out.println(new String(buf.array(), 0, buf.limit())); System.out.println("=================================="); } } catch (IOException e) { e.printStackTrace(); } finally { if (raf != null) { try { raf.close(); } catch (IOException e) { e.printStackTrace(); } } if (rafChannel != null) { try { rafChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } }
彙集寫入(Gathering Writes):
將多個緩衝區中的數據彙集到通道中
示例與分散相似,也是操做緩衝區數組
5.字符集(Charset)
編碼:編成字節碼(數組)——將字符串轉換成字節數組
解碼:解釋成看的懂的字符串——將字節數組解成字符串
查看全部可用的字符集:
@Test public void test5() { // 全部可用的字符集 Map<String, Charset> map = Charset.availableCharsets(); for (Map.Entry<String, Charset> entry : map.entrySet()) { System.out.println("key:"+entry.getKey()+" value:"+entry.getValue()); } }
編碼:
@Test public void test6() { // 獲取字符集的字符集對象 Charset cs1 = Charset.forName("GBK"); // 獲取編碼器與解碼器 CharsetEncoder encoder = cs1.newEncoder(); CharsetDecoder decoder = cs1.newDecoder(); // 進行編碼與解碼(其實也就是CharBuffer與ByteBuffer之間的轉換) CharBuffer charBuffer = CharBuffer.allocate(1024); charBuffer.put("金陵豈是池中物,一遇風雲便化龍。"); // 切換讀模式,開始編碼 charBuffer.flip(); ByteBuffer byteBuffer = null; try { byteBuffer = encoder.encode(charBuffer); } catch (IOException e) { e.printStackTrace(); } for (int i = 0; i < 32; i++){ System.out.println(byteBuffer.get()); } }
結果是字節數組的形式(數值):
解碼查看:
@Test public void test6() { // 獲取字符集的字符集對象 Charset cs1 = Charset.forName("GBK"); // 獲取編碼器與解碼器 CharsetEncoder encoder = cs1.newEncoder(); CharsetDecoder decoder = cs1.newDecoder(); // 進行編碼與解碼(其實也就是CharBuffer與ByteBuffer之間的轉換) CharBuffer charBuffer = CharBuffer.allocate(1024); charBuffer.put("金陵豈是池中物,一遇風雲便化龍。"); // 切換讀模式,開始編碼 charBuffer.flip(); ByteBuffer byteBuffer = null; try { byteBuffer = encoder.encode(charBuffer); CharBuffer charBuffer1 = decoder.decode(byteBuffer); System.out.println(charBuffer1.toString()); } catch (IOException e) { e.printStackTrace(); } // for (int i = 0; i < 32; i++){ // System.out.println(byteBuffer.get()); // } }