同步和異步的區別最大在於異步的話調用者不須要等待處理結果,被調用者會經過回調等機制來通知調用者其返回結果。html
舉個生活中簡單的例子,你媽媽讓你燒水,小時候你比較笨啊,在那裏傻等着水開(同步阻塞)。java
等你稍微再長大一點,你知道每次燒水的空隙能夠去幹點其餘事,而後只須要時不時來看看水開了沒有(同步非阻塞)。git
後來,大家家用上了水開了會發出聲音的壺,這樣你就只須要聽到響聲後就知道水開了,在這期間你能夠隨便幹本身的事情,你須要去倒水了(異步非阻塞)。github
在 Java NIO 中負責數據的存取。緩衝區就是數組。用於存儲不一樣數據類型的數據。數組
/* * 根據數據類型不一樣(boolean 除外),提供了相應類型的緩衝區: * ByteBuffer * CharBuffer * ShortBuffer * IntBuffer * LongBuffer * FloatBuffer * DoubleBuffer * * 上述緩衝區的管理方式幾乎一致,經過 allocate() 獲取緩衝區 */
Buffer 全部子類提供了兩個用於數據操做的方法:get() 與 put()
獲取 Buffer 中的數據網絡
放入數據到 Buffer 中app
其它方法dom
// 分配緩衝區:JVM 內存中 ByteBuffer buf = ByteBuffer.allocate(1024); // 分配直接緩衝區:本地內存中 ByteBuffer bufDirect = ByteBuffer.allocateDirect(1024); // 是否爲直接緩衝區 System.out.println(buf.isDirect()); // 直接字節緩衝區還能夠經過 FileChannel 的 map() 方法將文件區域直接映射到內存中來建立。該方法返回 MappedByteBuffer。
非直接緩衝區異步
直接緩衝區ide
import org.junit.Test; import java.nio.ByteBuffer; public class TestBuffer { @Test public void markAndReset() { String str = "abcde14693090"; // 分配直接緩衝區 ByteBuffer buf = ByteBuffer.allocateDirect(1024); // 存入數據 buf.put(str.getBytes()); // 切換到讀取模式 buf.flip(); byte[] dst = new byte[buf.limit()]; buf.get(dst, 0, 2); System.out.println(new String(dst, 0, 2)); System.out.println(buf.position()); // mark() : 標記 buf.mark(); buf.get(dst, 2, 2); System.out.println(new String(dst, 2, 2)); System.out.println(buf.position()); // reset() : 恢復到 mark 的位置 buf.reset(); System.out.println(buf.position()); // 判斷緩衝區中是否還有剩餘數據 if (buf.hasRemaining()) { // 獲取緩衝區中能夠操做的數量 System.out.println(buf.remaining()); } } @Test public void getAndPut() { String str = "abcde"; //1. 分配一個指定大小的緩衝區 ByteBuffer buf = ByteBuffer.allocate(1024); System.out.println("allocate():" + buf.position() + "\t" + buf.limit() + "\t" + buf.capacity()); //2. 利用 put() 存入數據到緩衝區中 buf.put(str.getBytes()); System.out.println("put():" + buf.position() + "\t" + buf.limit() + "\t" + buf.capacity()); //3. 切換讀取數據模式 buf.flip(); System.out.println("flip():" + buf.position() + "\t" + buf.limit() + "\t" + buf.capacity()); //4. 利用 get() 讀取緩衝區中的數據 byte[] dst = new byte[buf.limit()]; buf.get(dst); System.out.println(new String(dst, 0, dst.length)); System.out.println("get():" + buf.position() + "\t" + buf.limit() + "\t" + buf.capacity()); //5. rewind() : 可重複讀 buf.rewind(); System.out.println("rewind():" + buf.position() + "\t" + buf.limit() + "\t" + buf.capacity()); //6. clear() : 清空緩衝區. 可是緩衝區中的數據依然存在,只是處於「被遺忘」狀態 buf.clear(); System.out.println("clear():" + buf.position() + "\t" + buf.limit() + "\t" + buf.capacity()); // 獲取單個字符 System.out.println((char) buf.get()); } }
表示 IO 源與目標節點打開的鏈接,在 Java NIO 中負責緩衝區中數據的傳輸,相似於傳統的「流」。只不過 Channel 自己不能直接訪問數據,只能與 Buffer 進行交互。
/* * java.nio.channels.Channel 接口的主要實現類: * |--FileChannel:用於讀取、寫入、映射和操做文件的通道。 * |--SocketChannel:經過 TCP 讀寫網絡中的數據。 * |--DatagramChannel:經過 UDP 讀寫網絡中的數據通道。 * |--ServerSocketChannel:能夠監聽新進來的 TCP 鏈接,對每個新進來的鏈接都會建立一個 SocketChannel。 */
/* * 獲取通道的一種方式是對支持通道的對象調用 getChannel() 方法。 * 支持通道的類以下: * 本地 IO: * |--FileInputStream * |--FileOutputStream * |--RandomAccessFile * 網絡 IO: * |--Socket * |--ServerSocket * |--DatagramSocket * * 在 JDK 1.7 中, NIO.2 針對各個通道的實現類提供了靜態方法 open() 來獲取通道。 * 在 JDK 1.7 中, NIO.2 的 Files 工具類的靜態方法 newByteChannel() 也能夠獲取通道。 */
/* * 將 Buffer 中數據寫入 Channel * int bytesWritten = inChannel,write(buf) * 從 Channel 讀取數據到 Buffer * int bytesRead = inChannel.read(buf) * * Channel 之間的數據傳輸(將數據從源通道傳輸到其餘 Channel 中) * transferFrom() * transferTo() */
複製文件的幾種方式
// 通道之間的數據傳輸(直接緩衝區) @Test public void channelCopy() throws IOException { FileChannel inChannel = FileChannel.open(Paths.get("D:/123.txt"), StandardOpenOption.READ); FileChannel outChannel = FileChannel.open(Paths.get("D:/456.txt"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE); // inChannel.transferTo(0, inChannel.size(), outChannel); outChannel.transferFrom(inChannel, 0, inChannel.size()); inChannel.close(); outChannel.close(); } // 使用直接緩衝區完成文件的複製(內存映射文件) @Test public void byteBuffCopy() { long start = System.currentTimeMillis(); try (FileChannel inChannel = FileChannel.open(Paths.get("D:/123.txt"), StandardOpenOption.READ); FileChannel outChannel = FileChannel.open(Paths.get("D:/456.txt"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE)) { // 內存映射文件 MappedByteBuffer inMappedBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size()); MappedByteBuffer outMappedBuf = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size()); // 直接對緩衝區進行數據的讀寫操做 byte[] dst = new byte[inMappedBuf.limit()]; inMappedBuf.get(dst); outMappedBuf.put(dst); } catch (IOException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("耗費時間爲:" + (end - start)); } // 利用通道完成文件的複製(非直接緩衝區) @Test public void fileCopy() { long start = System.currentTimeMillis(); try (FileInputStream fis = new FileInputStream("D:/123.txt"); FileOutputStream fos = new FileOutputStream("D:/456.txt"); // 獲取通道 FileChannel inChannel = fis.getChannel(); FileChannel outChannel = fos.getChannel()) { // 分配指定大小的緩衝區 ByteBuffer buf = ByteBuffer.allocate(1024); // 將通道中的數據存入緩衝區中 while (inChannel.read(buf) != -1) { // 切換讀取數據的模式 buf.flip(); // 將緩衝區中的數據寫入通道中 outChannel.write(buf); // 清空緩衝區 buf.clear(); } } catch (IOException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("Time:" + (end - start)); }
分散讀取(Scattering Reads):從 Channel 中讀取的數據「分散」到多個 Buffer 中。(按照緩衝區的順序,從 Channel 中讀取的數據依次將 Buffer 填滿。)
彙集寫入(Gathering Writes):將多個 Buffer 中的數據「彙集」到 Channel。(按照緩衝區的順序,寫入 position 和 limit 之間的數據到 Channel 。)
使用分散和彙集來複制文件部份內容
// 分散和彙集 @Test public void scatterAndGather() throws IOException { RandomAccessFile raf1 = new RandomAccessFile("D:/123.txt", "rw"); // 獲取通道 FileChannel channel1 = raf1.getChannel(); // 分配指定大小的緩衝區 ByteBuffer buf1 = ByteBuffer.allocate(100); ByteBuffer buf2 = ByteBuffer.allocate(1024); // 分散讀取 ByteBuffer[] bufs = {buf1, buf2}; channel1.read(bufs); // 轉換模式 for (ByteBuffer byteBuffer : bufs) { byteBuffer.flip(); } System.out.println(new String(bufs[0].array(), 0, bufs[0].limit())); System.out.println("-----------------"); System.out.println(new String(bufs[1].array(), 0, bufs[1].limit())); // 彙集寫入 RandomAccessFile raf2 = new RandomAccessFile("D:/456.txt", "rw"); FileChannel channel2 = raf2.getChannel(); channel2.write(bufs); }
/* * 字符集:Charset * 編碼:字符串 -> 字節數組 * 解碼:字節數組 -> 字符串 */ @Test public void testCharset() throws IOException { // 指定字符集 Charset cs1 = Charset.forName("GBK"); CharBuffer cBuf = CharBuffer.allocate(1024); cBuf.put("字符集"); cBuf.flip(); //獲取編碼器 CharsetEncoder ce = cs1.newEncoder(); //編碼 ByteBuffer bBuf = ce.encode(cBuf); System.out.println(Arrays.toString(bBuf.array())); bBuf.flip(); //獲取解碼器 CharsetDecoder cd = cs1.newDecoder(); //解碼 cBuf = cd.decode(bBuf); System.out.println(cBuf.toString()); } // Java 支持的字符集 @Test public void getCharset() { Map<String, Charset> map = Charset.availableCharsets(); for (Entry<String, Charset> entry : map.entrySet()) { System.out.println(entry.getKey() + "=" + entry.getValue()); } }
https://snailclimb.gitee.io/javaguide/#/java/BIO-NIO-AIO
https://snailclimb.gitee.io/javaguide/#/java/Java%20IO%E4%B8%8ENIO
https://cyc2018.github.io/CS-Notes/#/notes/Java%20IO?id=%e4%b8%83%e3%80%81nio