日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JAVA NIO之文件通道

發(fā)布時間:2025/3/21 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JAVA NIO之文件通道 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

1.簡介

通道是 Java NIO 的核心內(nèi)容之一,在使用上,通道需和緩存類(ByteBuffer)配合完成讀寫等操作。與傳統(tǒng)的流式 IO 中數(shù)據(jù)單向流動不同,通道中的數(shù)據(jù)可以雙向流動。通道既可以讀,也可以寫。這里我們舉個例子說明一下,我們可以把通道看做水管,把緩存看做水塔,把文件看做水庫,把水看做數(shù)據(jù)。當從磁盤中將文件數(shù)據(jù)讀取到緩存中時,就是從水庫向水塔里抽水。當然,從磁盤里讀取數(shù)據(jù)并不會將讀取的部分從磁盤里刪除,但從水庫里抽水,則水庫里的水量在無補充的情況下確實變少了。當然,這只是一個小問題,大家不要扣這個細節(jié)哈,繼續(xù)往下說。當水塔中存儲了水之后,我們可以用這些水燒飯,澆花等,這就相當于處理緩存的數(shù)據(jù)。過了一段時間后,水塔需要進行清洗。這個時候需要把水塔里的水放回水庫中,這就相當于向磁盤中寫入數(shù)據(jù)。通過這里例子,大家應該知道通道是什么了,以及有什么用。既然知道了,那么我們繼續(xù)往下看。

Java NIO 出現(xiàn)在 JDK 1.4 中,由于 NIO 效率高于傳統(tǒng)的 IO,所以 Sun 公司從底層對傳統(tǒng) IO 的實現(xiàn)進行了修改。修改的方式就是在保證兼容性的情況下,使用 NIO 重構(gòu) IO 的方法實現(xiàn),無形中提高了傳統(tǒng) IO 的效率。

?2.基本操作

通道類型分為兩種,一種是面向文件的,另一種是面向網(wǎng)絡的。具體的類聲明如下:

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

正如上列表,NIO 通道涵蓋了文件 IO,TCP 和 UDP 網(wǎng)絡 IO 等通道類型。本文我們先來說說文件通道。

?2.1 創(chuàng)建通道

FileChannel 是一個用于連接文件的通道,通過該通道,既可以從文件中讀取,也可以向文件中寫入數(shù)據(jù)。與SocketChannel 不同,FileChannel 無法設置為非阻塞模式,這意味著它只能運行在阻塞模式下。在使用FileChannel 之前,需要先打開它。由于 FileChannel 是一個抽象類,所以不能通過直接創(chuàng)建而來。必須通過像 InputStream、OutputStream 或 RandomAccessFile 等實例獲取一個 FileChannel 實例。

1 2 3 4 5 6 7 8 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();

?2.2 讀寫操作

讀寫操作比較簡單,這里直接上代碼了。下面的代碼會先向文件中寫入數(shù)據(jù),然后再將寫入的數(shù)據(jù)讀出來并打印。代碼如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 // 獲取管道 RandomAccessFile raf = new RandomAccessFile(FILE_PATH, "rw"); FileChannel rafChannel = raf.getChannel();// 準備數(shù)據(jù) String data = "新數(shù)據(jù),時間: " + System.currentTimeMillis(); System.out.println("原數(shù)據(jù):\n" + " " + data); ByteBuffer buffer = ByteBuffer.allocate(128); buffer.clear(); buffer.put(data.getBytes()); buffer.flip();// 寫入數(shù)據(jù) rafChannel.write(buffer);rafChannel.close(); raf.close();// 重新打開管道 raf = new RandomAccessFile(FILE_PATH, "rw"); rafChannel = raf.getChannel();// 讀取剛剛寫入的數(shù)據(jù) buffer.clear(); rafChannel.read(buffer);// 打印讀取出的數(shù)據(jù) buffer.flip(); byte[] bytes = new byte[buffer.limit()]; buffer.get(bytes); System.out.println("讀取到的數(shù)據(jù):\n" + " " + new String(bytes));rafChannel.close(); raf.close();

上面的代碼輸出結(jié)果如下:

?2.3 數(shù)據(jù)轉(zhuǎn)移操作

我們有時需要將一個文件中的內(nèi)容復制到另一個文件中去,最容易想到的做法是利用傳統(tǒng)的 IO 將源文件中的內(nèi)容讀取到內(nèi)存中,然后再往目標文件中寫入。現(xiàn)在,有了 NIO,我們可以利用更方便快捷的方式去完成復制操作。FileChannel 提供了一對數(shù)據(jù)轉(zhuǎn)移方法 - transferFrom/transferTo,通過使用這兩個方法,即可簡化文件復制操作。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 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 文件找那個的數(shù)據(jù)轉(zhuǎn)移到 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();byte[] bytes = new byte[buffer.limit()];buffer.get(bytes);return new String(bytes); }

通過上面的代碼,我們可以明顯感受到,利用 transferTo 減少了編碼量。那么為什么利用 transferTo 可以減少編碼量呢?在解答這個問題前,先來說說程序讀取數(shù)據(jù)和寫入文件的過程。

我們現(xiàn)在所使用的 PC 操作系統(tǒng),將內(nèi)存分為了內(nèi)核空間和用戶空間。操作系統(tǒng)的內(nèi)核和一些硬件的驅(qū)動程序就是運行在內(nèi)核空間內(nèi),而用戶空間就是我們自己寫的程序所能運行的內(nèi)存區(qū)域。這里,當我們調(diào)用 read 從磁盤中讀取數(shù)據(jù)時,內(nèi)核會首先將數(shù)據(jù)讀取到內(nèi)核空間中,然后再將數(shù)據(jù)從內(nèi)核空間復制到用戶空間內(nèi)。也就是說,我們需要通過內(nèi)核進行數(shù)據(jù)中轉(zhuǎn)。同樣,寫入數(shù)據(jù)也是如此。系統(tǒng)先從用戶空間將數(shù)據(jù)拷貝到內(nèi)核空間中,然后再由內(nèi)核空間向磁盤寫入。相關(guān)示意圖如下:

與上面的數(shù)據(jù)流向不同,FileChannel 的 transferTo 方法底層基于 sendfile64(Linux 平臺下)系統(tǒng)調(diào)用實現(xiàn)。sendfile64 會直接在內(nèi)核空間內(nèi)進行數(shù)據(jù)拷貝,免去了內(nèi)核往用戶空間拷貝,用戶空間再往內(nèi)核空間拷貝這兩步操作,因此提高了效率。其示意圖如下:

通過上面的講解,大家應該知道了 transferTo 和 transferFrom 的效率會高于傳統(tǒng)的 read 和 write 在效率上的區(qū)別。區(qū)別的原因在于免去了內(nèi)核空間和用戶空間的相互拷貝,雖然內(nèi)存間拷貝的速度比較快,但涉及到大量的數(shù)據(jù)拷貝時,相互拷貝的帶來的消耗是不應該被忽略的。

講完了背景知識,咱們再來看看 FileChannel 是怎樣調(diào)用 sendfile64 這個函數(shù)的。相關(guān)代碼如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public long transferTo(long position, long count,WritableByteChannel target)throws IOException {// 省略一些代碼int icount = (int)Math.min(count, Integer.MAX_VALUE);if ((sz - position) < icount)icount = (int)(sz - position);long n;// Attempt a direct transfer, if the kernel supports itif ((n = transferToDirectly(position, icount, target)) >= 0)return n;// Attempt a mapped transfer, but only to trusted channel typesif ((n = transferToTrustedChannel(position, icount, target)) >= 0)return n;// Slow path for untrusted targetsreturn 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) && isOpen());// 省略一些代碼return IOStatus.normalize(n);} finally {threads.remove(ti);end (n > -1);} }

從上面代碼(transferToDirectly 方法可以在 openjdk/jdk/src/share/classes/sun/nio/ch/FileChannelImpl.java 中找到)中可以看得出 transferTo 的調(diào)用路徑,先是調(diào)用 transferToDirectly,然后 transferToDirectly 再調(diào)用 transferTo0。transferTo0 是 native 類型的方法,我們再去看看 transferTo0 是怎樣實現(xiàn)的,其代碼在openjdk/jdk/src/solaris/native/sun/nio/ch/FileChannelImpl.c中。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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, &offset, (size_t)count);if (n < 0) {if (errno == EAGAIN)return IOS_UNAVAILABLE;if ((errno == EINVAL) && ((ssize_t)count >= 0))return IOS_UNSUPPORTED_CASE;if (errno == EINTR) {return IOS_INTERRUPTED;}JNU_ThrowIOExceptionWithLastError(env, "Transfer failed");return IOS_THROWN;}return n;// 其他平臺的代碼省略 #endif }

如上所示,transferTo0 最終調(diào)用了 sendfile64 函數(shù),關(guān)于 sendfile64 這個系統(tǒng)調(diào)用的詳細說明,請參考 man-page,這里就不展開說明了。

?2.4 內(nèi)存映射

內(nèi)存映射這個概念源自操作系統(tǒng),是指將一個文件映射到某一段虛擬內(nèi)存(物理內(nèi)存可能不連續(xù))上去。我們通過對這段虛擬內(nèi)存的讀寫即可達到對文件的讀寫的效果,從而可以簡化對文件的操作。當然,這只是內(nèi)存映射的一個優(yōu)點。內(nèi)存映射還有其他的一些優(yōu)點,比如兩個進程映射同一個文件,可以實現(xiàn)進程間通信。再比如,C 程序運行時需要 C 標準庫支持,操作系統(tǒng)將 C 標準庫放到了內(nèi)存中,普通的 C 程序只需要將 C 標準庫映射到自己的進程空間內(nèi)就行了,從而可以降低內(nèi)存占用。以上簡單介紹了內(nèi)存映射的概念及作用,關(guān)于這方面的知識,建議大家去看《深入理解計算機系統(tǒng)》關(guān)于內(nèi)存映射的章節(jié),講的很好。

Unix/Linux 操作系統(tǒng)內(nèi)存映射的系統(tǒng)調(diào)用mmap,Java 在這個系統(tǒng)調(diào)用的基礎(chǔ)上,封裝了 Java 的內(nèi)存映射方法。這里我就不一步一步往下追蹤了,大家有興趣可以自己追蹤一下 Java 封裝的內(nèi)存映射方法的調(diào)用棧。下面來簡單的示例演示一下內(nèi)存映射的用法:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // 從標準輸入獲取數(shù)據(jù) 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();// 獲取內(nèi)存映射緩沖區(qū),并向緩沖區(qū)寫入數(shù)據(jù) MappedByteBuffer mappedBuffer = channel.map(MapMode.READ_WRITE, 0, bytes.length); mappedBuffer.put(bytes);raf.close(); raf.close();// 再次打開剛剛的文件,讀取其中的內(nèi)容 raf = new RandomAccessFile("map.txt", "rw"); channel = raf.getChannel(); System.out.println("\n文件內(nèi)容:") System.out.println(readChannel(channel));raf.close(); raf.close();

上面的代碼從標準輸入中獲取數(shù)據(jù),然后將數(shù)據(jù)通過內(nèi)存映射緩存寫入到文件中。代碼運行結(jié)果如下:

接下來在用 C 代碼演示上面代碼的功能,如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <stdio.h> #include <fcntl.h> #include <sys/mman.h> #include <memory.h> #include <unistd.h>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);// 將文件映射到內(nèi)存中dst = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, dstfd, 0);// 將輸入的數(shù)據(jù)拷貝到映射內(nèi)存中memcpy(dst, buf, len);munmap(dst, len);close(dstfd);// 重新打開文件,并輸出文件內(nèi)容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; }

關(guān)于 mmap 函數(shù)的參數(shù)說明,這里就不細說了,大家可以參考 man-page。上面的代碼運行結(jié)果如下:

關(guān)于內(nèi)存映射就說到了,更深入的分析需要涉及到很多操作系統(tǒng)層面的東西。我對這些東西了解的也不多,所以就不繼續(xù)分析了,慚愧慚愧。

?2.5 其他操作

FileChannel 還有一些其他的方法,這里通過一個表格來列舉這些方法,就不一一展開說明了。如下:

方法名用途
position返回或修改通道讀寫位置
size獲取通道所關(guān)聯(lián)文件的大小
truncate截斷通道所關(guān)聯(lián)的文件
force強制將通道中的新數(shù)據(jù)刷新到文件中
close關(guān)閉通道
lock對通道文件進行加鎖

以上所列舉的方法用起來比較簡單,大家自己寫代碼驗證一下吧,這里就不貼代碼了。

?3.總結(jié)

以上章節(jié)對 NIO 文件通道的用法和部分方法的實現(xiàn)進行了簡單分析。從上面的分析可以看出,NIO FileChannel 在實現(xiàn)上,實際上是對底層操作系統(tǒng)的一些 API 進行了再次封裝,也就是一層皮。有了這層封裝后,對上就屏蔽了底層 API 的細節(jié),以降低使用難度。Java 為了提高開發(fā)效率,屏蔽了操作系統(tǒng)層面的細節(jié)。雖然 Java 可以屏蔽這些細節(jié),但作為開發(fā)人員,我覺得我們不能也去屏蔽這些細節(jié)(雖然不了解這些細節(jié)也能寫代碼),有時間還是應該多了解了解這些底層的東西。畢竟要想往更高的層次發(fā)展,這些底層的知識必不可少。說到這里,感覺很慚愧,我的技術(shù)基礎(chǔ)也很薄弱。大學期間沒有意識到專業(yè)基礎(chǔ)課的重要性,學了很多東西,但忽略了基礎(chǔ)。好在工作不久后看了很多牛人的博客,也意識到了自己的不足。現(xiàn)在靜下心來打基礎(chǔ),算是亡羊補牢吧。

好了,關(guān)于文件通道的內(nèi)容這里就說到這,謝謝大家的閱讀。

?參考

  • 《Java 編程思想》
  • 《深入理解計算機系統(tǒng)》
  • Java NIO Channel
  • 本文鏈接:?https://www.tianxiaobo.com/2018/03/24/JAVA-NIO之文件通道/

from:http://www.tianxiaobo.com/2018/03/24/JAVA-NIO%E4%B9%8B%E6%96%87%E4%BB%B6%E9%80%9A%E9%81%93/?

總結(jié)

以上是生活随笔為你收集整理的JAVA NIO之文件通道的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 一区二区久久精品66国产精品 | 天堂免费在线视频 | 3d动漫精品啪啪一区二区下载 | 国产精品视频入口 | 中文字幕一区二区三区免费看 | 911毛片 | 精品亚洲国产成人av制服丝袜 | 亚洲精品婷婷 | 久久九九国产视频 | 色图在线观看 | 怨女1988国语版在线观看高清 | 欧美久久久久久久久中文字幕 | 91精品国产高清91久久久久久 | 色哟哟网站入口 | 无套在线观看 | 国产人妻精品久久久久野外 | 91在线观看免费视频 | 久久精品国产亚洲AV黑人 | 亚洲午夜无码av毛片久久 | 日韩av一二三区 | 日本一区二区三区视频在线 | √资源天堂中文在线 | 五月天在线播放 | 欧美永久免费 | 亚洲一卡二卡在线观看 | 亚洲黄色免费看 | 婷婷六月综合 | 亚洲性xxxx | 国产小视频免费观看 | wwww黄色片| 国产精品久久亚洲 | 日本在线高清 | 在线视频亚洲色图 | 亚洲av久久久噜噜噜噜 | 国产精品手机在线观看 | 丹丹的呻吟声1一7 | 日本xxxxxⅹxxxx69 | 欧美精产国品一二三 | 亚洲AV无码国产精品 | 欧美成人a交片免费看 | 老司机亚洲精品 | 亚洲精品97| 亚洲污视频 | 亚洲最大免费视频 | 国产精品亚洲无码 | 久操视频在线 | 国产情侣呻吟对白高潮 | 一区二区三区蜜桃 | 97毛片 | 国产香蕉97碰碰碰视频在线观看 | 精品人妻无码一区二区色欲产成人 | 欧美色图校园春色 | 国产欧美一区二区三区白浆喷水 | 深夜久久久| 国产在线拍 | 国产情侣一区二区三区 | 在线观看福利片 | 中文一区在线观看 | 成人激情五月天 | 久久毛片基地 | 蜜桃视频一区二区三区 | 日本a视频 | 处女朱莉 | 中文在线一区二区三区 | 偷自拍 | 亚洲乱淫| 日韩精品一区二区三 | 69xx视频在线观看 | 涩涩综合| 国产精品网友自拍 | 九九热最新视频 | 人与动物2免费观看完整版电影高清 | 伊人免费视频二 | 亚洲免费观看高清完整 | 国产无遮挡a片又黄又爽 | 奇米影视一区二区三区 | 欧美日韩在线视频观看 | 爽妇网国产精品 | 国产精品最新 | 国产无遮挡免费观看视频网站 | 亚洲成人免费网站 | 一本色道久久综合亚洲精品 | 欧美性生话| 国产精品久久777777毛茸茸 | 日本黄色免费观看 | 国产在线播 | 欧美国产精品 | 亚洲午夜av久久乱码 | av免费在线网站 | 强行糟蹋人妻hd中文 | 久久嫩草视频 | 亚洲免费大片 | 日本一区二区三区在线观看 | 天堂av电影在线观看 | 91天堂在线| 在线免费观看 | 亚洲婷婷综合网 | 欧美无专区 | 超碰免费在|