<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                # JAVA NIO之文件通道 ## 1.簡介 通道是 Java NIO 的核心內容之一,在使用上,通道需和緩存類(ByteBuffer)配合完成讀寫等操作。與傳統的流式 IO 中數據單向流動不同,通道中的數據可以雙向流動。通道既可以讀,也可以寫。這里我們舉個例子說明一下,我們可以把通道看做水管,把緩存看做水塔,把文件看做水庫,把水看做數據。當從磁盤中將文件數據讀取到緩存中時,就是從水庫向水塔里抽水。當然,從磁盤里讀取數據并不會將讀取的部分從磁盤里刪除,但從水庫里抽水,則水庫里的水量在無補充的情況下確實變少了。當然,這只是一個小問題,大家不要扣這個細節哈,繼續往下說。當水塔中存儲了水之后,我們可以用這些水燒飯,澆花等,這就相當于處理緩存的數據。過了一段時間后,水塔需要進行清洗。這個時候需要把水塔里的水放回水庫中,這就相當于向磁盤中寫入數據。通過這里例子,大家應該知道通道是什么了,以及有什么用。既然知道了,那么我們繼續往下看。 Java NIO 出現在 JDK 1.4 中,由于 NIO 效率高于傳統的 IO,所以 Sun 公司從底層對傳統 IO 的實現進行了修改。修改的方式就是在保證兼容性的情況下,使用 NIO 重構 IO 的方法實現,無形中提高了傳統 IO 的效率。 ## [](http://www.tianxiaobo.com/2018/03/24/JAVA-NIO%E4%B9%8B%E6%96%87%E4%BB%B6%E9%80%9A%E9%81%93/#2基本操作)2.基本操作 通道類型分為兩種,一種是面向文件的,另一種是面向網絡的。具體的類聲明如下: * FileChannel * DatagramChannel * SocketChannel * ServerSocketChannel 正如上列表,NIO 通道涵蓋了文件 IO,TCP 和 UDP 網絡 IO 等通道類型。本文我們先來說說文件通道。 ### [](http://www.tianxiaobo.com/2018/03/24/JAVA-NIO%E4%B9%8B%E6%96%87%E4%BB%B6%E9%80%9A%E9%81%93/#21-創建通道)2.1 創建通道 FileChannel 是一個用于連接文件的通道,通過該通道,既可以從文件中讀取,也可以向文件中寫入數據。與SocketChannel 不同,FileChannel 無法設置為非阻塞模式,這意味著它只能運行在阻塞模式下。在使用FileChannel 之前,需要先打開它。由于 FileChannel 是一個抽象類,所以不能通過直接創建而來。必須通過像 InputStream、OutputStream 或 RandomAccessFile 等實例獲取一個 FileChannel 實例。 ``` FileInputStream fis = new FileInputStream(FILE_PATH); FileChannel channel = fis.getChannel(); FileOutputStream fos = new FileOutputStream(FILE_PATH); FileChannel channel = fis.getChannel(); RandomAccessFile raf = new RandomAccessFile(FILE_PATH , "rw"); FileChannel channel = raf.getChannel(); ``` ### [](http://www.tianxiaobo.com/2018/03/24/JAVA-NIO%E4%B9%8B%E6%96%87%E4%BB%B6%E9%80%9A%E9%81%93/#22-讀寫操作)2.2 讀寫操作 讀寫操作比較簡單,這里直接上代碼了。下面的代碼會先向文件中寫入數據,然后再將寫入的數據讀出來并打印。代碼如下: ``` // 獲取管道 RandomAccessFile raf = new RandomAccessFile(FILE_PATH, "rw"); FileChannel rafChannel = raf.getChannel(); // 準備數據 String data = "新數據,時間: " + System.currentTimeMillis(); System.out.println("原數據:\n" + " " + data); ByteBuffer buffer = ByteBuffer.allocate(128); buffer.clear(); buffer.put(data.getBytes()); buffer.flip(); // 寫入數據 rafChannel.write(buffer); rafChannel.close(); raf.close(); // 重新打開管道 raf = new RandomAccessFile(FILE_PATH, "rw"); rafChannel = raf.getChannel(); // 讀取剛剛寫入的數據 buffer.clear(); rafChannel.read(buffer); // 打印讀取出的數據 buffer.flip(); byte[] bytes = new byte[buffer.limit()]; buffer.get(bytes); System.out.println("讀取到的數據:\n" + " " + new String(bytes)); rafChannel.close(); raf.close(); ``` 上面的代碼輸出結果如下: ![-w572](https://blog-pictures.oss-cn-shanghai.aliyuncs.com/15218023308513.jpg) ### [](http://www.tianxiaobo.com/2018/03/24/JAVA-NIO%E4%B9%8B%E6%96%87%E4%BB%B6%E9%80%9A%E9%81%93/#23-數據轉移操作)2.3 數據轉移操作 我們有時需要將一個文件中的內容復制到另一個文件中去,最容易想到的做法是利用傳統的 IO 將源文件中的內容讀取到內存中,然后再往目標文件中寫入。現在,有了 NIO,我們可以利用更方便快捷的方式去完成復制操作。FileChannel 提供了一對數據轉移方法 - transferFrom/transferTo,通過使用這兩個方法,即可簡化文件復制操作。 ``` public static void main(String[] args) throws IOException { RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw"); FileChannel fromChannel = fromFile.getChannel(); RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw"); FileChannel toChannel = toFile.getChannel(); long position = 0; long count = fromChannel.size(); // 將 fromFile 文件找那個的數據轉移到 toFile 中去 System.out.println("before transfer: " + readChannel(toChannel)); fromChannel.transferTo(position, count, toChannel); System.out.println("after transfer : " + readChannel(toChannel)); fromChannel.close(); fromFile.close(); toChannel.close(); toFile.close(); } private static String readChannel(FileChannel channel) throws IOException { ByteBuffer buffer = ByteBuffer.allocate(32); buffer.clear(); // 將 channel 讀取位置設為 0,也就是文件開始位置 channel.position(0); channel.read(buffer); // 再次將文件位置歸零 channel.position(0); buffer.flip();//用于buffer緩存對象內容填寫后,轉為讀模式 byte[] bytes = new byte[buffer.limit()]; buffer.get(bytes); return new String(bytes); } ``` ![-w521](https://blog-pictures.oss-cn-shanghai.aliyuncs.com/15215257042945.jpg) 通過上面的代碼,我們可以明顯感受到,利用 transferTo 減少了編碼量。那么為什么利用 transferTo 可以減少編碼量呢?在解答這個問題前,先來說說程序讀取數據和寫入文件的過程。 我們現在所使用的 PC 操作系統,將內存分為了內核空間和用戶空間。操作系統的內核和一些硬件的驅動程序就是運行在內核空間內,而用戶空間就是我們自己寫的程序所能運行的內存區域。這里,當我們調用 read 從磁盤中讀取數據時,內核會首先將數據讀取到內核空間中,然后再將數據從內核空間復制到用戶空間內。也就是說,我們需要通過內核進行數據中轉。同樣,寫入數據也是如此。系統先從用戶空間將數據拷貝到內核空間中,然后再由內核空間向磁盤寫入。相關示意圖如下: ![](https://blog-pictures.oss-cn-shanghai.aliyuncs.com/15215607014407.jpg) 與上面的數據流向不同,FileChannel 的 transferTo 方法底層基于 sendfile64(Linux 平臺下)系統調用實現。sendfile64 會直接在內核空間內進行數據拷貝,免去了內核往用戶空間拷貝,用戶空間再往內核空間拷貝這兩步操作,因此提高了效率。其示意圖如下: ![](https://blog-pictures.oss-cn-shanghai.aliyuncs.com/15215608614648.jpg) 通過上面的講解,大家應該知道了 transferTo 和 transferFrom 的效率會高于傳統的 read 和 write 在效率上的區別。區別的原因在于免去了內核空間和用戶空間的相互拷貝,雖然內存間拷貝的速度比較快,但涉及到大量的數據拷貝時,相互拷貝的帶來的消耗是不應該被忽略的。 講完了背景知識,咱們再來看看 FileChannel 是怎樣調用 sendfile64 這個函數的。相關代碼如下: ``` public long transferTo(long position, long count, WritableByteChannel target) throws IOException { // 省略一些代碼 int icount = (int)Math.min(count, Integer.MAX_VALUE); if ((sz - position) &lt; icount) icount = (int)(sz - position); long n; // Attempt a direct transfer, if the kernel supports it if ((n = transferToDirectly(position, icount, target)) &gt;= 0) return n; // Attempt a mapped transfer, but only to trusted channel types if ((n = transferToTrustedChannel(position, icount, target)) &gt;= 0) return n; // Slow path for untrusted targets return transferToArbitraryChannel(position, icount, target); } private long transferToDirectly(long position, int icount, WritableByteChannel target) throws IOException { // 省略一些代碼 long n = -1; int ti = -1; try { begin(); ti = threads.add(); if (!isOpen()) return -1; do { n = transferTo0(thisFDVal, position, icount, targetFDVal); } while ((n == IOStatus.INTERRUPTED) &amp;&amp; isOpen()); // 省略一些代碼 return IOStatus.normalize(n); } finally { threads.remove(ti); end (n &gt; -1); } } ``` 從上面代碼(transferToDirectly 方法可以在 openjdk/jdk/src/share/classes/sun/nio/ch/FileChannelImpl.java 中找到)中可以看得出 transferTo 的調用路徑,先是調用 transferToDirectly,然后 transferToDirectly 再調用 transferTo0。transferTo0 是 native 類型的方法,我們再去看看 transferTo0 是怎樣實現的,其代碼在`openjdk/jdk/src/solaris/native/sun/nio/ch/FileChannelImpl.c`中。 ``` JNIEXPORT jlong JNICALL Java_sun_nio_ch_FileChannelImpl_transferTo0(JNIEnv *env, jobject this, jint srcFD, jlong position, jlong count, jint dstFD) { #if defined(__linux__) off64_t offset = (off64_t)position; jlong n = sendfile64(dstFD, srcFD, &amp;offset, (size_t)count); if (n &lt; 0) { if (errno == EAGAIN) return IOS_UNAVAILABLE; if ((errno == EINVAL) &amp;&amp; ((ssize_t)count &gt;= 0)) return IOS_UNSUPPORTED_CASE; if (errno == EINTR) { return IOS_INTERRUPTED; } JNU_ThrowIOExceptionWithLastError(env, "Transfer failed"); return IOS_THROWN; } return n; // 其他平臺的代碼省略 #endif } ``` 如上所示,transferTo0 最終調用了 sendfile64 函數,關于 sendfile64 這個系統調用的詳細說明,請參考 man-page,這里就不展開說明了。 ### [](http://www.tianxiaobo.com/2018/03/24/JAVA-NIO%E4%B9%8B%E6%96%87%E4%BB%B6%E9%80%9A%E9%81%93/#24-內存映射)2.4 內存映射 內存映射這個概念源自操作系統,是指將一個文件映射到某一段虛擬內存(物理內存可能不連續)上去。我們通過對這段虛擬內存的讀寫即可達到對文件的讀寫的效果,從而可以簡化對文件的操作。當然,這只是內存映射的一個優點。內存映射還有其他的一些優點,比如兩個進程映射同一個文件,可以實現進程間通信。再比如,C 程序運行時需要 C 標準庫支持,操作系統將 C 標準庫放到了內存中,普通的 C 程序只需要將 C 標準庫映射到自己的進程空間內就行了,從而可以降低內存占用。以上簡單介紹了內存映射的概念及作用,關于這方面的知識,建議大家去看[《深入理解計算機系統》](https://book.douban.com/subject/5333562/)關于內存映射的章節,講的很好。 Unix/Linux 操作系統內存映射的系統調用`mmap`,Java 在這個系統調用的基礎上,封裝了 Java 的內存映射方法。這里我就不一步一步往下追蹤了,大家有興趣可以自己追蹤一下 Java 封裝的內存映射方法的調用棧。下面來簡單的示例演示一下內存映射的用法: ``` // 從標準輸入獲取數據 Scanner sc = new Scanner(System.in); System.out.println("請輸入:"); String str = sc.nextLine(); byte[] bytes = str.getBytes(); RandomAccessFile raf = new RandomAccessFile("map.txt", "rw"); FileChannel channel = raf.getChannel(); // 獲取內存映射緩沖區,并向緩沖區寫入數據 MappedByteBuffer mappedBuffer = channel.map(MapMode.READ_WRITE, 0, bytes.length); mappedBuffer.put(bytes); raf.close(); raf.close(); // 再次打開剛剛的文件,讀取其中的內容 raf = new RandomAccessFile("map.txt", "rw"); channel = raf.getChannel(); System.out.println("\n文件內容:") System.out.println(readChannel(channel)); raf.close(); raf.close(); ``` 上面的代碼從標準輸入中獲取數據,然后將數據通過內存映射緩存寫入到文件中。代碼運行結果如下: ![-w332](https://blog-pictures.oss-cn-shanghai.aliyuncs.com/15216468804678.jpg) 接下來在用 C 代碼演示上面代碼的功能,如下: ``` #include &lt;stdio.h&gt; #include &lt;fcntl.h&gt; #include &lt;sys/mman.h&gt; #include &lt;memory.h&gt; #include &lt;unistd.h&gt; int main() { int dstfd; void *dst; char buf[64], out[64]; int len; printf("Please input:\n"); scanf("%s", buf); len = strlen(buf); // 打開文件 dstfd = open("dst.txt", O_RDWR | O_CREAT | O_TRUNC, S_IRWXU); lseek(dstfd, len - 1, SEEK_SET); write(dstfd, "", 1); // 將文件映射到內存中 dst = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, dstfd, 0); // 將輸入的數據拷貝到映射內存中 memcpy(dst, buf, len); munmap(dst, len); close(dstfd); // 重新打開文件,并輸出文件內容 dstfd = open("dst.txt", O_RDONLY); dst = mmap(NULL, len, PROT_READ, MAP_SHARED, dstfd, 0); bzero(out, 64); memcpy(out, dst, len); printf("\nfile content:\n%s\n", out); munmap(dst, len); close(dstfd); return 0; } ``` 關于 mmap 函數的參數說明,這里就不細說了,大家可以參考 man-page。上面的代碼運行結果如下: ![-w332](https://blog-pictures.oss-cn-shanghai.aliyuncs.com/15216495027516.jpg) 關于內存映射就說到了,更深入的分析需要涉及到很多操作系統層面的東西。我對這些東西了解的也不多,所以就不繼續分析了,慚愧慚愧。 ### [](http://www.tianxiaobo.com/2018/03/24/JAVA-NIO%E4%B9%8B%E6%96%87%E4%BB%B6%E9%80%9A%E9%81%93/#25-其他操作)2.5 其他操作 FileChannel 還有一些其他的方法,這里通過一個表格來列舉這些方法,就不一一展開說明了。如下: | 方法名 | 用途 | | --- | --- | | position | 返回或修改通道讀寫位置 | | size | 獲取通道所關聯文件的大小 | | truncate | 截斷通道所關聯的文件 | | force | 強制將通道中的新數據刷新到文件中 | | close | 關閉通道 | | lock | 對通道文件進行加鎖 | 以上所列舉的方法用起來比較簡單,大家自己寫代碼驗證一下吧,這里就不貼代碼了。 ## [](http://www.tianxiaobo.com/2018/03/24/JAVA-NIO%E4%B9%8B%E6%96%87%E4%BB%B6%E9%80%9A%E9%81%93/#3總結)3.總結 以上章節對 NIO 文件通道的用法和部分方法的實現進行了簡單分析。從上面的分析可以看出,NIO FileChannel 在實現上,實際上是對底層操作系統的一些 API 進行了再次封裝,也就是一層皮。有了這層封裝后,對上就屏蔽了底層 API 的細節,以降低使用難度。Java 為了提高開發效率,屏蔽了操作系統層面的細節。雖然 Java 可以屏蔽這些細節,但作為開發人員,我覺得我們不能也去屏蔽這些細節(雖然不了解這些細節也能寫代碼),有時間還是應該多了解了解這些底層的東西。畢竟要想往更高的層次發展,這些底層的知識必不可少。說到這里,感覺很慚愧,我的技術基礎也很薄弱。大學期間沒有意識到專業基礎課的重要性,學了很多東西,但忽略了基礎。好在工作不久后看了很多牛人的博客,也意識到了自己的不足。現在靜下心來打基礎,算是亡羊補牢吧。 好了,關于文件通道的內容這里就說到這,謝謝大家的閱讀。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看