NIO 教程(轉(zhuǎn))

轉(zhuǎn)自 http://www.ibm.com/developerworks/cn/education/java/j-nio/index.html
在開(kāi)始之前
關(guān)于本教程
新的輸入/輸出 (NIO) 庫(kù)是在 JDK 1.4 中引入的屋厘。NIO 彌補(bǔ)了原來(lái)的 I/O 的不足,它在標(biāo)準(zhǔn) Java 代碼中提供了高速的旨椒、面向塊的 I/O欠窒。通過(guò)定義包含數(shù)據(jù)的類柿估,以及通過(guò)以塊的形式處理這些數(shù)據(jù)亡笑,NIO 不用使用本機(jī)代碼就可以利用低級(jí)優(yōu)化脸狸,這是原來(lái)的 I/O 包所無(wú)法做到的。

在本教程中缴渊,我們將討論 NIO 庫(kù)的幾乎所有方面赏壹,從高級(jí)的概念性內(nèi)容到底層的編程細(xì)節(jié)。除了學(xué)習(xí)諸如緩沖區(qū)和通道這樣的關(guān)鍵 I/O 元素外衔沼,您還有機(jī)會(huì)看到在更新后的庫(kù)中標(biāo)準(zhǔn) I/O 是如何工作的蝌借。您還會(huì)了解只能通過(guò) NIO 來(lái)完成的工作,如異步 I/O 和直接緩沖區(qū)指蚁。

在本教程中菩佑,我們將使用展示 NIO 庫(kù)的不同方面的代碼示例。幾乎每一個(gè)代碼示例都是一個(gè)大的 Java 程序的一部分凝化,您可以在 參考資料 中找到這個(gè) Java 程序稍坯。在做這些練習(xí)時(shí),我們推薦您在自己的系統(tǒng)上下載搓劫、編譯和運(yùn)行這些程序瞧哟。在您學(xué)習(xí)了本教程以后,這些代碼將為您的 NIO 編程努力提供一個(gè)起點(diǎn)枪向。

本教程是為希望學(xué)習(xí)更多關(guān)于 JDK 1.4 NIO 庫(kù)的知識(shí)的所有程序員而寫(xiě)的勤揩。為了最大程度地從這里的討論中獲益,您應(yīng)該理解基本的 Java 編程概念秘蛔,如類雄可、繼承和使用包。多少熟悉一些原來(lái)的 I/O 庫(kù)(來(lái)自 java.io.*
包)也會(huì)有所幫助缠犀。

雖然本教程要求掌握 Java 語(yǔ)言的工作詞匯和概念,但是不需要有很多實(shí)際編程經(jīng)驗(yàn)聪舒。除了徹底介紹與本教程有關(guān)的所有概念外辨液,我還保持代碼示例盡可能短小和簡(jiǎn)單。目的是讓即使沒(méi)有多少 Java 編程經(jīng)驗(yàn)的讀者也能容易地開(kāi)始學(xué)習(xí) NIO箱残。
如何運(yùn)行代碼
源代碼歸檔文件(在 參考資料 中提供)包含了本教程中使用的所有程序滔迈。每一個(gè)程序都由一個(gè) Java 文件構(gòu)成。每一個(gè)文件都根據(jù)名稱來(lái)識(shí)別被辑,并且可以容易地與它所展示的編程概念相關(guān)聯(lián)燎悍。
教程中的一些程序需要命令行參數(shù)才能運(yùn)行。要從命令行運(yùn)行一個(gè)程序盼理,只需使用最方便的命令行提示符谈山。在 Windows 中,命令行提供符是 “Command” 或者 “command.com” 程序宏怔。在 UNIX 中奏路,可以使用任何 shell畴椰。
需要安裝 JDK 1.4 并將它包括在路徑中,才能完成本教程中的練習(xí)鸽粉。如果需要安裝和配置 JDK 1.4 的幫助斜脂,請(qǐng)參見(jiàn) 參考資料


輸入/輸出:概念性描述
I/O 簡(jiǎn)介
I/O ? 或者輸入/輸出 ? 指的是計(jì)算機(jī)與外部世界或者一個(gè)程序與計(jì)算機(jī)的其余部分的之間的接口触机。它對(duì)于任何計(jì)算機(jī)系統(tǒng)都非常關(guān)鍵帚戳,因而所有 I/O 的主體實(shí)際上是內(nèi)置在操作系統(tǒng)中的。單獨(dú)的程序一般是讓系統(tǒng)為它們完成大部分的工作儡首。

在 Java 編程中片任,直到最近一直使用 *流 *的方式完成 I/O。所有 I/O 都被視為單個(gè)的字節(jié)的移動(dòng)椒舵,通過(guò)一個(gè)稱為 Stream 的對(duì)象一次移動(dòng)一個(gè)字節(jié)蚂踊。流 I/O 用于與外部世界接觸。它也在內(nèi)部使用笔宿,用于將對(duì)象轉(zhuǎn)換為字節(jié)犁钟,然后再轉(zhuǎn)換回對(duì)象。

NIO 與原來(lái)的 I/O 有同樣的作用和目的泼橘,但是它使用不同的方式? 塊 I/O涝动。正如您將在本教程中學(xué)到的,塊 I/O 的效率可以比流 I/O 高許多炬灭。

為什么要使用 NIO?
NIO 的創(chuàng)建目的是為了讓 Java 程序員可以實(shí)現(xiàn)高速 I/O 而無(wú)需編寫(xiě)自定義的本機(jī)代碼醋粟。NIO 將最耗時(shí)的 I/O 操作(即填充和提取緩沖區(qū))轉(zhuǎn)移回操作系統(tǒng),因而可以極大地提高速度重归。

流與塊的比較
原來(lái)的 I/O 庫(kù)(在 java.io.*中) 與 NIO 最重要的區(qū)別是數(shù)據(jù)打包和傳輸?shù)姆绞矫自浮U缜懊嫣岬降模瓉?lái)的 I/O 以流的方式處理數(shù)據(jù)鼻吮,而 NIO 以塊的方式處理數(shù)據(jù)育苟。

*面向流 *的 I/O 系統(tǒng)一次一個(gè)字節(jié)地處理數(shù)據(jù)。一個(gè)輸入流產(chǎn)生一個(gè)字節(jié)的數(shù)據(jù)椎木,一個(gè)輸出流消費(fèi)一個(gè)字節(jié)的數(shù)據(jù)违柏。為流式數(shù)據(jù)創(chuàng)建過(guò)濾器非常容易。鏈接幾個(gè)過(guò)濾器香椎,以便每個(gè)過(guò)濾器只負(fù)責(zé)單個(gè)復(fù)雜處理機(jī)制的一部分漱竖,這樣也是相對(duì)簡(jiǎn)單的。不利的一面是畜伐,面向流的 I/O 通常相當(dāng)慢馍惹。

一個(gè) *面向塊 *的 I/O 系統(tǒng)以塊的形式處理數(shù)據(jù)。每一個(gè)操作都在一步中產(chǎn)生或者消費(fèi)一個(gè)數(shù)據(jù)塊。按塊處理數(shù)據(jù)比按(流式的)字節(jié)處理數(shù)據(jù)要快得多讼积。但是面向塊的 I/O 缺少一些面向流的 I/O 所具有的優(yōu)雅性和簡(jiǎn)單性肥照。

集成的 I/O
在 JDK 1.4 中原來(lái)的 I/O 包和 NIO 已經(jīng)很好地集成了。 java.io.已經(jīng)以 NIO 為基礎(chǔ)重新實(shí)現(xiàn)了勤众,所以現(xiàn)在它可以利用 NIO 的一些特性舆绎。例如, java.io.包中的一些類包含以塊的形式讀寫(xiě)數(shù)據(jù)的方法们颜,這使得即使在更面向流的系統(tǒng)中吕朵,處理速度也會(huì)更快。

也可以用 NIO 庫(kù)實(shí)現(xiàn)標(biāo)準(zhǔn) I/O 功能窥突。例如努溃,可以容易地使用塊 I/O 一次一個(gè)字節(jié)地移動(dòng)數(shù)據(jù)。但是正如您會(huì)看到的阻问,NIO 還提供了原 I/O 包中所沒(méi)有的許多好處梧税。


通道和緩沖區(qū)
概述
通道 和 緩沖區(qū) 是 NIO 中的核心對(duì)象,幾乎在每一個(gè) I/O 操作中都要使用它們称近。

通道是對(duì)原 I/O 包中的流的模擬第队。到任何目的地(或來(lái)自任何地方)的所有數(shù)據(jù)都必須通過(guò)一個(gè) Channel 對(duì)象。一個(gè) Buffer 實(shí)質(zhì)上是一個(gè)容器對(duì)象刨秆。發(fā)送給一個(gè)通道的所有對(duì)象都必須首先放到緩沖區(qū)中凳谦;同樣地,從通道中讀取的任何數(shù)據(jù)都要讀到緩沖區(qū)中衡未。

在本節(jié)中尸执,您會(huì)了解到 NIO 中通道和緩沖區(qū)是如何工作的。

什么是緩沖區(qū)缓醋?
Buffer 是一個(gè)對(duì)象如失, 它包含一些要寫(xiě)入或者剛讀出的數(shù)據(jù)。 在 NIO 中加入 Buffer對(duì)象送粱,體現(xiàn)了新庫(kù)與原 I/O 的一個(gè)重要區(qū)別褪贵。在面向流的 I/O 中,您將數(shù)據(jù)直接寫(xiě)入或者將數(shù)據(jù)直接讀到 Stream 對(duì)象中葫督。

在 NIO 庫(kù)中,所有數(shù)據(jù)都是用緩沖區(qū)處理的板惑。在讀取數(shù)據(jù)時(shí)橄镜,它是直接讀到緩沖區(qū)中的。在寫(xiě)入數(shù)據(jù)時(shí)冯乘,它是寫(xiě)入到緩沖區(qū)中的洽胶。任何時(shí)候訪問(wèn) NIO 中的數(shù)據(jù),您都是將它放到緩沖區(qū)中。

緩沖區(qū)實(shí)質(zhì)上是一個(gè)數(shù)組姊氓。通常它是一個(gè)字節(jié)數(shù)組丐怯,但是也可以使用其他種類的數(shù)組。但是一個(gè)緩沖區(qū)不 *僅僅 *是一個(gè)數(shù)組翔横。緩沖區(qū)提供了對(duì)數(shù)據(jù)的結(jié)構(gòu)化訪問(wèn)读跷,而且還可以跟蹤系統(tǒng)的讀/寫(xiě)進(jìn)程。

緩沖區(qū)類型
最常用的緩沖區(qū)類型是 ByteBuffer禾唁。一個(gè) ByteBuffer可以在其底層字節(jié)數(shù)組上進(jìn)行 get/set 操作(即字節(jié)的獲取和設(shè)置)效览。

ByteBuffer不是 NIO 中唯一的緩沖區(qū)類型。事實(shí)上荡短,對(duì)于每一種基本 Java 類型都有一種緩沖區(qū)類型:

  • ByteBuffer

  • CharBuffer

  • ShortBuffer

  • IntBuffer

  • LongBuffer

  • FloatBuffer

  • DoubleBuffer

每一個(gè) Buffer類都是 Buffer接口的一個(gè)實(shí)例丐枉。 除了 ByteBuffer,每一個(gè) Buffer 類都有完全一樣的操作掘托,只是它們所處理的數(shù)據(jù)類型不一樣瘦锹。因?yàn)榇蠖鄶?shù)標(biāo)準(zhǔn) I/O 操作都使用 ByteBuffer,所以它具有所有共享的緩沖區(qū)操作以及一些特有的操作闪盔。

現(xiàn)在您可以花一點(diǎn)時(shí)間運(yùn)行 UseFloatBuffer.java弯院,它包含了類型化的緩沖區(qū)的一個(gè)應(yīng)用例子。
什么是通道锭沟?
Channel是一個(gè)對(duì)象抽兆,可以通過(guò)它讀取和寫(xiě)入數(shù)據(jù)。拿 NIO 與原來(lái)的 I/O 做個(gè)比較族淮,通道就像是流辫红。

正如前面提到的,所有數(shù)據(jù)都通過(guò) Buffer對(duì)象來(lái)處理祝辣。您永遠(yuǎn)不會(huì)將字節(jié)直接寫(xiě)入通道中贴妻,相反,您是將數(shù)據(jù)寫(xiě)入包含一個(gè)或者多個(gè)字節(jié)的緩沖區(qū)蝙斜。同樣名惩,您不會(huì)直接從通道中讀取字節(jié),而是將數(shù)據(jù)從通道讀入緩沖區(qū)孕荠,再?gòu)木彌_區(qū)獲取這個(gè)字節(jié)娩鹉。

通道類型
通道與流的不同之處在于通道是雙向的。而流只是在一個(gè)方向上移動(dòng)(一個(gè)流必須是 InputStream或者 OutputStream的子類)稚伍, 而 通道
可以用于讀弯予、寫(xiě)或者同時(shí)用于讀寫(xiě)。

因?yàn)樗鼈兪请p向的个曙,所以通道可以比流更好地反映底層操作系統(tǒng)的真實(shí)情況锈嫩。特別是在 UNIX 模型中,底層操作系統(tǒng)通道是雙向的。


從理論到實(shí)踐:NIO 中的讀和寫(xiě)
概述
讀和寫(xiě)是 I/O 的基本過(guò)程呼寸。從一個(gè)通道中讀取很簡(jiǎn)單:只需創(chuàng)建一個(gè)緩沖區(qū)艳汽,然后讓通道將數(shù)據(jù)讀到這個(gè)緩沖區(qū)中。寫(xiě)入也相當(dāng)簡(jiǎn)單:創(chuàng)建一個(gè)緩沖區(qū)对雪,用數(shù)據(jù)填充它河狐,然后讓通道用這些數(shù)據(jù)來(lái)執(zhí)行寫(xiě)入操作。

在本節(jié)中慌植,我們將學(xué)習(xí)有關(guān)在 Java 程序中讀取和寫(xiě)入數(shù)據(jù)的一些知識(shí)甚牲。我們將回顧 NIO 的主要組件(緩沖區(qū)、通道和一些相關(guān)的方法)蝶柿,看看它們是如何交互以進(jìn)行讀寫(xiě)的丈钙。在接下來(lái)的幾節(jié)中,我們將更詳細(xì)地分析這其中的每個(gè)組件以及其交互交汤。

從文件中讀取
在我們第一個(gè)練習(xí)中雏赦,我們將從一個(gè)文件中讀取一些數(shù)據(jù)。如果使用原來(lái)的 I/O芙扎,那么我們只需創(chuàng)建一個(gè) FileInputStream并從它那里讀取星岗。而在 NIO 中,情況稍有不同:我們首先從 FileInputStream獲取一個(gè) Channel對(duì)象戒洼,然后使用這個(gè)通道來(lái)讀取數(shù)據(jù)俏橘。

在 NIO 系統(tǒng)中,任何時(shí)候執(zhí)行一個(gè)讀操作圈浇,您都是從通道中讀取寥掐,但是您不是 *直接 *從通道讀取。因?yàn)樗袛?shù)據(jù)最終都駐留在緩沖區(qū)中磷蜀,所以您是從通道讀到緩沖區(qū)中召耘。

因此讀取文件涉及三個(gè)步驟:(1) 從 FileInputStream獲取 Channel,(2) 創(chuàng)建 Buffer褐隆,(3) 將數(shù)據(jù)從 Channel讀到 Buffer 中污它。

現(xiàn)在,讓我們看一下這個(gè)過(guò)程庶弃。

三個(gè)容易的步驟
第一步是獲取通道衫贬。我們從 FileInputStream 獲取通道:
FileInputStream fin = new FileInputStream( "readandshow.txt" ); FileChannel fc = fin.getChannel();

下一步是創(chuàng)建緩沖區(qū):
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
最后,需要將數(shù)據(jù)從通道讀到緩沖區(qū)中歇攻,如下所示:
fc.read( buffer );

您會(huì)注意到固惯,我們不需要告訴通道要讀 *多少數(shù)據(jù) *到緩沖區(qū)中。每一個(gè)緩沖區(qū)都有復(fù)雜的內(nèi)部統(tǒng)計(jì)機(jī)制掉伏,它會(huì)跟蹤已經(jīng)讀了多少數(shù)據(jù)以及還有多少空間可以容納更多的數(shù)據(jù)缝呕。我們將在 緩沖區(qū)內(nèi)部細(xì)節(jié) 中介紹更多關(guān)于緩沖區(qū)統(tǒng)計(jì)機(jī)制的內(nèi)容。

寫(xiě)入文件
在 NIO 中寫(xiě)入文件類似于從文件中讀取斧散。首先從 FileOutputStream
獲取一個(gè)通道:
FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" ); FileChannel fc = fout.getChannel();

下一步是創(chuàng)建一個(gè)緩沖區(qū)并在其中放入一些數(shù)據(jù) - 在這里供常,數(shù)據(jù)將從一個(gè)名為 message 的數(shù)組中取出,這個(gè)數(shù)組包含字符串 "Some bytes" 的 ASCII 字節(jié)(本教程后面將會(huì)解釋 buffer.flip()和 buffer.put()調(diào)用)鸡捐。
ByteBuffer buffer = ByteBuffer.allocate( 1024 ); for (int i=0; i<message.length; ++i) { buffer.put( message[i] );}buffer.flip();
最后一步是寫(xiě)入緩沖區(qū)中:
fc.write( buffer );

注意在這里同樣不需要告訴通道要寫(xiě)入多數(shù)據(jù)栈暇。緩沖區(qū)的內(nèi)部統(tǒng)計(jì)機(jī)制會(huì)跟蹤它包含多少數(shù)據(jù)以及還有多少數(shù)據(jù)要寫(xiě)入。

讀寫(xiě)結(jié)合
下面我們將看一下在結(jié)合讀和寫(xiě)時(shí)會(huì)有什么情況箍镜。我們以一個(gè)名為 CopyFile.java 的簡(jiǎn)單程序作為這個(gè)練習(xí)的基礎(chǔ),它將一個(gè)文件的所有內(nèi)容拷貝到另一個(gè)文件中。CopyFile.java 執(zhí)行三個(gè)基本操作:首先創(chuàng)建一個(gè) Buffer羹铅,然后從源文件中將數(shù)據(jù)讀到這個(gè)緩沖區(qū)中奥额,然后將緩沖區(qū)寫(xiě)入目標(biāo)文件。這個(gè)程序不斷重復(fù) ― 讀歇僧、寫(xiě)图张、讀、寫(xiě) ― 直到源文件結(jié)束诈悍。

CopyFile 程序讓您看到我們?nèi)绾螜z查操作的狀態(tài)祸轮,以及如何使用 clear() 和 flip() 方法重設(shè)緩沖區(qū),并準(zhǔn)備緩沖區(qū)以便將新讀取的數(shù)據(jù)寫(xiě)到另一個(gè)通道中侥钳。

運(yùn)行 CopyFile 例子
因?yàn)榫彌_區(qū)會(huì)跟蹤它自己的數(shù)據(jù)适袜,所以 CopyFile 程序的內(nèi)部循環(huán) (inner loop) 非常簡(jiǎn)單舷夺,如下所示:
fcin.read( buffer ); fcout.write( buffer );

第一行將數(shù)據(jù)從輸入通道 fcin中讀入緩沖區(qū)苦酱,第二行將這些數(shù)據(jù)寫(xiě)到輸出通道 fcout。

檢查狀態(tài)
下一步是檢查拷貝何時(shí)完成冕房。當(dāng)沒(méi)有更多的數(shù)據(jù)時(shí)躏啰,拷貝就算完成,并且可以在 read()方法返回 -1 是判斷這一點(diǎn)耙册,如下所示:
int r = fcin.read( buffer );if (r==-1) { break;}

重設(shè)緩沖區(qū)
最后给僵,在從輸入通道讀入緩沖區(qū)之前,我們調(diào)用 clear()方法详拙。同樣帝际,在將緩沖區(qū)寫(xiě)入輸出通道之前,我們調(diào)用 flip()方法饶辙,如下所示:
buffer.clear(); int r = fcin.read( buffer ); if (r==-1) { break; } buffer.flip(); fcout.write( buffer );

clear()方法重設(shè)緩沖區(qū)蹲诀,使它可以接受讀入的數(shù)據(jù)。 flip()方法讓緩沖區(qū)可以將新讀入的數(shù)據(jù)寫(xiě)入另一個(gè)通道弃揽。


緩沖區(qū)內(nèi)部細(xì)節(jié)
概述
本節(jié)將介紹 NIO 中兩個(gè)重要的緩沖區(qū)組件:狀態(tài)變量和訪問(wèn)方法 (accessor)脯爪。

狀態(tài)變量是前一節(jié)中提到的"內(nèi)部統(tǒng)計(jì)機(jī)制"的關(guān)鍵则北。每一個(gè)讀/寫(xiě)操作都會(huì)改變緩沖區(qū)的狀態(tài)。通過(guò)記錄和跟蹤這些變化痕慢,緩沖區(qū)就可能夠內(nèi)部地管理自己的資源尚揣。

在從通道讀取數(shù)據(jù)時(shí),數(shù)據(jù)被放入到緩沖區(qū)掖举。在有些情況下快骗,可以將這個(gè)緩沖區(qū)直接寫(xiě)入另一個(gè)通道,但是在一般情況下塔次,您還需要查看數(shù)據(jù)方篮。這是使用 *訪問(wèn)方法 *get()來(lái)完成的。同樣励负,如果要將原始數(shù)據(jù)放入緩沖區(qū)中藕溅,就要使用訪問(wèn)方法 put()。

在本節(jié)中继榆,您將學(xué)習(xí)關(guān)于 NIO 中的狀態(tài)變量和訪問(wèn)方法的內(nèi)容蜈垮。我們將描述每一個(gè)組件,并讓您有機(jī)會(huì)看到它的實(shí)際應(yīng)用裕照。雖然 NIO 的內(nèi)部統(tǒng)計(jì)機(jī)制初看起來(lái)可能很復(fù)雜攒发,但是您很快就會(huì)看到大部分的實(shí)際工作都已經(jīng)替您完成了。您可能習(xí)慣于通過(guò)手工編碼進(jìn)行簿記 ― 即使用字節(jié)數(shù)組和索引變量晋南,現(xiàn)在它已在 NIO 中內(nèi)部地處理了惠猿。

狀態(tài)變量
可以用三個(gè)值指定緩沖區(qū)在任意時(shí)刻的狀態(tài):

  • position
  • limit
  • capacity

這三個(gè)變量一起可以跟蹤緩沖區(qū)的狀態(tài)和它所包含的數(shù)據(jù)。我們將在下面的小節(jié)中詳細(xì)分析每一個(gè)變量负间,還要介紹它們?nèi)绾芜m應(yīng)典型的讀/寫(xiě)(輸入/輸出)進(jìn)程偶妖。在這個(gè)例子中,我們假定要將數(shù)據(jù)從一個(gè)輸入通道拷貝到一個(gè)輸出通道政溃。

Position
您可以回想一下趾访,緩沖區(qū)實(shí)際上就是美化了的數(shù)組。在從通道讀取時(shí)董虱,您將所讀取的數(shù)據(jù)放到底層的數(shù)組中扼鞋。 position變量跟蹤已經(jīng)寫(xiě)了多少數(shù)據(jù)。更準(zhǔn)確地說(shuō)愤诱,它指定了下一個(gè)字節(jié)將放到數(shù)組的哪一個(gè)元素中云头。因此,如果您從通道中讀三個(gè)字節(jié)到緩沖區(qū)中淫半,那么緩沖區(qū)的position將會(huì)設(shè)置為3溃槐,指向數(shù)組中第四個(gè)元素。

同樣科吭,在寫(xiě)入通道時(shí)昏滴,您是從緩沖區(qū)中獲取數(shù)據(jù)猴鲫。 position值跟蹤從緩沖區(qū)中獲取了多少數(shù)據(jù)。更準(zhǔn)確地說(shuō)谣殊,它指定下一個(gè)字節(jié)來(lái)自數(shù)組的哪一個(gè)元素变隔。因此如果從緩沖區(qū)寫(xiě)了5個(gè)字節(jié)到通道中,那么緩沖區(qū)的 position將被設(shè)置為5蟹倾,指向數(shù)組的第六個(gè)元素。

Limit
limit
變量表明還有多少數(shù)據(jù)需要取出(在從緩沖區(qū)寫(xiě)入通道時(shí))猖闪,或者還有多少空間可以放入數(shù)據(jù)(在從通道讀入緩沖區(qū)時(shí))鲜棠。
position總是小于或者等于 limit。

Capacity
緩沖區(qū)的 capacity
表明可以儲(chǔ)存在緩沖區(qū)中的最大數(shù)據(jù)容量培慌。實(shí)際上豁陆,它指定了底層數(shù)組的大小 ― 或者至少是指定了準(zhǔn)許我們使用的底層數(shù)組的容量。
limit決不能大于 capacity吵护。

觀察變量
我們首先觀察一個(gè)新創(chuàng)建的緩沖區(qū)盒音。出于本例子的需要,我們假設(shè)這個(gè)緩沖區(qū)的 總?cè)萘?為8個(gè)字節(jié)馅而。 Buffer 的狀態(tài)如下所示:

Buffer state
回想一下,limit決不能大于 capacity祥诽,此例中這兩個(gè)值都被設(shè)置為 8。我們通過(guò)將它們指向數(shù)組的尾部之后(如果有第8個(gè)槽瓮恭,則是第8個(gè)槽所在的位置)來(lái)說(shuō)明這點(diǎn)雄坪。

Array

position設(shè)置為0。如果我們讀一些數(shù)據(jù)到緩沖區(qū)中屯蹦,那么下一個(gè)讀取的數(shù)據(jù)就進(jìn)入 slot 0 维哈。如果我們從緩沖區(qū)寫(xiě)一些數(shù)據(jù),從緩沖區(qū)讀取的下一個(gè)字節(jié)就來(lái)自 slot 0 登澜。 position 設(shè)置如下所示:

Position setting

由于 capacity 不會(huì)改變阔挠,所以我們?cè)谙旅娴挠懻撝锌梢院雎运?/p>

第一次讀取
現(xiàn)在我們可以開(kāi)始在新創(chuàng)建的緩沖區(qū)上進(jìn)行讀/寫(xiě)操作。首先從輸入通道中讀一些數(shù)據(jù)到緩沖區(qū)中脑蠕。第一次讀取得到三個(gè)字節(jié)购撼。它們被放到數(shù)組中從 position開(kāi)始的位置,這時(shí) position 被設(shè)置為 0谴仙。讀完之后份招,position 就增加到 3,如下所示:

Position increased to 3
limit沒(méi)有改變狞甚。

第二次讀取
在第二次讀取時(shí)锁摔,我們從輸入通道讀取另外兩個(gè)字節(jié)到緩沖區(qū)中。這兩個(gè)字節(jié)儲(chǔ)存在由 position所指定的位置上哼审, position 因而增加 2:

Position increased by 2
limit沒(méi)有改變谐腰。

flip
現(xiàn)在我們要將數(shù)據(jù)寫(xiě)到輸出通道中孕豹。在這之前,我們必須調(diào)用 flip()
方法十气。這個(gè)方法做兩件非常重要的事:
1.它將 limit設(shè)置為當(dāng)前 position励背。
2.它將 position設(shè)置為 0。

前一小節(jié)中的圖顯示了在 flip 之前緩沖區(qū)的情況砸西。下面是在 flip 之后的緩沖區(qū):


Buffer after the flip

我們現(xiàn)在可以將數(shù)據(jù)從緩沖區(qū)寫(xiě)入通道了叶眉。 position被設(shè)置為 0,這意味著我們得到的下一個(gè)字節(jié)是第一個(gè)字節(jié)芹枷。limit 已被設(shè)置為原來(lái)的 position衅疙,這意味著它包括以前讀到的所有字節(jié),并且一個(gè)字節(jié)也不多鸳慈。

第一次寫(xiě)入
在第一次寫(xiě)入時(shí)饱溢,我們從緩沖區(qū)中取四個(gè)字節(jié)并將它們寫(xiě)入輸出通道。這使得 position增加到 4走芋,而 limit不變绩郎,如下所示:

Position advanced to 4, limit unchanged

第二次寫(xiě)入
我們只剩下一個(gè)字節(jié)可寫(xiě)了。 limit在我們調(diào)用 flip()時(shí)被設(shè)置為 5翁逞,并且 position不能超過(guò) limit肋杖。所以最后一次寫(xiě)入操作從緩沖區(qū)取出一個(gè)字節(jié)并將它寫(xiě)入輸出通道。這使得 position增加到 5挖函,并保持 limit不變兽愤,如下所示:

Position advanced to 5, limit unchanged

clear
最后一步是調(diào)用緩沖區(qū)的 clear() 方法。這個(gè)方法重設(shè)緩沖區(qū)以便接收更多的字節(jié)挪圾。 Clear做兩種非常重要的事情:
1.它將 limit設(shè)置為與 capacity 相同浅萧。
2.它設(shè)置 position 為 0。

下圖顯示了在調(diào)用 clear()后緩沖區(qū)的狀態(tài):


State of the buffer after clear() has been called

緩沖區(qū)現(xiàn)在可以接收新的數(shù)據(jù)了哲思。

訪問(wèn)方法
到目前為止洼畅,我們只是使用緩沖區(qū)將數(shù)據(jù)從一個(gè)通道轉(zhuǎn)移到另一個(gè)通道。然而棚赔,程序經(jīng)常需要直接處理數(shù)據(jù)帝簇。例如,您可能需要將用戶數(shù)據(jù)保存到磁盤(pán)靠益。在這種情況下丧肴,您必須將這些數(shù)據(jù)直接放入緩沖區(qū),然后用通道將緩沖區(qū)寫(xiě)入磁盤(pán)胧后。

或者芋浮,您可能想要從磁盤(pán)讀取用戶數(shù)據(jù)。在這種情況下壳快,您要將數(shù)據(jù)從通道讀到緩沖區(qū)中纸巷,然后檢查緩沖區(qū)中的數(shù)據(jù)镇草。

在本節(jié)的最后,我們將詳細(xì)分析如何使用 ByteBuffer 類的 get()
和 put()方法直接訪問(wèn)緩沖區(qū)中的數(shù)據(jù)瘤旨。
get() 方法
ByteBuffer類中有四個(gè) get()方法:

  • byte get();
  • ByteBuffer get( byte dst[] );
  • ByteBuffer get( byte dst[], int offset, int length );
  • byte get( int index );

第一個(gè)方法獲取單個(gè)字節(jié)梯啤。第二和第三個(gè)方法將一組字節(jié)讀到一個(gè)數(shù)組中。第四個(gè)方法從緩沖區(qū)中的特定位置獲取字節(jié)存哲。那些返回 ByteBuffer
的方法只是返回調(diào)用它們的緩沖區(qū)的 this值因宇。

此外,我們認(rèn)為前三個(gè) get()方法是相對(duì)的祟偷,而最后一個(gè)方法是絕對(duì)的察滑。 *相對(duì) *意味著 get()操作服從 limit 和 position 值 ― 更明確地說(shuō),字節(jié)是從當(dāng)前 position讀取的肩袍,而 position 在 get 之后會(huì)增加。另一方面婚惫,一個(gè) *絕對(duì) *方法會(huì)忽略 limit和 position值氛赐,也不會(huì)影響它們。事實(shí)上先舷,它完全繞過(guò)了緩沖區(qū)的統(tǒng)計(jì)方法艰管。

上面列出的方法對(duì)應(yīng)于 ByteBuffer類。其他類有等價(jià)的 get() 方法蒋川,這些方法除了不是處理字節(jié)外牲芋,其它方面是是完全一樣的,它們處理的是與該緩沖區(qū)類相適應(yīng)的類型捺球。

put()方法
ByteBuffer 類中有五個(gè) put()方法:

  • ByteBuffer put( byte b );
  • ByteBuffer put( byte src[] );
  • ByteBuffer put( byte src[], int offset, int length );
  • ByteBuffer put( ByteBuffer src );
  • ByteBuffer put( int index, byte b );

第一個(gè)方法 寫(xiě)入(put) 單個(gè)字節(jié)缸浦。第二和第三個(gè)方法寫(xiě)入來(lái)自一個(gè)數(shù)組的一組字節(jié)。第四個(gè)方法將數(shù)據(jù)從一個(gè)給定的源 ByteBuffer 寫(xiě)入這個(gè) ByteBuffer氮兵。第五個(gè)方法將字節(jié)寫(xiě)入緩沖區(qū)中特定的 位置 裂逐。那些返回 ByteBuffer 的方法只是返回調(diào)用它們的緩沖區(qū)的 this 值。

與 get()方法一樣泣栈,我們將把 put()方法劃分為 *相對(duì) *或者 *絕對(duì) *的卜高。前四個(gè)方法是相對(duì)的,而第五個(gè)方法是絕對(duì)的南片。

上面顯示的方法對(duì)應(yīng)于 ByteBuffer 類掺涛。其他類有等價(jià)的 put() 方法,這些方法除了不是處理字節(jié)之外疼进,其它方面是完全一樣的薪缆。它們處理的是與該緩沖區(qū)類相適應(yīng)的類型。

類型化的 get() 和 put() 方法
除了前些小節(jié)中描述的 get()和 put()方法伞广, ByteBuffer還有用于讀寫(xiě)不同類型的值的其他方法矮燎,如下所示:

  • getByte()
  • getChar()
  • getShort()
  • getInt()
  • getLong()
  • getFloat()
  • getDouble()
  • putByte()
  • putChar()
  • putShort()
  • putInt()
  • putLong()
  • putFloat()
  • putDouble()

事實(shí)上定血,這其中的每個(gè)方法都有兩種類型 ― 一種是相對(duì)的,另一種是絕對(duì)的诞外。它們對(duì)于讀取格式化的二進(jìn)制數(shù)據(jù)(如圖像文件的頭部)很有用澜沟。

您可以在例子程序 TypesInByteBuffer.java 中看到這些方法的實(shí)際應(yīng)用。

緩沖區(qū)的使用:一個(gè)內(nèi)部循環(huán)
下面的內(nèi)部循環(huán)概括了使用緩沖區(qū)將數(shù)據(jù)從輸入通道拷貝到輸出通道的過(guò)程峡谊。

while (true) { buffer.clear(); int r = fcin.read( buffer ); if (r==-1) { break; } buffer.flip(); fcout.write( buffer ); }

read() 和 write()調(diào)用得到了極大的簡(jiǎn)化茫虽,因?yàn)樵S多工作細(xì)節(jié)都由緩沖區(qū)完成了。 clear() 和 flip()方法用于讓緩沖區(qū)在讀和寫(xiě)之間切換既们。


關(guān)于緩沖區(qū)的更多內(nèi)容
概述
到目前為止濒析,您已經(jīng)學(xué)習(xí)了使用緩沖區(qū)進(jìn)行日常工作所需要掌握的大部分內(nèi)容。我們的例子沒(méi)怎么超出標(biāo)準(zhǔn)的讀/寫(xiě)過(guò)程種類啥纸,在原來(lái)的 I/O 中可以像在 NIO 中一樣容易地實(shí)現(xiàn)這樣的標(biāo)準(zhǔn)讀寫(xiě)過(guò)程号杏。

本節(jié)將討論使用緩沖區(qū)的一些更復(fù)雜的方面,比如緩沖區(qū)分配斯棒、包裝和分片盾致。我們還會(huì)討論 NIO 帶給 Java 平臺(tái)的一些新功能。您將學(xué)到如何創(chuàng)建不同類型的緩沖區(qū)以達(dá)到不同的目的荣暮,如可保護(hù)數(shù)據(jù)不被修改的 *只讀 *緩沖區(qū)庭惜,和直接映射到底層操作系統(tǒng)緩沖區(qū)的 *直接 *緩沖區(qū)。我們將在本節(jié)的最后介紹如何在 NIO 中創(chuàng)建內(nèi)存映射文件穗酥。

緩沖區(qū)分配和包裝
在能夠讀和寫(xiě)之前护赊,必須有一個(gè)緩沖區(qū)。要?jiǎng)?chuàng)建緩沖區(qū)砾跃,您必須 *分配 *它骏啰。我們使用靜態(tài)方法 allocate() 來(lái)分配緩沖區(qū):
ByteBuffer buffer = ByteBuffer.allocate( 1024 );

allocate() 方法分配一個(gè)具有指定大小的底層數(shù)組,并將它包裝到一個(gè)緩沖區(qū)對(duì)象中 ― 在本例中是一個(gè) ByteBuffer抽高。

您還可以將一個(gè)現(xiàn)有的數(shù)組轉(zhuǎn)換為緩沖區(qū)器一,如下所示:
byte array[] = new byte[1024]; ByteBuffer buffer = ByteBuffer.wrap( array );

本例使用了 wrap() 方法將一個(gè)數(shù)組包裝為緩沖區(qū)。必須非常小心地進(jìn)行這類操作厨内。一旦完成包裝祈秕,底層數(shù)據(jù)就可以通過(guò)緩沖區(qū)或者直接訪問(wèn)。

緩沖區(qū)分片
slice()
方法根據(jù)現(xiàn)有的緩沖區(qū)創(chuàng)建一種 *子緩沖區(qū) *雏胃。也就是說(shuō)请毛,它創(chuàng)建一個(gè)新的緩沖區(qū),新緩沖區(qū)與原來(lái)的緩沖區(qū)的一部分共享數(shù)據(jù)瞭亮。

使用例子可以最好地說(shuō)明這點(diǎn)方仿。讓我們首先創(chuàng)建一個(gè)長(zhǎng)度為 10的 ByteBuffer:

ByteBuffer buffer = ByteBuffer.allocate( 10 );

然后使用數(shù)據(jù)來(lái)填充這個(gè)緩沖區(qū),在第 n 個(gè)槽中放入數(shù)字 n
for (int i=0; i<buffer.capacity(); ++i) { buffer.put( (byte)i ); }

現(xiàn)在我們對(duì)這個(gè)緩沖區(qū) *分片 *,以創(chuàng)建一個(gè)包含槽 3 到槽 6 的子緩沖區(qū)仙蚜。在某種意義上此洲,子緩沖區(qū)就像原來(lái)的緩沖區(qū)中的一個(gè) *窗口 *。

窗口的起始和結(jié)束位置通過(guò)設(shè)置 position和 limit值來(lái)指定委粉,然后調(diào)用 Buffer 的 slice()方法:
buffer.position( 3 ); buffer.limit( 7 ); ByteBuffer slice = buffer.slice();

片 是緩沖區(qū)的 子緩沖區(qū) 呜师。不過(guò), 片段 和 緩沖區(qū) 共享同一個(gè)底層數(shù)據(jù)數(shù)組贾节,我們?cè)谙乱还?jié)將會(huì)看到這一點(diǎn)汁汗。

緩沖區(qū)份片和數(shù)據(jù)共享
我們已經(jīng)創(chuàng)建了原緩沖區(qū)的子緩沖區(qū),并且我們知道緩沖區(qū)和子緩沖區(qū)共享同一個(gè)底層數(shù)據(jù)數(shù)組栗涂。讓我們看看這意味著什么知牌。

我們遍歷子緩沖區(qū),將每一個(gè)元素乘以 11 來(lái)改變它斤程。例如角寸,5 會(huì)變成 55。

for (int i=0; i<slice.capacity(); ++i) { byte b = slice.get( i ); b *= 11; slice.put( i, b ); }

最后忿墅,再看一下原緩沖區(qū)中的內(nèi)容:

buffer.position( 0 ); buffer.limit( buffer.capacity() ); while (buffer.remaining()>0) { System.out.println( buffer.get() ); }

結(jié)果表明只有在子緩沖區(qū)窗口中的元素被改變了:

$ java SliceBuffer 0 12 33 44 55 66 7 8 9
緩沖區(qū)片對(duì)于促進(jìn)抽象非常有幫助扁藕。可以編寫(xiě)自己的函數(shù)處理整個(gè)緩沖區(qū)球匕,而且如果想要將這個(gè)過(guò)程應(yīng)用于子緩沖區(qū)上纹磺,您只需取主緩沖區(qū)的一個(gè)片帖烘,并將它傳遞給您的函數(shù)亮曹。這比編寫(xiě)自己的函數(shù)來(lái)取額外的參數(shù)以指定要對(duì)緩沖區(qū)的哪一部分進(jìn)行操作更容易。

只讀緩沖區(qū)
只讀緩沖區(qū)非常簡(jiǎn)單 ― 您可以讀取它們秘症,但是不能向它們寫(xiě)入照卦。可以通過(guò)調(diào)用緩沖區(qū)的 asReadOnlyBuffer()方法乡摹,將任何常規(guī)緩沖區(qū)轉(zhuǎn)換為只讀緩沖區(qū)役耕,這個(gè)方法返回一個(gè)與原緩沖區(qū)完全相同的緩沖區(qū)(并與其共享數(shù)據(jù)),只不過(guò)它是只讀的聪廉。

只讀緩沖區(qū)對(duì)于保護(hù)數(shù)據(jù)很有用瞬痘。在將緩沖區(qū)傳遞給某個(gè)對(duì)象的方法時(shí),您無(wú)法知道這個(gè)方法是否會(huì)修改緩沖區(qū)中的數(shù)據(jù)板熊。創(chuàng)建一個(gè)只讀的緩沖區(qū)可以 *保證 *該緩沖區(qū)不會(huì)被修改.

不能將只讀的緩沖區(qū)轉(zhuǎn)換為可寫(xiě)的緩沖區(qū)框全。

直接和間接緩沖區(qū)
另一種有用的 ByteBuffer是直接緩沖區(qū)。 *直接緩沖區(qū) *是為加快 I/O 速度干签,而以一種特殊的方式分配其內(nèi)存的緩沖區(qū)津辩。

實(shí)際上,直接緩沖區(qū)的準(zhǔn)確定義是與實(shí)現(xiàn)相關(guān)的。Sun 的文檔是這樣描述直接緩沖區(qū)的:

給定一個(gè)直接字節(jié)緩沖區(qū)喘沿,Java 虛擬機(jī)將盡最大努力直接對(duì)它執(zhí)行本機(jī) I/O 操作闸度。也就是說(shuō),它會(huì)在每一次調(diào)用底層操作系統(tǒng)的本機(jī) I/O 操作之前(或之后)蚜印,嘗試避免將緩沖區(qū)的內(nèi)容拷貝到一個(gè)中間緩沖區(qū)中(或者從一個(gè)中間緩沖區(qū)中拷貝數(shù)據(jù))莺禁。

您可以在例子程序 FastCopyFile.java 中看到直接緩沖區(qū)的實(shí)際應(yīng)用,這個(gè)程序是 CopyFile.java 的另一個(gè)版本晒哄,它使用了直接緩沖區(qū)以提高速度睁宰。

還可以用內(nèi)存映射文件創(chuàng)建直接緩沖區(qū)。

內(nèi)存映射文件 I/O
內(nèi)存映射文件 I/O 是一種讀和寫(xiě)文件數(shù)據(jù)的方法寝凌,它可以比常規(guī)的基于流或者基于通道的 I/O 快得多柒傻。

內(nèi)存映射文件 I/O 是通過(guò)使文件中的數(shù)據(jù)神奇般地出現(xiàn)為內(nèi)存數(shù)組的內(nèi)容來(lái)完成的。這其初聽(tīng)起來(lái)似乎不過(guò)就是將整個(gè)文件讀到內(nèi)存中较木,但是事實(shí)上并不是這樣红符。一般來(lái)說(shuō),只有文件中實(shí)際讀取或者寫(xiě)入的部分才會(huì)送入(或者 *映射 *)到內(nèi)存中伐债。

內(nèi)存映射并不真的神奇或者多么不尋吃ず睿。現(xiàn)代操作系統(tǒng)一般根據(jù)需要將文件的部分映射為內(nèi)存的部分峰锁,從而實(shí)現(xiàn)文件系統(tǒng)萎馅。Java 內(nèi)存映射機(jī)制不過(guò)是在底層操作系統(tǒng)中可以采用這種機(jī)制時(shí),提供了對(duì)該機(jī)制的訪問(wèn)虹蒋。

盡管創(chuàng)建內(nèi)存映射文件相當(dāng)簡(jiǎn)單糜芳,但是向它寫(xiě)入可能是危險(xiǎn)的。僅只是改變數(shù)組的單個(gè)元素這樣的簡(jiǎn)單操作魄衅,就可能會(huì)直接修改磁盤(pán)上的文件峭竣。修改數(shù)據(jù)與將數(shù)據(jù)保存到磁盤(pán)是沒(méi)有分開(kāi)的。

將文件映射到內(nèi)存
了解內(nèi)存映射的最好方法是使用例子晃虫。在下面的例子中皆撩,我們要將一個(gè) FileChannel (它的全部或者部分)映射到內(nèi)存中。為此我們將使用FileChannel.map()方法哲银。下面代碼行將文件的前 1024 個(gè)字節(jié)映射到內(nèi)存中:

MappedByteBuffer mbb = fc.map( FileChannel.MapMode.READ_WRITE, 0, 1024 );
map() 方法返回一個(gè) MappedByteBuffer扛吞,它是 ByteBuffer 的子類。因此荆责,您可以像使用其他任何 ByteBuffer一樣使用新映射的緩沖區(qū)滥比,操作系統(tǒng)會(huì)在需要時(shí)負(fù)責(zé)執(zhí)行行映射。


分散和聚集
概述
分散/聚集 I/O 是使用多個(gè)而不是單個(gè)緩沖區(qū)來(lái)保存數(shù)據(jù)的讀寫(xiě)方法草巡。

一個(gè)分散的讀取就像一個(gè)常規(guī)通道讀取守呜,只不過(guò)它是將數(shù)據(jù)讀到一個(gè)緩沖區(qū)數(shù)組中而不是讀到單個(gè)緩沖區(qū)中型酥。同樣地,一個(gè)聚集寫(xiě)入是向緩沖區(qū)數(shù)組而不是向單個(gè)緩沖區(qū)寫(xiě)入數(shù)據(jù)查乒。

分散/聚集 I/O 對(duì)于將數(shù)據(jù)流劃分為單獨(dú)的部分很有用弥喉,這有助于實(shí)現(xiàn)復(fù)雜的數(shù)據(jù)格式。

分散/聚集 I/O
通道可以有選擇地實(shí)現(xiàn)兩個(gè)新的接口: ScatteringByteChannel
和 GatheringByteChannel玛迄。一個(gè) ScatteringByteChannel
是一個(gè)具有兩個(gè)附加讀方法的通道:

  • long read( ByteBuffer[] dsts );
  • long read( ByteBuffer[] dsts, int offset, int length );

這些 long read() 方法很像標(biāo)準(zhǔn)的 read 方法由境,只不過(guò)它們不是取單個(gè)緩沖區(qū)而是取一個(gè)緩沖區(qū)數(shù)組。

在 *分散讀取 *中蓖议,通道依次填充每個(gè)緩沖區(qū)虏杰。填滿一個(gè)緩沖區(qū)后,它就開(kāi)始填充下一個(gè)勒虾。在某種意義上纺阔,緩沖區(qū)數(shù)組就像一個(gè)大緩沖區(qū)。

分散/聚集的應(yīng)用
分散/聚集 I/O 對(duì)于將數(shù)據(jù)劃分為幾個(gè)部分很有用修然。例如笛钝,您可能在編寫(xiě)一個(gè)使用消息對(duì)象的網(wǎng)絡(luò)應(yīng)用程序,每一個(gè)消息被劃分為固定長(zhǎng)度的頭部和固定長(zhǎng)度的正文愕宋。您可以創(chuàng)建一個(gè)剛好可以容納頭部的緩沖區(qū)和另一個(gè)剛好可以容難正文的緩沖區(qū)玻靡。當(dāng)您將它們放入一個(gè)數(shù)組中并使用分散讀取來(lái)向它們讀入消息時(shí),頭部和正文將整齊地劃分到這兩個(gè)緩沖區(qū)中中贝。

我們從緩沖區(qū)所得到的方便性對(duì)于緩沖區(qū)數(shù)組同樣有效囤捻。因?yàn)槊恳粋€(gè)緩沖區(qū)都跟蹤自己還可以接受多少數(shù)據(jù),所以分散讀取會(huì)自動(dòng)找到有空間接受數(shù)據(jù)的第一個(gè)緩沖區(qū)邻寿。在這個(gè)緩沖區(qū)填滿后蝎土,它就會(huì)移動(dòng)到下一個(gè)緩沖區(qū)。

聚集寫(xiě)入
*聚集寫(xiě)入 *類似于分散讀取老厌,只不過(guò)是用來(lái)寫(xiě)入瘟则。它也有接受緩沖區(qū)數(shù)組的方法:

  • long write( ByteBuffer[] srcs );
  • long write( ByteBuffer[] srcs, int offset, int length );

聚集寫(xiě)對(duì)于把一組單獨(dú)的緩沖區(qū)中組成單個(gè)數(shù)據(jù)流很有用黎炉。為了與上面的消息例子保持一致枝秤,您可以使用聚集寫(xiě)入來(lái)自動(dòng)將網(wǎng)絡(luò)消息的各個(gè)部分組裝為單個(gè)數(shù)據(jù)流,以便跨越網(wǎng)絡(luò)傳輸消息慷嗜。

從例子程序 UseScatterGather.java 中可以看到分散讀取和聚集寫(xiě)入的實(shí)際應(yīng)用淀弹。


文件鎖定
概述
文件鎖定初看起來(lái)可能讓人迷惑。它 *似乎 *指的是防止程序或者用戶訪問(wèn)特定文件庆械。事實(shí)上薇溃,文件鎖就像常規(guī)的 Java 對(duì)象鎖 ― 它們是 *勸告式的(advisory) *鎖。它們不阻止任何形式的數(shù)據(jù)訪問(wèn)缭乘,相反沐序,它們通過(guò)鎖的共享和獲取賴允許系統(tǒng)的不同部分相互協(xié)調(diào)。

您可以鎖定整個(gè)文件或者文件的一部分。如果您獲取一個(gè)排它鎖策幼,那么其他人就不能獲得同一個(gè)文件或者文件的一部分上的鎖邑时。如果您獲得一個(gè)共享鎖,那么其他人可以獲得同一個(gè)文件或者文件一部分上的共享鎖特姐,但是不能獲得排它鎖晶丘。文件鎖定并不總是出于保護(hù)數(shù)據(jù)的目的。例如唐含,您可能臨時(shí)鎖定一個(gè)文件以保證特定的寫(xiě)操作成為原子的浅浮,而不會(huì)有其他程序的干擾。

大多數(shù)操作系統(tǒng)提供了文件系統(tǒng)鎖捷枯,但是它們并不都是采用同樣的方式滚秩。有些實(shí)現(xiàn)提供了共享鎖,而另一些僅提供了排它鎖淮捆。事實(shí)上叔遂,有些實(shí)現(xiàn)使得文件的鎖定部分不可訪問(wèn),盡管大多數(shù)實(shí)現(xiàn)不是這樣的争剿。

在本節(jié)中已艰,您將學(xué)習(xí)如何在 NIO 中執(zhí)行簡(jiǎn)單的文件鎖過(guò)程,我們還將探討一些保證被鎖定的文件盡可能可移植的方法蚕苇。

鎖定文件
要獲取文件的一部分上的鎖哩掺,您要調(diào)用一個(gè)打開(kāi)的 FileChannel
上的 lock() 方法。注意涩笤,如果要獲取一個(gè)排它鎖嚼吞,您必須以寫(xiě)方式打開(kāi)文件。

RandomAccessFile raf = new RandomAccessFile( "usefilelocks.txt", "rw" ); FileChannel fc = raf.getChannel(); FileLock lock = fc.lock( start, end, false );

在擁有鎖之后蹬碧,您可以執(zhí)行需要的任何敏感操作舱禽,然后再釋放鎖:
lock.release();

在釋放鎖后,嘗試獲得鎖的其他任何程序都有機(jī)會(huì)獲得它恩沽。

本小節(jié)的例子程序 UseFileLocks.java 必須與它自己并行運(yùn)行誊稚。這個(gè)程序獲取一個(gè)文件上的鎖,持有三秒鐘罗心,然后釋放它里伯。如果同時(shí)運(yùn)行這個(gè)程序的多個(gè)實(shí)例,您會(huì)看到每個(gè)實(shí)例依次獲得鎖渤闷。

文件鎖定和可移植性
文件鎖定可能是一個(gè)復(fù)雜的操作疾瓮,特別是考慮到不同的操作系統(tǒng)是以不同的方式實(shí)現(xiàn)鎖這一事實(shí)。下面的指導(dǎo)原則將幫助您盡可能保持代碼的可移植性:

  • 只使用排它鎖飒箭。
  • 將所有的鎖視為勸告式的(advisory)狼电。

連網(wǎng)和異步 I/O
概述
連網(wǎng)是學(xué)習(xí)異步 I/O 的很好基礎(chǔ)蜒灰,而異步 I/O 對(duì)于在 Java 語(yǔ)言中執(zhí)行任何輸入/輸出過(guò)程的人來(lái)說(shuō),無(wú)疑都是必須具備的知識(shí)肩碟。NIO 中的連網(wǎng)與 NIO 中的其他任何操作沒(méi)有什么不同 ― 它依賴通道和緩沖區(qū)卷员,而您通常使用 InputStream和 OutputStream 來(lái)獲得通道。

本節(jié)首先介紹異步 I/O 的基礎(chǔ) ― 它是什么以及它不是什么腾务,然后轉(zhuǎn)向更實(shí)用的毕骡、程序性的例子。

異步 I/O
異步 I/O 是一種 *沒(méi)有阻塞地 *讀寫(xiě)數(shù)據(jù)的方法岩瘦。通常未巫,在代碼進(jìn)行 read()
調(diào)用時(shí),代碼會(huì)阻塞直至有可供讀取的數(shù)據(jù)启昧。同樣叙凡, write()
調(diào)用將會(huì)阻塞直至數(shù)據(jù)能夠?qū)懭搿?/p>

另一方面,異步 I/O 調(diào)用不會(huì)阻塞密末。相反握爷,您將注冊(cè)對(duì)特定 I/O 事件的興趣 ― 可讀的數(shù)據(jù)的到達(dá)、新的套接字連接严里,等等新啼,而在發(fā)生這樣的事件時(shí),系統(tǒng)將會(huì)告訴您刹碾。

異步 I/O 的一個(gè)優(yōu)勢(shì)在于燥撞,它允許您同時(shí)根據(jù)大量的輸入和輸出執(zhí)行 I/O。同步程序常常要求助于輪詢迷帜,或者創(chuàng)建許許多多的線程以處理大量的連接物舒。使用異步 I/O,您可以監(jiān)聽(tīng)任何數(shù)量的通道上的事件戏锹,不用輪詢冠胯,也不用額外的線程。

我們將通過(guò)研究一個(gè)名為 MultiPortEcho.java 的例子程序來(lái)查看異步 I/O 的實(shí)際應(yīng)用锦针。這個(gè)程序就像傳統(tǒng)的 echo server荠察,它接受網(wǎng)絡(luò)連接并向它們回響它們可能發(fā)送的數(shù)據(jù)。不過(guò)它有一個(gè)附加的特性伞插,就是它能同時(shí)監(jiān)聽(tīng)多個(gè)端口割粮,并處理來(lái)自所有這些端口的連接盾碗。并且它只在單個(gè)線程中完成所有這些工作媚污。

Selectors
本節(jié)的闡述對(duì)應(yīng)于 MultiPortEcho的源代碼中的 go()方法的實(shí)現(xiàn),因此應(yīng)該看一下源代碼廷雅,以便對(duì)所發(fā)生的事情有個(gè)更全面的了解耗美。

異步 I/O 中的核心對(duì)象名為 Selector京髓。Selector 就是您注冊(cè)對(duì)各種 I/O 事件的興趣的地方,而且當(dāng)那些事件發(fā)生時(shí)商架,就是這個(gè)對(duì)象告訴您所發(fā)生的事件堰怨。

所以,我們需要做的第一件事就是創(chuàng)建一個(gè) Selector:
Selector selector = Selector.open();

然后狱窘,我們將對(duì)不同的通道對(duì)象調(diào)用 register()方法辞色,以便注冊(cè)我們對(duì)這些對(duì)象中發(fā)生的 I/O 事件的興趣赠制。register() 的第一個(gè)參數(shù)總是這個(gè) Selector。

打開(kāi)一個(gè) ServerSocketChannel
為了接收連接揽涮,我們需要一個(gè) ServerSocketChannel。事實(shí)上饿肺,我們要監(jiān)聽(tīng)的每一個(gè)端口都需要有一個(gè) ServerSocketChannel蒋困。對(duì)于每一個(gè)端口,我們打開(kāi)一個(gè) ServerSocketChannel敬辣,如下所示:
ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.configureBlocking( false ); ServerSocket ss = ssc.socket(); InetSocketAddress address = new InetSocketAddress( ports[i] ); ss.bind( address );

第一行創(chuàng)建一個(gè)新的 ServerSocketChannel
雪标,最后三行將它綁定到給定的端口。第二行將 ServerSocketChannel
設(shè)置為 *非阻塞的 *溉跃。我們必須對(duì)每一個(gè)要使用的套接字通道調(diào)用這個(gè)方法村刨,否則異步 I/O 就不能工作。

選擇鍵
下一步是將新打開(kāi)的 ServerSocketChannels 注冊(cè)到 Selector
上撰茎。為此我們使用 ServerSocketChannel.register() 方法烹困,如下所示:

SelectionKey key = ssc.register( selector, SelectionKey.OP_ACCEPT );

register()的第一個(gè)參數(shù)總是這個(gè) Selector。第二個(gè)參數(shù)是 OP_ACCEPT
乾吻,這里它指定我們想要監(jiān)聽(tīng) accept 事件髓梅,也就是在新的連接建立時(shí)所發(fā)生的事件。這是適用于 ServerSocketChannel的唯一事件類型绎签。

請(qǐng)注意對(duì) register()的調(diào)用的返回值枯饿。 SelectionKey代表這個(gè)通道在此 Selector上的這個(gè)注冊(cè)。當(dāng)某個(gè) Selector 通知您某個(gè)傳入事件時(shí)诡必,它是通過(guò)提供對(duì)應(yīng)于該事件的 SelectionKey來(lái)進(jìn)行的奢方。SelectionKey 還可以用于取消通道的注冊(cè)。

內(nèi)部循環(huán)
現(xiàn)在已經(jīng)注冊(cè)了我們對(duì)一些 I/O 事件的興趣爸舒,下面將進(jìn)入主循環(huán)蟋字。使用 Selectors 的幾乎每個(gè)程序都像下面這樣使用內(nèi)部循環(huán):

int num = selector.select(); Set selectedKeys = selector.selectedKeys(); Iterator it = selectedKeys.iterator(); while (it.hasNext()) { SelectionKey key = (SelectionKey)it.next(); // ... deal with I/O event ... }

首先,我們調(diào)用 Selector 的 select()方法扭勉。這個(gè)方法會(huì)阻塞鹊奖,直到至少有一個(gè)已注冊(cè)的事件發(fā)生。當(dāng)一個(gè)或者更多的事件發(fā)生時(shí)涂炎,select() 方法將返回所發(fā)生的事件的數(shù)量忠聚。

接下來(lái)设哗,我們調(diào)用 Selector 的 selectedKeys()方法,它返回發(fā)生了事件的 SelectionKey 對(duì)象的一個(gè) 集合 两蟀。

我們通過(guò)迭代 SelectionKeys 并依次處理每個(gè) SelectionKey
來(lái)處理事件网梢。對(duì)于每一個(gè) SelectionKey,您必須確定發(fā)生的是什么 I/O 事件赂毯,以及這個(gè)事件影響哪些 I/O 對(duì)象战虏。

監(jiān)聽(tīng)新連接
程序執(zhí)行到這里,我們僅注冊(cè)了 ServerSocketChannel党涕,并且僅注冊(cè)它們“接收”事件活烙。為確認(rèn)這一點(diǎn),我們對(duì) SelectionKey 調(diào)用 readyOps()
方法遣鼓,并檢查發(fā)生了什么類型的事件:

if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) { // Accept the new connection // ... }

可以肯定地說(shuō)啸盏, readOps()方法告訴我們?cè)撌录切碌倪B接。

接受新的連接
因?yàn)槲覀冎肋@個(gè)服務(wù)器套接字上有一個(gè)傳入連接在等待骑祟,所以可以安全地接受它回懦;也就是說(shuō),不用擔(dān)心 accept() 操作會(huì)阻塞:
ServerSocketChannel ssc = (ServerSocketChannel)key.channel(); SocketChannel sc = ssc.accept();

下一步是將新連接的 SocketChannel配置為非阻塞的次企。而且由于接受這個(gè)連接的目的是為了讀取來(lái)自套接字的數(shù)據(jù)怯晕,所以我們還必須將SocketChannel注冊(cè)到 Selector上,如下所示:

sc.configureBlocking( false ); SelectionKey newKey = sc.register( selector, SelectionKey.OP_READ );

注意我們使用 register()的 OP_READ 參數(shù)缸棵,將 SocketChannel注冊(cè)用于 *讀取 *而不是 *接受 *新連接舟茶。

刪除處理過(guò)的 SelectionKey
在處理 SelectionKey之后,我們幾乎可以返回主循環(huán)了堵第。但是我們必須首先將處理過(guò)的 SelectionKey 從選定的鍵集合中刪除吧凉。如果我們沒(méi)有刪除處理過(guò)的鍵,那么它仍然會(huì)在主集合中以一個(gè)激活的鍵出現(xiàn)踏志,這會(huì)導(dǎo)致我們嘗試再次處理它阀捅。我們調(diào)用迭代器的 remove() 方法來(lái)刪除處理過(guò)的 SelectionKey:

it.remove();

現(xiàn)在我們可以返回主循環(huán)并接受從一個(gè)套接字中傳入的數(shù)據(jù)(或者一個(gè)傳入的 I/O 事件)了。

傳入的 I/O
當(dāng)來(lái)自一個(gè)套接字的數(shù)據(jù)到達(dá)時(shí)针余,它會(huì)觸發(fā)一個(gè) I/O 事件饲鄙。這會(huì)導(dǎo)致在主循環(huán)中調(diào)用 Selector.select(),并返回一個(gè)或者多個(gè) I/O 事件圆雁。這一次忍级, SelectionKey將被標(biāo)記為 OP_READ事件,如下所示:

} else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) { // Read the data SocketChannel sc = (SocketChannel)key.channel(); // ... }

與以前一樣伪朽,我們?nèi)〉冒l(fā)生 I/O 事件的通道并處理它轴咱。在本例中,由于這是一個(gè) echo server,我們只希望從套接字中讀取數(shù)據(jù)并馬上將它發(fā)送回去嗦玖。關(guān)于這個(gè)過(guò)程的細(xì)節(jié)患雇,請(qǐng)參見(jiàn) 參考資料 中的源代碼 (MultiPortEcho.java)跃脊。

回到主循環(huán)
每次返回主循環(huán)宇挫,我們都要調(diào)用 select的 Selector()方法,并取得一組 SelectionKey酪术。每個(gè)鍵代表一個(gè) I/O 事件器瘪。我們處理事件,從選定的鍵集中刪除 SelectionKey绘雁,然后返回主循環(huán)的頂部橡疼。

這個(gè)程序有點(diǎn)過(guò)于簡(jiǎn)單,因?yàn)樗哪康闹皇钦故井惒?I/O 所涉及的技術(shù)庐舟。在現(xiàn)實(shí)的應(yīng)用程序中欣除,您需要通過(guò)將通道從 Selector 中刪除來(lái)處理關(guān)閉的通道。而且您可能要使用多個(gè)線程挪略。這個(gè)程序可以僅使用一個(gè)線程历帚,因?yàn)樗皇且粋€(gè)演示,但是在現(xiàn)實(shí)場(chǎng)景中杠娱,創(chuàng)建一個(gè)線程池來(lái)負(fù)責(zé) I/O 事件處理中的耗時(shí)部分會(huì)更有意義挽牢。


字符集
概述
根據(jù) Sun 的文檔,一個(gè) Charset 是“十六位 Unicode 字符序列與字節(jié)序列之間的一個(gè)命名的映射”禽拔。實(shí)際上,一個(gè) Charset 允許您以盡可能最具可移植性的方式讀寫(xiě)字符序列室叉。

Java 語(yǔ)言被定義為基于 Unicode睹栖。然而在實(shí)際上,許多人編寫(xiě)代碼時(shí)都假設(shè)一個(gè)字符在磁盤(pán)上或者在網(wǎng)絡(luò)流中用一個(gè)字節(jié)表示茧痕。這種假設(shè)在許多情況下成立磨淌,但是并不是在所有情況下都成立,而且隨著計(jì)算機(jī)變得對(duì) Unicode 越來(lái)越友好凿渊,這個(gè)假設(shè)就日益變得不能成立了梁只。

在本節(jié)中,我們將看一下如何使用 Charsets 以適合現(xiàn)代文本格式的方式處理文本數(shù)據(jù)埃脏。這里將使用的示例程序相當(dāng)簡(jiǎn)單搪锣,不過(guò),它觸及了使用 Charset 的所有關(guān)鍵方面:為給定的字符編碼創(chuàng)建 Charset彩掐,以及使用該 Charset 解碼和編碼文本數(shù)據(jù)构舟。

編碼/解碼
要讀和寫(xiě)文本,我們要分別使用 CharsetDecoder 和 CharsetEncoder堵幽。將它們稱為 *編碼器 *和 *解碼器 *是有道理的狗超。一個(gè) *字符 *不再表示一個(gè)特定的位模式弹澎,而是表示字符系統(tǒng)中的一個(gè)實(shí)體。因此努咐,由某個(gè)實(shí)際的位模式表示的字符必須以某種特定的 *編碼 *來(lái)表示苦蒿。

CharsetDecoder 用于將逐位表示的一串字符轉(zhuǎn)換為具體的 char
值。同樣渗稍,一個(gè) CharsetEncoder用于將字符轉(zhuǎn)換回位佩迟。

在下一個(gè)小節(jié)中,我們將考察一個(gè)使用這些對(duì)象來(lái)讀寫(xiě)數(shù)據(jù)的程序竿屹。

處理文本的正確方式
現(xiàn)在我們將分析這個(gè)例子程序 UseCharsets.java报强。這個(gè)程序非常簡(jiǎn)單 ― 它從一個(gè)文件中讀取一些文本,并將該文本寫(xiě)入另一個(gè)文件拱燃。但是它把該數(shù)據(jù)當(dāng)作文本數(shù)據(jù)秉溉,并使用 CharBuffer來(lái)將該數(shù)句讀入一個(gè) CharsetDecoder中。同樣碗誉,它使用 CharsetEncoder 來(lái)寫(xiě)回該數(shù)據(jù)召嘶。

我們將假設(shè)字符以 ISO-8859-1(Latin1) 字符集(這是 ASCII 的標(biāo)準(zhǔn)擴(kuò)展)的形式儲(chǔ)存在磁盤(pán)上。盡管我們必須為使用 Unicode 做好準(zhǔn)備诗充,但是也必須認(rèn)識(shí)到不同的文件是以不同的格式儲(chǔ)存的苍蔬,而 ASCII 無(wú)疑是非常普遍的一種格式。事實(shí)上蝴蜓,每種 Java 實(shí)現(xiàn)都要求對(duì)以下字符編碼提供完全的支持:

  • US-ASCII
  • ISO-8859-1
  • UTF-8
  • UTF-16BE
  • UTF-16LE
  • UTF-16

示例程序
在打開(kāi)相應(yīng)的文件碟绑、將輸入數(shù)據(jù)讀入名為 inputData的 ByteBuffer
之后,我們的程序必須創(chuàng)建 ISO-8859-1 (Latin1) 字符集的一個(gè)實(shí)例:
Charset latin1 = Charset.forName( "ISO-8859-1" );

然后茎匠,創(chuàng)建一個(gè)解碼器(用于讀雀裰佟)和一個(gè)編碼器 (用于寫(xiě)入):
CharsetDecoder decoder = latin1.newDecoder(); CharsetEncoder encoder = latin1.newEncoder();

為了將字節(jié)數(shù)據(jù)解碼為一組字符,我們把 ByteBuffer傳遞給 CharsetDecoder诵冒,結(jié)果得到一個(gè) CharBuffer:
CharBuffer cb = decoder.decode( inputData );

如果想要處理字符凯肋,我們可以在程序的此處進(jìn)行。但是我們只想無(wú)改變地將它寫(xiě)回汽馋,所以沒(méi)有什么要做的侮东。
要寫(xiě)回?cái)?shù)據(jù),我們必須使用 CharsetEncoder將它轉(zhuǎn)換回字節(jié):
ByteBuffer outputData = encoder.encode( cb );

在轉(zhuǎn)換完成之后豹芯,我們就可以將數(shù)據(jù)寫(xiě)到文件中了悄雅。


結(jié)束語(yǔ)和參考資料
結(jié)束語(yǔ)
正如您所看到的, NIO 庫(kù)有大量的特性铁蹈。在一些新特性(例如文件鎖定和字符集)提供新功能的同時(shí)宽闲,許多特性在優(yōu)化方面也非常優(yōu)秀。

在基礎(chǔ)層次上,通道和緩沖區(qū)可以做的事情幾乎都可以用原來(lái)的面向流的類來(lái)完成容诬。但是通道和緩沖區(qū)允許以 *快得多 *的方式完成這些相同的舊操作 ― 事實(shí)上接近系統(tǒng)所允許的最大速度娩梨。

不過(guò) NIO 最強(qiáng)大的長(zhǎng)度之一在于,它提供了一種在 Java 語(yǔ)言中執(zhí)行進(jìn)行輸入/輸出的新的(也是迫切需要的)結(jié)構(gòu)化方式览徒。隨諸如緩沖區(qū)狈定、通道和異步 I/O 這些概念性(且可實(shí)現(xiàn)的)實(shí)體而來(lái)的,是我們重新思考 Java 程序中的 I/O過(guò)程的機(jī)會(huì)吱殉。這樣掸冤,NIO 甚至為我們最熟悉的 I/O 過(guò)程也帶來(lái)了新的活力厘托,同時(shí)賦予我們通過(guò)和以前不同并且更好的方式執(zhí)行它們的機(jī)會(huì)友雳。

參考資料

  • 下載 本教程中的例子的完整源代碼。

  • 關(guān)于安裝和配置 JDK 1.4 的更多信息铅匹,請(qǐng)參見(jiàn) SDK 文檔 押赊。

  • Sun's guide to the new I/O APIs 提供了對(duì) NIO 的全面介紹,包括一些本教程沒(méi)有涵蓋的細(xì)節(jié)內(nèi)容包斑。

  • 在線 API 規(guī)范 描述了 NIO 的類和方法流礁,該規(guī)范使用的是您了解并喜歡的 autodoc 格式。

  • JSR 51 是 Java Community Process 文檔罗丰,它最先規(guī)定了 NIO 的新特性神帅。事實(shí)上,JDK 1.4 中實(shí)現(xiàn)的 NIO 是該文檔描述的特性的一個(gè)子集萌抵。

  • 想獲得關(guān)于流 I/O(包括問(wèn)題找御、解決方案和 NIO 的介紹)的全面介紹嗎?再?zèng)]有比 Merlin Hughes 的"Turning streams inside out " (developerWorks绍填,2002年7月)更好的了霎桅。

  • 當(dāng)然,還可以學(xué)習(xí)教程"Introduction to Java I/O" (developerWorks讨永,2000年4月)滔驶,它討論了 JDK 1.4 之前的 Java I/O 的所有基礎(chǔ)。

  • John Zukowski 在其 *Merlin 的魔力 *專欄中撰寫(xiě)了一些關(guān)于 NIO 的優(yōu)秀文章:

  • "" The ins and outs of Merlin's new I/O buffers " (developerWorks卿闹,2003年3月)是介紹緩沖區(qū)基本知識(shí)的另一篇文章揭糕。

  • "" Character sets " " (developerWorks,2002年10月)專門(mén)討論字符集(特別是轉(zhuǎn)換和編碼模式)锻霎。

  • 通過(guò) Kalagnanam 和 Balu G 的 "" Merlin brings nonblocking I/O to the Java platform " (developerWorks著角,2002年3月)進(jìn)一步了解 NIO。

  • Greg Travis 在他的 "*JDK 1.4 Tutorial” *(Manning 出版社量窘,2002年3月)一書(shū)中仔細(xì)研究了 NIO雇寇。

  • 您可以在 developerWorks Java 技術(shù)專區(qū) 找到數(shù)百篇關(guān)于 Java 編程的各個(gè)方面的文章。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市锨侯,隨后出現(xiàn)的幾起案子嫩海,更是在濱河造成了極大的恐慌,老刑警劉巖囚痴,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叁怪,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡深滚,警方通過(guò)查閱死者的電腦和手機(jī)奕谭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)痴荐,“玉大人血柳,你說(shuō)我怎么就攤上這事∩祝” “怎么了难捌?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)鸦难。 經(jīng)常有香客問(wèn)我根吁,道長(zhǎng),這世上最難降的妖魔是什么合蔽? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任击敌,我火速辦了婚禮,結(jié)果婚禮上拴事,老公的妹妹穿的比我還像新娘沃斤。我一直安慰自己,他們只是感情好挤聘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布轰枝。 她就那樣靜靜地躺著,像睡著了一般组去。 火紅的嫁衣襯著肌膚如雪鞍陨。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,727評(píng)論 1 305
  • 那天从隆,我揣著相機(jī)與錄音诚撵,去河邊找鬼。 笑死键闺,一個(gè)胖子當(dāng)著我的面吹牛寿烟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播辛燥,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼筛武,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼缝其!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起徘六,我...
    開(kāi)封第一講書(shū)人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤内边,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后待锈,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體漠其,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年竿音,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了和屎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡春瞬,死狀恐怖柴信,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情快鱼,我是刑警寧澤颠印,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布纲岭,位于F島的核電站抹竹,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏止潮。R本人自食惡果不足惜窃判,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望喇闸。 院中可真熱鬧袄琳,春花似錦、人聲如沸燃乍。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)刻蟹。三九已至逗旁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間舆瘪,已是汗流浹背片效。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留英古,地道東北人淀衣。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像召调,于是被迫代替她去往敵國(guó)和親膨桥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蛮浑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Java NIO(New IO)是從Java 1.4版本開(kāi)始引入的一個(gè)新的IO API,可以替代標(biāo)準(zhǔn)的Java I...
    JackChen1024閱讀 7,555評(píng)論 1 143
  • ChannelChannel CharacteristicsJava NIO Channel Classesbuf...
    六尺帳篷閱讀 3,928評(píng)論 2 28
  • Buffer java NIO庫(kù)是在jdk1.4中引入的只嚣,NIO與IO之間的第一個(gè)區(qū)別在于陵吸,IO是面向流的,而NI...
    德彪閱讀 2,209評(píng)論 0 3
  • 紅哈哈閱讀 220評(píng)論 0 0
  • 1日正好是一個(gè)開(kāi)始囚似, 那么就來(lái)寫(xiě)寫(xiě)關(guān)于結(jié)束和開(kāi)始的故事吧。 看到群里有伙伴說(shuō)线得,新的一天饶唤,有很多事想要有新的開(kāi)始,是...
    怡兒話書(shū)影閱讀 615評(píng)論 0 1