| 第二章 緩沖區(qū) 我們以 Buffer類開(kāi)始我們對(duì)java.nio軟件包的瀏覽歷程。這些類是java.nio的構(gòu)造基礎(chǔ)。在本章中,我們將深入研究緩沖區(qū),了解各種不同的類型,并學(xué)會(huì)怎樣使用。到那時(shí)我們將明了java.nio緩沖區(qū)是如何與java.nio.channels這一通道類相聯(lián)系的。 一個(gè)Buffer對(duì)象是固定數(shù)量的數(shù)據(jù)的容器。其作用是一個(gè)存儲(chǔ)器,或者分段運(yùn)輸區(qū),在這里數(shù)據(jù)可被存儲(chǔ)并在之后用于檢索。緩沖區(qū)如我們?cè)诘谝徽滤懻摰哪菢颖粚?xiě)滿和釋放。對(duì)于每個(gè)非布爾原始數(shù)據(jù)類型都有一個(gè)緩沖區(qū)類。盡管緩沖區(qū)作用于它們存儲(chǔ)的原始數(shù)據(jù)類型,但緩沖區(qū)十分傾向于處理字節(jié)。非字節(jié)緩沖區(qū)可以在后臺(tái)執(zhí)行從字節(jié)或到字節(jié)的轉(zhuǎn)換,這取決于緩沖區(qū)是如何創(chuàng)建的。我們將在本章節(jié)后面的部分檢查緩沖區(qū)數(shù)據(jù)存儲(chǔ)的含義。 緩沖區(qū)的工作與通道緊密聯(lián)系。通道是 I/O 傳輸發(fā)生時(shí)通過(guò)的入口,而緩沖區(qū)是這些數(shù)據(jù)傳輸?shù)膩?lái)源或目標(biāo)。對(duì)于離開(kāi)緩沖區(qū)的傳輸,您想傳遞出去的數(shù)據(jù)被置于一個(gè)緩沖區(qū),被傳送到通道。對(duì)于傳回緩沖區(qū)的傳輸,一個(gè)通道將數(shù)據(jù)放置在您所提供的緩沖區(qū)中。這種在協(xié)同對(duì)象(通常是您所寫(xiě)的對(duì)象以及一到多個(gè)Channel對(duì)象之間進(jìn)行的緩沖區(qū)數(shù)據(jù)傳遞是高效數(shù)據(jù)處理的關(guān)鍵。通道將在第三章被詳細(xì)涉及。 圖 2-1 是 Buffer的類層次圖。在頂部是通用Buffer類(抽象父類)。Buffer定義所有緩沖區(qū)類型共有的操作,無(wú)論是它們所包含的數(shù)據(jù)類型還是可能具有的特定行為。這一共同點(diǎn)將會(huì)成為我們的出發(fā)點(diǎn)。 圖 2-1. Buffer 類的家譜 2.1 緩沖區(qū)基礎(chǔ) 概念上,緩沖區(qū)是包在一個(gè)對(duì)象內(nèi)的基本數(shù)據(jù)元素?cái)?shù)組。Buffer類相比一個(gè)簡(jiǎn)單數(shù)組的優(yōu)點(diǎn)是它將關(guān)于數(shù)據(jù)的數(shù)據(jù)內(nèi)容和信息包含在一個(gè)單一的對(duì)象中。Buffer類以及它專有的子類定義了一個(gè)用于處理數(shù)據(jù)緩沖區(qū)的 API。 2.1.1 屬性 所有的緩沖區(qū)都具有四個(gè)屬性來(lái)提供關(guān)于其所包含的數(shù)據(jù)元素的信息。它們是: 容量(Capacity) 緩沖區(qū)能夠容納的數(shù)據(jù)元素的最大數(shù)量。這一容量在緩沖區(qū)創(chuàng)建時(shí)被設(shè)定,并且永遠(yuǎn)不能被改變。 上界(Limit) 緩沖區(qū)的第一個(gè)不能被讀或?qū)懙脑?。或者說(shuō),緩沖區(qū)中現(xiàn)存元素的計(jì)數(shù)。 位置(Position) 下一個(gè)要被讀或?qū)懙脑氐乃饕N恢脮?huì)自動(dòng)由相應(yīng)的 get()和 put()函數(shù)更新。 標(biāo)記(Mark) 一個(gè)備忘位置。調(diào)用 mark()來(lái)設(shè)定 mark = postion。調(diào)用 reset()設(shè)定 position = mark。標(biāo)記在設(shè)定前是未定義的(undefined)。 這四個(gè)屬性之間總是遵循以下關(guān)系: 0 <= mark <= position <= limit <= capacity 讓我們來(lái)看看這些屬性在實(shí)際應(yīng)用中的一些例子。圖 2-2 展示了一個(gè)新創(chuàng)建的容量為10的 ByteBuffer邏輯視圖。 圖 2-2. 新創(chuàng)建的 ByteBuffer 位置被設(shè)為 0,而且容量和上界被設(shè)為 10,剛好經(jīng)過(guò)緩沖區(qū)能夠容納的最后一個(gè)字節(jié)。標(biāo)記最初未定義。容量是固定的,但另外的三個(gè)屬性可以在使用緩沖區(qū)時(shí)改變。 2.1.2 緩沖區(qū) API 讓我們來(lái)看一下可以如何使用一個(gè)緩沖區(qū)。以下是 Buffer 類的方法簽名: package java.nio; public abstract class Buffer { public final int capacity(); public final int position(); public final Buffer position (int newPosition); public final int limit(); public final Buffer limit (int newLimit); public final Buffer mark(); public final Buffer reset(); public final Buffer clear(); public final Buffer flip(); public final Buffer rewind(); public final int remaining(); public final boolean hasRemaining(); public abstract boolean isReadOnly(); } 關(guān)于這個(gè)API有一點(diǎn)要注意的是,像clear()這類函數(shù),您通常應(yīng)當(dāng)返回void,而不是Buffer引用。這些函數(shù)將引用返回到它們?cè)?this)上被引用的對(duì)象。這是一個(gè)允許級(jí)聯(lián)調(diào)用的類設(shè)計(jì)方法。級(jí)聯(lián)調(diào)用允許這種類型的代碼: buffer.mark(); buffer.position(5); buffer.reset(); 被簡(jiǎn)寫(xiě)為: buffer.mark().position(5).reset(); java.nio中的類被特意地設(shè)計(jì)為支持級(jí)聯(lián)調(diào)用。您可能已經(jīng)在StringBuffer類中看到了級(jí)聯(lián)調(diào)用的使用。; 如果聰明地使用級(jí)聯(lián)調(diào)用,就能產(chǎn)生簡(jiǎn)潔,優(yōu)美,易讀的代碼。但如果濫用,就會(huì)使代碼不知所云。當(dāng)級(jí)聯(lián)調(diào)用可以增加可讀性并使讓您的目標(biāo)更加明確時(shí)使用它。如果使用級(jí)聯(lián)調(diào)用會(huì)使代碼作用不夠清晰,那么請(qǐng)不要使用它。請(qǐng)時(shí)刻保證您的代碼易于他人閱讀。 對(duì)于 API還要注意的一點(diǎn)是isReadOnly()函數(shù)。所有的緩沖區(qū)都是可讀的,但并非所有都可寫(xiě)。每個(gè)具體的緩沖區(qū)類都通過(guò)執(zhí)行isReadOnly()來(lái)標(biāo)示其是否允許該緩存區(qū)的內(nèi)容被修改。一些類型的緩沖區(qū)類可能未使其數(shù)據(jù)元素存儲(chǔ)在一個(gè)數(shù)組中 。例如MappedByteBuffer的內(nèi)容可能實(shí)際是一個(gè)只讀文件。您也可以明確地創(chuàng)建一個(gè)只讀視圖緩沖區(qū),來(lái)防止對(duì)內(nèi)容的意外修改。對(duì)只讀的緩沖區(qū)的修改嘗試將會(huì)導(dǎo)致ReadOnlyBufferException拋出。但是我們要提前做好準(zhǔn)備。 2.1.3 存取 讓我們從起點(diǎn)開(kāi)始。緩沖區(qū)管理著固定數(shù)目的數(shù)據(jù)元素。但在任何特定的時(shí)刻,我們可能只對(duì)緩沖區(qū)中的一部分元素感興趣。換句話說(shuō),在我們想清空緩沖區(qū)之前,我們可能只使用了緩沖區(qū)的一部分。這時(shí),我們需要能夠追蹤添加到緩沖區(qū)內(nèi)的數(shù)據(jù)元素的數(shù)量,放入下一個(gè)元素的位置等等的方法。位置屬性做到了這一點(diǎn)。它在調(diào)用put()時(shí)指出了下一個(gè)數(shù)據(jù)元素應(yīng)該被插入的位置,或者當(dāng)get()被調(diào)用時(shí)指出下一個(gè)元素應(yīng)從何處檢索。聰明的讀者會(huì)注意到上文所列出的的 Buffer API 并沒(méi)有包括get()或put()函數(shù)。每一個(gè)Buffer類都有這兩個(gè)函數(shù),但它們所采用的參數(shù)類型,以及它們返回的數(shù)據(jù)類型,對(duì)每個(gè)子類來(lái)說(shuō)都是唯一的,所以它們不能在頂層Buffer類中被抽象地聲明。它們的定義必須被特定類型的子類所遵從。對(duì)于這一討論,我們將假設(shè)使用具有這里所給出的函數(shù)的 ByteBuffer類【get()和put()還有更多的形式,我們將在 2.1.10 小節(jié)中進(jìn)行討論】: public abstract class ByteBuffer extends Buffer implements Comparable { // 這里是部分API列表 public abstract byte get(); public abstract byte get (int index); public abstract ByteBuffer put (byte b); public abstract ByteBuffer put (int index, byte b); } get和put可以是相對(duì)的或者是絕對(duì)的。在前面的程序列表中,相對(duì)方案是不帶有索引參數(shù)的函數(shù)。當(dāng)相對(duì)函數(shù)被調(diào)用時(shí),位置在返回時(shí)前進(jìn)一。如果位置前進(jìn)過(guò)多,相對(duì)運(yùn)算就會(huì)拋出異常。對(duì)于put(),如果運(yùn)算會(huì)導(dǎo)致位置超出上界 , 就會(huì)拋出BufferOverflowException異常。對(duì)于get(),如果位置不小于上界,就會(huì)拋出BufferUnderflowException異常。絕對(duì)存取不會(huì)影響緩沖區(qū)的位置屬性,但是如果您所提供的索引超出范圍(負(fù)數(shù)或不小于上界),也將拋出IndexOutOfBoundsException異常。 2.1.4 填充 讓我們看一個(gè)例子。我們將代表「Hello」字符串的 ASCII 碼載入一個(gè)名為 buffer 的ByteBuffer對(duì)象中。當(dāng)在圖 2.2 所新建的緩沖區(qū)上執(zhí)行以下代碼后,緩沖區(qū)的結(jié)果狀態(tài)如圖 2.3 所示: buffer.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'o'); 圖 2-3 五次調(diào)用 put()之后的緩沖區(qū) 注意本例中的每個(gè)字符都必須被強(qiáng)制轉(zhuǎn)換為 byte。我們不能不經(jīng)強(qiáng)制轉(zhuǎn)換而這樣操作: buffer.put('H'); 因?yàn)槲覀兇娣诺氖亲止?jié)而不是字符。記住在 java 中,字符在內(nèi)部以 Unicode 碼表示,每個(gè) Unicode 字符占 16 位。本章節(jié)的例子使用包含 ascii 字符集數(shù)值的字節(jié)。通過(guò)將char 強(qiáng)制轉(zhuǎn)換為 byte,我們刪除了前八位來(lái)建立一個(gè)八位字節(jié)值。這通常只適合于拉丁字符而不能適合所有可能的 Unicode 字符。為了讓事情簡(jiǎn)化,我們暫時(shí)故意忽略字符集的映射問(wèn)題。第六章中將詳細(xì)涉及字符編碼。 既然我們已經(jīng)在 buffer 中存放了一些數(shù)據(jù),如果我們想在不丟失位置的情況下進(jìn)行一些更改該怎么辦呢?put()的絕對(duì)方案可以達(dá)到這樣的目的。假設(shè)我們想將緩沖區(qū)中的內(nèi)容從「Hello」的 ASCII 碼更改為「Mellow」。我們可以這樣實(shí)現(xiàn): buffer.put(0,(byte)'M').put((byte)'w'); 這里通過(guò)進(jìn)行一次絕對(duì)方案的put將0位置的字節(jié)代替為十六進(jìn)制數(shù)值0x4d,將0x77放入當(dāng)前位置(當(dāng)前位置不會(huì)受到絕對(duì) put()的影響)的字節(jié),并將位置屬性加一。結(jié)果如圖 2.4 所示。 圖 2-4 修改后的 buffer 2.1.5 翻轉(zhuǎn) 我們已經(jīng)寫(xiě)滿了緩沖區(qū),現(xiàn)在我們必須準(zhǔn)備將其清空。我們想把這個(gè)緩沖區(qū)傳遞給一個(gè)通道,以使內(nèi)容能被全部寫(xiě)出。但如果通道現(xiàn)在在緩沖區(qū)上執(zhí)行g(shù)et(),那么它將從我們剛剛插入的有用數(shù)據(jù)之外取出未定義數(shù)據(jù)。如果我們將位置值重新設(shè)為 0,通道就會(huì)從正確位置開(kāi)始獲取,但是它是怎樣知道何時(shí)到達(dá)我們所插入數(shù)據(jù)末端的呢?這就是上界屬性被引入的目的。上界屬性指明了緩沖區(qū)有效內(nèi)容的末端。我們需要將上界屬性設(shè)置為當(dāng)前位置,然后將位置重置為 0。我們可以人工用下面的代碼實(shí)現(xiàn): buffer.limit(buffer.position()).position(0); 但這種從填充到釋放狀態(tài)的緩沖區(qū)翻轉(zhuǎn)是 API 設(shè)計(jì)者預(yù)先設(shè)計(jì)好的,他們?yōu)槲覀兲峁┝艘粋€(gè)非常便利的函數(shù): Buffer.flip(); flip()函數(shù)將一個(gè)能夠繼續(xù)添加數(shù)據(jù)元素的填充狀態(tài)的緩沖區(qū)翻轉(zhuǎn)成一個(gè)準(zhǔn)備讀出元素的釋放狀態(tài)。在翻轉(zhuǎn)之后,圖 2.4 的緩沖區(qū)會(huì)變成圖 2.5 中的樣子。 圖 2-5. 被翻轉(zhuǎn)后的緩沖區(qū) rewind()函數(shù)與flip()相似,但不影響上界屬性。它只是將位置值設(shè)回 0。您可以使用rewind()后退,重讀已經(jīng)被翻轉(zhuǎn)的緩沖區(qū)中的數(shù)據(jù)。 如果將緩沖區(qū)翻轉(zhuǎn)兩次會(huì)怎樣呢?它實(shí)際上會(huì)大小變?yōu)?0。按照?qǐng)D 2.5 的相同步驟對(duì)緩沖區(qū)進(jìn)行操作;把上界設(shè)為位置的值,并把位置設(shè)為 0。上界和位置都變成 0。嘗試對(duì)緩沖區(qū)上位置和上界都為 0 的 get()操作會(huì)導(dǎo)致BufferUnderflowException異常。而 put()則會(huì)導(dǎo)致 BufferOverflowException 異常。 2.1.6 釋放 如果我們現(xiàn)在將圖 2.5 中的緩沖區(qū)傳入通道,它將取出我們存放在那里的數(shù)據(jù),從位置開(kāi)始直到上界結(jié)束。很簡(jiǎn)單,不是嗎? 同樣地,如果您接收到一個(gè)在別處被填滿的緩沖區(qū),您可能需要在檢索內(nèi)容之前將其翻轉(zhuǎn)。例如,如果一個(gè)通道的read()操作完成,而您想要查看被通道放入緩沖區(qū)內(nèi)的數(shù)據(jù),那么您需要在調(diào)用get()之前翻轉(zhuǎn)緩沖區(qū)。通道對(duì)象在緩沖區(qū)上調(diào)用put()增加數(shù)據(jù);put()和read()可以隨意混合使用。 布爾函數(shù)hasRemaining()會(huì)在釋放緩沖區(qū)時(shí)告訴您是否已經(jīng)達(dá)到緩沖區(qū)的上界。以下是一種將數(shù)據(jù)元素從緩沖區(qū)釋放到一個(gè)數(shù)組的方法(在 2.1.10 小節(jié)中,我們將學(xué)到進(jìn)行批量傳輸?shù)母咝У姆椒?。 for (int i = 0; buffer.hasRemaining(), i ) { myByteArray [i] = buffer.get(); } 作為選擇,remaining()函數(shù)將告知您從當(dāng)前位置到上界還剩余的元素?cái)?shù)目。您也可以通過(guò)下面的循環(huán)來(lái)釋放圖 2-5 所示的緩沖區(qū)。 int count = buffer.remaining(); for (int i = 0; i < count, i ) { myByteArray [i] = buffer.get(); } 如果您對(duì)緩沖區(qū)有專門的控制,這種方法會(huì)更高效,因?yàn)樯辖绮粫?huì)在每次循環(huán)重復(fù)時(shí)都被檢查(這要求調(diào)用一個(gè)buffer樣例程序)。上文中的第一個(gè)例子允許多線程同時(shí)從緩沖區(qū)釋放元素。 緩沖區(qū)并不是多線程安全的。如果您想以多線程同時(shí)存取特定的緩沖區(qū),您需要在存取緩沖區(qū)之前進(jìn)行同步(例如對(duì)緩沖區(qū)對(duì)象進(jìn)行跟蹤)。 一旦緩沖區(qū)對(duì)象完成填充并釋放,它就可以被重新使用了。clear()函數(shù)將緩沖區(qū)重置為空狀態(tài)。它并不改變緩沖區(qū)中的任何數(shù)據(jù)元素,而是僅僅將上界設(shè)為容量的值,并把位置設(shè)回 0,如圖 2.2 所示。這使得緩沖區(qū)可以被重新填入。參見(jiàn)示例 2.1。 例 2.1 填充和釋放緩沖區(qū) package com.ronsoft.books.nio.buffers; import java.nio.CharBuffer; /** * Buffer fill/drain example. This code uses the simplest * means of filling and draining a buffer: one element at * a time. * @author Ron Hitchens (ron@ronsoft.com) */ public class BufferFillDrain{ public static void main (String [] argv) throws Exception{ CharBuffer buffer = CharBuffer.allocate (100); while (fillBuffer(buffer)) { buffer.flip(); drainBuffer(buffer); buffer.clear(); } } private static void drainBuffer (CharBuffer buffer){ while (buffer.hasRemaining()) { System.out.print (buffer.get()); } System.out.println (""); } private static boolean fillBuffer (CharBuffer buffer){ if (index >= strings.length) { return (false); } String string = strings [index ]; for (int i = 0; i < string.length(); i ) { buffer.put (string.charAt (i)); } return true; } private static int index = 0; private static String [] strings = { "A random string value", "The product of an infinite number of monkeys", "Hey hey we're the Monkees", "Opening act for the Monkees: Jimi Hendrix", "'Scuse me while I kiss this fly", "Help Me! Help Me!", }; } 2.1.7 壓縮 public abstract class ByteBuffer extends Buffer implements Comparable { // 這里僅列出部分API public abstract ByteBuffer compact(); } 有時(shí),您可能只想從緩沖區(qū)中釋放一部分?jǐn)?shù)據(jù),而不是全部,然后重新填充。為了實(shí)現(xiàn)這一點(diǎn),未讀的數(shù)據(jù)元素需要下移以使第一個(gè)元素索引為 0。盡管重復(fù)這樣做會(huì)效率低下,但這有時(shí)非常必要,而 API 對(duì)此為您提供了一個(gè)compact()函數(shù)。這一緩沖區(qū)工具在復(fù)制數(shù)據(jù)時(shí)要比您使用get()和put()函數(shù)高效得多。所以當(dāng)您需要時(shí),請(qǐng)使用compact()。圖 2.6顯示了一個(gè)我們已經(jīng)釋放了一些元素,并且現(xiàn)在我們想要對(duì)其進(jìn)行壓縮的緩沖區(qū)。 圖 2.6 被部分釋放的緩沖區(qū) 這樣操作: buffer.compact(); 會(huì)導(dǎo)致緩沖區(qū)的狀態(tài)如圖 2-7 所示: 圖 2.7 壓縮后的 buffer 這里發(fā)生了幾件事。您會(huì)看到數(shù)據(jù)元素 2-5 被復(fù)制到 0-3 位置。位置 4 和 5 不受影響,但現(xiàn)在正在或已經(jīng)超出了當(dāng)前位置,因此是「死的」。它們可以被之后的 put()調(diào)用重寫(xiě)。還要注意的是,位置已經(jīng)被設(shè)為被復(fù)制的數(shù)據(jù)元素的數(shù)目。也就是說(shuō),緩沖區(qū)現(xiàn)在被定位在緩沖區(qū)中最后一個(gè)「存活」元素后插入數(shù)據(jù)的位置。最后,上界屬性被設(shè)置為容量的值,因此緩沖區(qū)可以被再次填滿。調(diào)用compact()的作用是丟棄已經(jīng)釋放的數(shù)據(jù),保留未釋放的數(shù)據(jù),并使緩沖區(qū)對(duì)重新填充容量準(zhǔn)備就緒。您可以用這種類似于先入先出(FIFO)隊(duì)列的方式使用緩沖區(qū)。當(dāng)然也存在更高效的算法(緩沖區(qū)移位并不是一個(gè)處理隊(duì)列的非常高效的方法)。但是壓縮對(duì)于使緩沖區(qū)與您從端口中讀入的數(shù)據(jù)(包)邏輯塊流的同步來(lái)說(shuō)也許是一種便利的方法。 如果您想在壓縮后釋放數(shù)據(jù),緩沖區(qū)會(huì)像之前所討論的那樣需要被翻轉(zhuǎn)。無(wú)論您之后是否要向緩沖區(qū)中添加新的數(shù)據(jù),這一點(diǎn)都是必要的。 2.1.8 標(biāo)記 這本章節(jié)的開(kāi)頭,我們已經(jīng)涉及了緩沖區(qū)四種屬性中的三種。第四種,標(biāo)記,使緩沖區(qū)能夠記住一個(gè)位置并在之后將其返回。緩沖區(qū)的標(biāo)記在mark()函數(shù)被調(diào)用之前是未定義的,調(diào)用時(shí)標(biāo)記被設(shè)為當(dāng)前位置的值。reset()函數(shù)將位置設(shè)為當(dāng)前的標(biāo)記值。如果標(biāo)記值未定義,調(diào)用reset()將導(dǎo)致InvalidMarkException異常。一些緩沖區(qū)函數(shù)會(huì)拋棄已經(jīng)設(shè)定的標(biāo)記(rewind(),clear(),以及 flip()總是拋棄標(biāo)記)。如果新設(shè)定的值比當(dāng)前的標(biāo)記小,調(diào)用limit()或 position()帶有索引參數(shù)的版本會(huì)拋棄標(biāo)記。 注意不要混淆 reset()和 clear()。clear()函數(shù)將清空緩沖區(qū),而reset()位置返回到一個(gè)先前設(shè)定的標(biāo)記。 讓我們看看這是如何進(jìn)行的。在圖 2.5 的緩沖區(qū)上執(zhí)行以下代碼將會(huì)導(dǎo)致圖 2-8 所顯示的緩沖區(qū)狀態(tài)。 buffer.position(2).mark().position(4); 圖 2-8 設(shè)有一個(gè)標(biāo)記的緩沖區(qū) 如果這個(gè)緩沖區(qū)現(xiàn)在被傳遞給一個(gè)通道,兩個(gè)字節(jié)(「ow」)將會(huì)被發(fā)送,而位置會(huì)前進(jìn)到 6。如果我們此時(shí)調(diào)用reset(),位置將會(huì)被設(shè)為標(biāo)記,如圖 2-9 所示。再次將緩沖區(qū)傳遞給通道將導(dǎo)致四個(gè)字節(jié)(「llow」)被發(fā)送。 圖 2-9 一個(gè)緩沖區(qū)位置被重設(shè)為標(biāo)記 結(jié)果可能沒(méi)什么意義(owllow 會(huì)被寫(xiě)入通道),但您了解了概念。 2.1.9 比較 有時(shí)候比較兩個(gè)緩沖區(qū)所包含的數(shù)據(jù)是很有必要的。所有的緩沖區(qū)都提供了一個(gè)常規(guī)的equals()函數(shù)用以測(cè)試兩個(gè)緩沖區(qū)的是否相等,以及一個(gè)compareTo()函數(shù)用以比較緩沖區(qū)。 public abstract class ByteBuffer extends Buffer implements Comparable { // 這里僅列出部分API public boolean equals (Object ob); public int compareTo (Object ob); } 兩個(gè)緩沖區(qū)可用下面的代碼來(lái)測(cè)試是否相等: if (buffer1.equals (buffer2)) { doSomething(); } 如果每個(gè)緩沖區(qū)中剩余的內(nèi)容相同,那么 equals()函數(shù)將返回true,否則返回false,因?yàn)檫@個(gè)測(cè)試是用于嚴(yán)格的相等而且是可換向的。前面的程序清單中的緩沖區(qū)名稱可以顛倒,并會(huì)產(chǎn)生相同的結(jié)果。兩個(gè)緩沖區(qū)被認(rèn)為相等的充要條件是: 兩個(gè)對(duì)象類型相同。包含不同數(shù)據(jù)類型的buffer永遠(yuǎn)不會(huì)相等,而且buffer絕不會(huì)等于非buffer對(duì)象。 兩個(gè)對(duì)象都剩余同樣數(shù)量的元素。Buffer的容量不需要相同,而且緩沖區(qū)中剩余數(shù)據(jù)的索引也不必相同。但每個(gè)緩沖區(qū)中剩余元素的數(shù)目(從位置到上界)必須相同。 在每個(gè)緩沖區(qū)中應(yīng)被get()函數(shù)返回的剩余數(shù)據(jù)元素序列必須一致。 如果不滿足以上任意條件,就會(huì)返回 false。 圖 2-10 說(shuō)明了兩個(gè)屬性不同的緩沖區(qū)也可以相等。 圖 2-11 顯示了兩個(gè)相似的緩沖區(qū),可能看起來(lái)是完全相同的緩沖區(qū),但測(cè)試時(shí)會(huì)發(fā)現(xiàn)并不相等。 圖 2-10 兩個(gè)被認(rèn)為是相等的緩沖區(qū) 圖 2-11 兩個(gè)被認(rèn)為不相等的緩沖區(qū) 緩沖區(qū)也支持用compareTo()函數(shù)以詞典順序進(jìn)行比較。這一函數(shù)在緩沖區(qū)參數(shù)小于,等于,或者大于引用compareTo()的對(duì)象實(shí)例時(shí),分別返回一個(gè)負(fù)整數(shù),0 和正整數(shù)。這些就是所有典型的緩沖區(qū)所實(shí)現(xiàn)的java.lang.Comparable接口語(yǔ)義。這意味著緩沖區(qū)數(shù)組可以通過(guò)調(diào)用 java.util.Arrays.sort()函數(shù)按照它們的內(nèi)容進(jìn)行排序。 與equals()相似,compareTo()不允許不同對(duì)象間進(jìn)行比較。但compareTo()更為嚴(yán)格:如果您傳遞一個(gè)類型錯(cuò)誤的對(duì)象,它會(huì)拋出ClassCastException異常,但equals()只會(huì)返回false。 比較是針對(duì)每個(gè)緩沖區(qū)內(nèi)剩余數(shù)據(jù)進(jìn)行的,與它們?cè)趀quals()中的方式相同,直到不相等的元素被發(fā)現(xiàn)或者到達(dá)緩沖區(qū)的上界。如果一個(gè)緩沖區(qū)在不相等元素發(fā)現(xiàn)前已經(jīng)被耗盡,較短的緩沖區(qū)被認(rèn)為是小于較長(zhǎng)的緩沖區(qū)。不像equals(),compareTo()不可交換:順序問(wèn)題。在本例中,一個(gè)小于零的結(jié)果表明 buffer2小于buffer1,而表達(dá)式的值就會(huì)是true: if (buffer1.compareTo (buffer2) < 0) { doSomething(); } 如果前面的代碼被應(yīng)用到圖 2-10 所示的緩沖區(qū)中,結(jié)果會(huì)是0,而 if 語(yǔ)句將毫無(wú)用處。被應(yīng)用到圖 2-11 的緩沖區(qū)的相同測(cè)試將會(huì)返回一個(gè)正數(shù)(表明 buffer2大于buffer1),而這個(gè)表達(dá)式也會(huì)被判斷為 false。 2.1.10 批量移動(dòng) 緩沖區(qū)的涉及目的就是為了能夠高效傳輸數(shù)據(jù)。一次移動(dòng)一個(gè)數(shù)據(jù)元素,如例 2-1 所示的那樣并不高效。如您在下面的程序清單中所看到的那樣,buffer API 提供了向緩沖區(qū)內(nèi)外批量移動(dòng)數(shù)據(jù)元素的函數(shù)。 public abstract class CharBuffer extends Buffer implements CharSequence, Comparable { // This is a partial API listing public CharBuffer get (char [] dst) public CharBuffer get (char [] dst, int offset, int length) public final CharBuffer put (char[] src) public CharBuffer put (char [] src, int offset, int length) public CharBuffer put (CharBuffer src) public final CharBuffer put (String src) public CharBuffer put (String src, int start, int end) } 有兩種形式的 get()可供從緩沖區(qū)到數(shù)組進(jìn)行的數(shù)據(jù)復(fù)制使用。第一種形式只將一個(gè)數(shù)組作為參數(shù),將一個(gè)緩沖區(qū)釋放到給定的數(shù)組。第二種形式使用 offset 和 length 參數(shù)來(lái)指定目標(biāo)數(shù)組的子區(qū)間。這些批量移動(dòng)的合成效果與前文所討論的循環(huán)是相同的,但是這些方法可能高效得多,因?yàn)檫@種緩沖區(qū)實(shí)現(xiàn)能夠利用本地代碼或其他的優(yōu)化來(lái)移動(dòng)數(shù)據(jù)。批量移動(dòng)總是具有指定的長(zhǎng)度。也就是說(shuō),您總是要求移動(dòng)固定數(shù)量的數(shù)據(jù)元素。當(dāng)參看程序簽名時(shí)這一點(diǎn)還不明顯,但是對(duì)get()的這一引用: buffer.get(myArray); 等價(jià)于: buffer.get(myArray,0,myArray.length); 如果您所要求的數(shù)量的數(shù)據(jù)不能被傳送,那么不會(huì)有數(shù)據(jù)被傳遞,緩沖區(qū)的狀態(tài)保持不變,同時(shí)拋出BufferUnderflowException異常。因此當(dāng)您傳入一個(gè)數(shù)組并且沒(méi)有指定長(zhǎng) 度,您就相當(dāng)于要求整個(gè)數(shù)組被填充。如果緩沖區(qū)中的數(shù)據(jù)不夠完全填滿數(shù)組,您會(huì)得到一個(gè)異常。這意味著如果您想將一個(gè)小型緩沖區(qū)傳入一個(gè)大型數(shù)組,您需要明確地指定緩沖區(qū)中剩余的數(shù)據(jù)長(zhǎng)度。上面的第一個(gè)例子不會(huì)如您第一眼所推出的結(jié)論那樣,將緩沖區(qū)內(nèi)剩余的數(shù)據(jù)元素復(fù)制到數(shù)組的底部。要將一個(gè)緩沖區(qū)釋放到一個(gè)大數(shù)組中,要這樣做: char [] bigArray = new char [1000]; // Get count of chars remaining in the buffer int length = buffer.remaining(); // Buffer is known to contain < 1,000 chars buffer.get (bigArrray, 0, length); // Do something useful with the data processData (bigArray, length); 記住在調(diào)用get()之前必須查詢緩沖區(qū)中的元素?cái)?shù)量(因?yàn)槲覀冃枰嬷猵rocessData()被放置在bigArray中的字符個(gè)數(shù))。調(diào)用get()會(huì)向前移動(dòng)緩沖區(qū)的位置屬性,所以之后調(diào)用remaining()會(huì)返回 0。get()的批量版本返回緩沖區(qū)的引用,而不是被傳送的數(shù)據(jù)元素的計(jì)數(shù),以減輕級(jí)聯(lián)調(diào)用的困難。另一方面,如果緩沖區(qū)存有比數(shù)組能容納的數(shù)量更多的數(shù)據(jù),您可以重復(fù)利用如下文所示的程序塊進(jìn)行讀?。?div> char [] smallArray = new char [10]; while (buffer.hasRemaining()) { int length = Math.min (buffer.remaining(), smallArray.length); buffer.get (smallArray, 0, length); processData (smallArray, length); } put()的批量版本工作方式相似,但以相反的方向移動(dòng)數(shù)據(jù),從數(shù)組移動(dòng)到緩沖區(qū)。他們?cè)趥魉蛿?shù)據(jù)的大小方面有著相同的語(yǔ)義: buffer.put(myArray); 等價(jià)于: buffer.put(myArray,0,myArray.length); 批量傳輸?shù)拇笮】偸枪潭ǖ?。省略長(zhǎng)度意味著整個(gè)數(shù)組會(huì)被填滿。 如果緩沖區(qū)有足夠的空間接受數(shù)組中的數(shù)據(jù)(buffer.remaining()>myArray.length),數(shù)據(jù)將會(huì)被復(fù)制到從當(dāng)前位置開(kāi)始的緩沖區(qū),并且緩沖區(qū)位置會(huì)被提前所增加數(shù)據(jù)元素的數(shù)量。如果緩沖區(qū)中沒(méi)有足夠的空間,那么不會(huì)有數(shù)據(jù)被傳遞,同時(shí)拋出一個(gè)BufferOverflowException異常。 也可以通過(guò)調(diào)用帶有一個(gè)緩沖區(qū)引用作為參數(shù)的put()來(lái)在兩個(gè)緩沖區(qū)內(nèi)進(jìn)行批量傳遞。 buffer.put(srcBuffer); 這等價(jià)于(假設(shè) dstBuffer 有足夠的空間): while (srcBuffer.hasRemaining()) { dstBuffer.put (srcBuffer.get()); } 兩個(gè)緩沖區(qū)的位置都會(huì)前進(jìn)所傳遞的數(shù)據(jù)元素的數(shù)量。范圍檢查會(huì)像對(duì)數(shù)組一樣進(jìn)行。具體來(lái)說(shuō),如果srcBuffer.remaining()大于 dstBuffer.remaining(),那么數(shù)據(jù)不會(huì)被傳遞,同時(shí)拋出BufferOverflowException異常。如果您對(duì)將一個(gè)緩沖區(qū)傳遞給它自己,就會(huì)引發(fā) java.lang.IllegalArgumentException 異常。 在這一章節(jié)中我一直使用 CharBuffer 為例,而且到目前為止,這一討論也已經(jīng)應(yīng)用到了其他的典型緩沖區(qū)上,比如 FloatBuffer,LongBuffer,等等。但是在下面的 API 程序清單的最后兩個(gè)函數(shù)中包含了兩個(gè)只對(duì)CharBuffer適用的批量移動(dòng)函數(shù)。 public abstract class CharBuffer extends Buffer implements CharSequence, Comparable { // 這里僅列出部分API public final CharBuffer put (String src) public CharBuffer put (String src, int start, int end) } 這些函數(shù)使用 String 作為參數(shù),而且與作用于 char 數(shù)組的批量移動(dòng)函數(shù)相似。如所有的 java 程序員所知,String 不同于 char 數(shù)組。但 String 確實(shí)包含 char 字符串,而且我們?nèi)祟惔_實(shí)傾向于將其在概念上認(rèn)為是 char 數(shù)組(尤其是我們中曾經(jīng)是或者現(xiàn)在還是 C 或C 程序員的那些人)。由于這些原因,CharBuffer 類提供了將 String 復(fù)制到CharBuffer 中的便利方法。 String 移動(dòng)與 char 數(shù)組移動(dòng)相似,除了在序列上是由 start 和 end 1 下標(biāo)確定(與String.subString()類似),而不是 start 下標(biāo)和 length。所以: buffer.put(myString); 等價(jià)于: buffer.put(myString,0,myString.length); 而這就是您怎樣復(fù)制字符 5-8,總共四個(gè)字符,從 myString 復(fù)制到 buffer。 buffer.put(myString,5,9); String 批量移動(dòng)等效于下面的代碼: for (int i = start; i < end; i ) { buffer.put (myString.charAt (i)); } 對(duì)String要進(jìn)行與char數(shù)組相同的范圍檢查。如果所有的字符都不適合緩沖區(qū),將會(huì)拋出BufferOverflowException異常。 Java nio入門教程詳解(五) |
|
|