3.4 內(nèi)存映射文件 新的FileChannel類提供了一個(gè)名為map()的方法,該方法可以在一個(gè)打開的文件和一個(gè)特殊類型的ByteBuffer之間建立一個(gè)虛擬內(nèi)存映射(第一章中已經(jīng)歸納了什么是內(nèi)存映射文件以及它們?nèi)绾瓮摂M內(nèi)存交互)。在FileChannel上調(diào)用map()方法會(huì)創(chuàng)建一個(gè)由磁盤文件支持的虛擬內(nèi)存映射(virtual memory mapping)并在那塊虛擬內(nèi)存空間外部封裝一個(gè)MappedByteBuffer對(duì)象(參見圖 1-6)。 由map()方法返回的MappedByteBuffer對(duì)象的行為在多數(shù)方面類似一個(gè)基于內(nèi)存的緩沖區(qū),只不過該對(duì)象的數(shù)據(jù)元素存儲(chǔ)在磁盤上的一個(gè)文件中。調(diào)用get()方法會(huì)從磁盤文件中獲取數(shù)據(jù),此數(shù)據(jù)反映該文件的當(dāng)前內(nèi)容,即使在映射建立之后文件已經(jīng)被一個(gè)外部進(jìn)程做了修改。通過文件映射看到的數(shù)據(jù)同您用常規(guī)方法讀取文件看到的內(nèi)容是完全一樣的。相似地,對(duì)映射的緩沖區(qū)實(shí)現(xiàn)一個(gè)put()會(huì)更新磁盤上的那個(gè)文件(假設(shè)對(duì)該文件您有寫的權(quán)限),并且您做的修改對(duì)于該文件的其他閱讀者也是可見的。 通過內(nèi)存映射機(jī)制來訪問一個(gè)文件會(huì)比使用常規(guī)方法讀寫高效得多,甚至比使用通道的效率都高。因?yàn)椴恍枰雒鞔_的系統(tǒng)調(diào)用,那會(huì)很消耗時(shí)間。更重要的是,操作系統(tǒng)的虛擬內(nèi)存可以自動(dòng)緩存內(nèi)存頁(memory page)。這些頁是用系統(tǒng)內(nèi)存來緩存的,所以不會(huì)消耗Java虛擬機(jī)內(nèi)存堆(memory heap)。 一旦一個(gè)內(nèi)存頁已經(jīng)生效(從磁盤上緩存進(jìn)來),它就能以完全的硬件速度再次被訪問而不需要再次調(diào)用系統(tǒng)命令來獲取數(shù)據(jù)。那些包含索引以及其他需頻繁引用或更新的內(nèi)容的巨大而結(jié)構(gòu)化文件能因內(nèi)存映射機(jī)制受益非常多。如果同時(shí)結(jié)合文件鎖定來保護(hù)關(guān)鍵區(qū)域和控制事務(wù)原子性,那您將能了解到內(nèi)存映射緩沖區(qū)如何可以被很好地利用。 下面讓我們來看一下如何使用內(nèi)存映射: public abstract class FileChannel extends AbstractChannel implements ByteChannel, GatheringByteChannel, ScatteringByteChannel { // 這里僅列出部分API public abstract MappedByteBuffer map(MapMode mode, long position, long size) public static class MapMode { public static final MapMode READ_ONLY public static final MapMode READ_WRITE public static final MapMode PRIVATE } } 可以看到,只有一種map()方法來創(chuàng)建一個(gè)文件映射。它的參數(shù)有mode,position和size。參數(shù)position和size同lock()方法的這兩個(gè)參數(shù)是一樣的(在前面的章節(jié)中已有討論)。我們可以創(chuàng)建一個(gè)MappedByteBuffer來代表一個(gè)文件中字節(jié)的某個(gè)子范圍。例如,要映射100到299(包含299)位置的字節(jié),可以使用下面的代碼: buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 100, 200); 如果要映射整個(gè)文件則使用: buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size()); 與文件鎖的范圍機(jī)制不一樣,映射文件的范圍不應(yīng)超過文件的實(shí)際大小。如果您請(qǐng)求一個(gè)超出文件大小的映射,文件會(huì)被增大以匹配映射的大小。假如您給size參數(shù)傳遞的值是Integer.MAX_VALUE,文件大小的值會(huì)膨脹到超過2.1GB。即使您請(qǐng)求的是一個(gè)只讀映射,map()方法也會(huì)嘗試這樣做并且大多數(shù)情況下都會(huì)拋出一個(gè)IOException異常,因?yàn)榈讓拥奈募荒鼙恍薷?。該行為同之前討論的文件「空洞」的行為是一致的。詳情?qǐng)參考 3.3.1 節(jié)。 FileChannel類定義了代表映射模式的常量,且是使用一個(gè)類型安全的枚舉而非數(shù)字值來定義這些常量。這些常量是FileChannel內(nèi)部定義的一個(gè)內(nèi)部類(inner class)的靜態(tài)字段,它們可以在編譯時(shí)被檢查類型,不過您可以像使用一個(gè)數(shù)值型常量那樣使用它們。 同常規(guī)的文件句柄類似,文件映射可以是可寫的或只讀的。前兩種映射模式MapMode.READ_ONLY和MapMode.READ_WRITE意義是很明顯的,它們表示您希望獲取的映射只讀還是允許修改映射的文件。請(qǐng)求的映射模式將受被調(diào)用map()方法的FileChannel對(duì)象的訪問權(quán)限所限制。如果通道是以只讀的權(quán)限打開的而您卻請(qǐng)求MapMode.READ_WRITE模式,那么map()方法會(huì)拋出一個(gè)NonWritableChannelException異常;如果您在一個(gè)沒有讀權(quán)限的通道上請(qǐng)求MapMode.READ_ONLY映射模式,那么將產(chǎn)生NonReadableChannelException異常。不過在以read/write權(quán)限打開的通道上請(qǐng)求一個(gè)MapMode.READ_ONLY映射卻是允許的。MappedByteBuffer對(duì)象的可變性可以通過對(duì)它調(diào)用isReadOnly()方法來檢查。 第三種模式MapMode.PRIVATE表示您想要一個(gè)寫時(shí)拷貝(copy-on-write)的映射。這意味著您通過put()方法所做的任何修改都會(huì)導(dǎo)致產(chǎn)生一個(gè)私有的數(shù)據(jù)拷貝并且該拷貝中的數(shù)據(jù)只有MappedByteBuffer實(shí)例可以看到。該過程不會(huì)對(duì)底層文件做任何修改,而且一旦緩沖區(qū)被施以垃圾收集動(dòng)作(garbage collected),那些修改都會(huì)丟失。盡管寫時(shí)拷貝的映射可以防止底層文件被修改,您也必須以read/write權(quán)限來打開文件以建立MapMode.PRIVATE映射。只有這樣,返回的MappedByteBuffer對(duì)象才能允許使用put()方法。 寫時(shí)拷貝這一技術(shù)經(jīng)常被操作系統(tǒng)使用,以在一個(gè)進(jìn)程生成另一個(gè)進(jìn)程時(shí)管理虛擬地址空間(virtual address spaces)。使用寫時(shí)拷貝可以允許父進(jìn)程和子進(jìn)程共享內(nèi)存頁直到它們中的一方實(shí)際發(fā)生修改行為。在處理同一文件的多個(gè)映射時(shí)也有相同的優(yōu)勢(shì)(當(dāng)然,這需要底層操作系統(tǒng)的支持)。假設(shè)一個(gè)文件被多個(gè)MappedByteBuffer對(duì)象映射并且每個(gè)映射都是MapMode.PRIVATE模式,那么這份文件的大部分內(nèi)容都可以被所有映射共享。 選擇使用MapMode.PRIVATE模式并不會(huì)導(dǎo)致您的緩沖區(qū)看不到通過其他方式對(duì)文件所做的修改。對(duì)文件某個(gè)區(qū)域的修改在使用MapMode.PRIVATE模式的緩沖區(qū)中都能反映出來,除非該緩沖區(qū)已經(jīng)修改了文件上的同一個(gè)區(qū)域。正如第一章中所描述的,內(nèi)存和文件系統(tǒng)都被劃分成了頁。當(dāng)在一個(gè)寫時(shí)拷貝的緩沖區(qū)上調(diào)用put()方法時(shí),受影響的頁會(huì)被拷貝,然后更改就會(huì)應(yīng)用到該拷貝中。具體的頁面大小取決于具體實(shí)現(xiàn),不過通常都是和底層文件系統(tǒng)的頁面大小時(shí)一樣的。如果緩沖區(qū)還沒對(duì)某個(gè)頁做出修改,那么這個(gè)頁就會(huì)反映被映射文件的相應(yīng)位置上的內(nèi)容。一旦某個(gè)頁因?yàn)閷懖僮鞫豢截?,之后就將使用該拷貝頁,并且不能被其他緩沖區(qū)或文件更新所修改。例3-5 的代碼詮釋了這一行為。 您應(yīng)該注意到了沒有unmap()方法。也就是說,一個(gè)映射一旦建立之后將保持有效,直到MappedByteBuffer對(duì)象被施以垃圾收集動(dòng)作為止。同鎖不一樣的是,映射緩沖區(qū)沒有綁定到創(chuàng)建它們的通道上。關(guān)閉相關(guān)聯(lián)的FileChannel不會(huì)破壞映射,只有丟棄緩沖區(qū)對(duì)象本身才會(huì)破壞該映射。NIO設(shè)計(jì)師們之所以做這樣的決定是因?yàn)楫?dāng)關(guān)閉通道時(shí)破壞映射會(huì)引起安全問題,而解決該安全問題又會(huì)導(dǎo)致性能問題。如果您確實(shí)需要知道一個(gè)映射是什么時(shí)候被破壞的,他們建議使用虛引用(phantom references,參見java.lang.ref.PhantomReference)和一個(gè)cleanup線程。不過有此需要的概率是微乎其微的。 MemoryMappedBuffer直接反映它所關(guān)聯(lián)的磁盤文件。如果映射有效時(shí)文件被在結(jié)構(gòu)上修改,就會(huì)產(chǎn)生奇怪的行為(當(dāng)然具體的行為是取決于操作系統(tǒng)和文件系統(tǒng)的)。MemoryMappedBuffer有固定的大小,不過它所映射的文件卻是彈性的。具體來說,如果映射有效時(shí)文件大小變化了,那么緩沖區(qū)的部分或全部?jī)?nèi)容都可能無法訪問,并將返回未定義的數(shù)據(jù)或者拋出未檢查的異常。關(guān)于被內(nèi)存映射的文件如何受其他線程或外部進(jìn)程控制這一點(diǎn),請(qǐng)務(wù)必小心對(duì)待。 所有的MappedByteBuffer對(duì)象都是直接的,這意味著它們占用的內(nèi)存空間位于Java虛擬機(jī)內(nèi)存堆之外(并且可能不會(huì)算作 Java 虛擬機(jī)的內(nèi)存占用,不過這取決于操作系統(tǒng)的虛擬內(nèi)存模型)。 因?yàn)镸appedByteBuffers也是ByteBuffers,所以能夠被傳遞SocketChannel之類通道的read()或write()以有效傳輸數(shù)據(jù)給被映射的文件或從被映射的文件讀取數(shù)據(jù)。如能再結(jié)合scatter/gather,那么從內(nèi)存緩沖區(qū)和被映射文件內(nèi)容中組織數(shù)據(jù)就變得很容易了。例 3-4 就是以此方式寫 HTTP 回應(yīng)的。3.4.1節(jié)中將描述一個(gè)傳輸數(shù)據(jù)給通道或從其他通道讀取數(shù)據(jù)的更加有效的方式。 到現(xiàn)在為止,我們已經(jīng)討論完了映射緩沖區(qū)同其他緩沖區(qū)相同的特性,這些也是您會(huì)用得最多的。不過MappedByteBuffer還定義了幾個(gè)它獨(dú)有的方法: public abstract class MappedByteBuffer extends ByteBuffer { // 這里僅列出部分API public final MappedByteBuffer load() public final boolean isLoaded() public final MappedByteBuffer force() } 當(dāng)我們?yōu)橐粋€(gè)文件建立虛擬內(nèi)存映射之后,文件數(shù)據(jù)通常不會(huì)因此被從磁盤讀取到內(nèi)存(這取決于操作系統(tǒng))。該過程類似打開一個(gè)文件:文件先被定位,然后一個(gè)文件句柄會(huì)被創(chuàng)建,當(dāng)您準(zhǔn)備好之后就可以通過這個(gè)句柄來訪問文件數(shù)據(jù)。對(duì)于映射緩沖區(qū),虛擬內(nèi)存系統(tǒng)將根據(jù)您的需要來把文件中相應(yīng)區(qū)塊的數(shù)據(jù)讀進(jìn)來。這個(gè)頁驗(yàn)證或防錯(cuò)過程需要一定的時(shí)間,因?yàn)閷⑽募?shù)據(jù)讀取到內(nèi)存需要一次或多次的磁盤訪問。某些場(chǎng)景下,您可能想先把所有的頁都讀進(jìn)內(nèi)存以實(shí)現(xiàn)最小的緩沖區(qū)訪問延遲。如果文件的所有頁都是常駐內(nèi)存的,那么它的訪問速度就和訪問一個(gè)基于內(nèi)存的緩沖區(qū)一樣了。 load()方法會(huì)加載整個(gè)文件以使它常駐內(nèi)存。正如我們?cè)诘谝徽滤懻摰?,一個(gè)內(nèi)存映射緩沖區(qū)會(huì)建立與某個(gè)文件的虛擬內(nèi)存映射。此映射使得操作系統(tǒng)的底層虛擬內(nèi)存子系統(tǒng)可以根據(jù)需要將文件中相應(yīng)區(qū)塊的數(shù)據(jù)讀進(jìn)內(nèi)存。已經(jīng)在內(nèi)存中或通過驗(yàn)證的頁會(huì)占用實(shí)際內(nèi)存空間,并且在它們被讀進(jìn)RAM時(shí)會(huì)擠出最近較少使用的其他內(nèi)存頁。 在一個(gè)映射緩沖區(qū)上調(diào)用load()方法會(huì)是一個(gè)代價(jià)高的操作,因?yàn)樗鼤?huì)導(dǎo)致大量的頁調(diào)入(page-in),具體數(shù)量取決于文件中被映射區(qū)域的實(shí)際大小。然而,load()方法返回并不能保證文件就會(huì)完全常駐內(nèi)存,這是由于請(qǐng)求頁面調(diào)入(demand paging)是動(dòng)態(tài)的。具體結(jié)果會(huì)因某些因素而有所差異,這些因素包括:操作系統(tǒng)、文件系統(tǒng),可用Java虛擬機(jī)內(nèi)存,最大Java虛擬機(jī)內(nèi)存,垃圾收集器實(shí)現(xiàn)過程等等。請(qǐng)小心使用load()方法,它可能會(huì)導(dǎo)致您不希望出現(xiàn)的結(jié)果。該方法的主要作用是為提前加載文件埋單,以便后續(xù)的訪問速度可以盡可能的快。 對(duì)于那些要求近乎實(shí)時(shí)訪問(near-realtime access)的程序,解決方案就是預(yù)加載。但是請(qǐng)記住,不能保證全部頁都會(huì)常駐內(nèi)存,不管怎樣,之后可能還會(huì)有頁調(diào)入發(fā)生。內(nèi)存頁什么時(shí)候以及怎樣消失受多個(gè)因素影響,這些因素中的許多都是不受 Java 虛擬機(jī)控制的。JDK 1.4的NIO并沒有提供一個(gè)可以把頁面固定到物理內(nèi)存上的 API,盡管一些操作系統(tǒng)是支持這樣做的。 對(duì)于大多數(shù)程序,特別是交互性的或其他事件驅(qū)動(dòng)(event-driven)的程序而言,為提前加載文件消耗資源是不劃算的。在實(shí)際訪問時(shí)分?jǐn)傢撜{(diào)入開銷才是更好的選擇。讓操作系統(tǒng)根據(jù)需要來調(diào)入頁意味著不訪問的頁永遠(yuǎn)不需要被加載。同預(yù)加載整個(gè)被映射的文件相比,這很容易減少 I/O 活動(dòng)總次數(shù)。操作系統(tǒng)已經(jīng)有一個(gè)復(fù)雜的內(nèi)存管理系統(tǒng)了,就讓它來替您完成此工作吧! 我們可以通過調(diào)用isLoaded()方法來判斷一個(gè)被映射的文件是否完全常駐內(nèi)存了。如果該方法返回true值,那么很大概率是映射緩沖區(qū)的訪問延遲很少或者根本沒有延遲。不過,這也是不能保證的。同樣地,返回false值并不一定意味著訪問緩沖區(qū)將很慢或者該文件并未完全常駐內(nèi)存。isLoaded()方法的返回值只是一個(gè)暗示,由于垃圾收集的異步性質(zhì)、底層操作系統(tǒng)以及運(yùn)行系統(tǒng)的動(dòng)態(tài)性等因素,想要在任意時(shí)刻準(zhǔn)確判斷全部映射頁的狀態(tài)是不可能的。 上面代碼中列出的最后一個(gè)方法force()同F(xiàn)ileChannel類中的同名方法相似(參見 3.3.1 節(jié))該方法會(huì)強(qiáng)制將映射緩沖區(qū)上的更改應(yīng)用到永久磁盤存儲(chǔ)器上。當(dāng)用MappedByteBuffer對(duì)象來更新一個(gè)文件,您應(yīng)該總是使用MappedByteBuffer.force()而非FileChannel.force(),因?yàn)橥ǖ缹?duì)象可能不清楚通過映射緩沖區(qū)做出的文件的全部更改。MappedByteBuffer沒有不更新文件元數(shù)據(jù)的選項(xiàng)——元數(shù)據(jù)總是會(huì)同時(shí)被更新的。請(qǐng)注意,非本地文件系統(tǒng)也同樣影響MappedByteBuffer.force()方法,正如它會(huì)對(duì)FileChannel.force()方法有影響,在這里(參見 3.3.1 節(jié))。 如果映射是以MapMode.READ_ONLY或MAP_MODE.PRIVATE模式建立的,那么調(diào)用force()方法將不起任何作用,因?yàn)橛肋h(yuǎn)不會(huì)有更改需要應(yīng)用到磁盤上(但是這樣做也是沒有害處的)。例 3-4 詮釋了內(nèi)存映射緩沖區(qū)如何同scatter/gather結(jié)合使用。 /* *例 3-4 使用映射文件和 gathering 寫操作來編寫 HTTP 回復(fù) */ package com.ronsoft.books.nio.channels; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; import java.nio.channels.GatheringByteChannel; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.URLConnection; /** * Dummy HTTP server using MappedByteBuffers. * Given a filename on the command line, pretend to be * a web server and generate an HTTP response containing * the file content preceded by appropriate headers. The * data is sent with a gathering write. * * @author Ron Hitchens (ron@ronsoft.com) */ public class MappedHttp { private static final String OUTPUT_FILE = "MappedHttp.out"; private static final String LINE_SEP = "\r\n"; private static final String SERVER_ID = "Server: Ronsoft Dummy Server"; private static final String HTTP_HDR = "HTTP/1.0 200 OK" LINE_SEP SERVER_ID LINE_SEP; private static final String HTTP_404_HDR = "HTTP/1.0 404 Not Found" LINE_SEP SERVER_ID LINE_SEP; private static final String MSG_404 = "Could not open file: "; public static void main (String [] argv) throws Exception { if (argv.length < 1) { System.err.println ("Usage: filename"); return; } String file = argv [0]; ByteBuffer header = ByteBuffer.wrap(bytes (HTTP_HDR)); ByteBuffer dynhdrs = ByteBuffer.allocate(128); ByteBuffer [] gather = { header, dynhdrs, null }; String contentType = "unknown/unknown"; long contentLength = -1; try { FileInputStream fis = new FileInputStream (file); FileChannel fc = fis.getChannel(); MappedByteBuffer filedata = fc.map (MapMode.READ_ONLY, 0, fc.size()); gather [2] = filedata; contentLength = fc.size(); contentType = URLConnection.guessContentTypeFromName (file); } catch (IOException e) { // file could not be opened; report problem ByteBuffer buf = ByteBuffer.allocate (128); String msg = MSG_404 e LINE_SEP; buf.put (bytes (msg)); buf.flip(); // Use the HTTP error response gather [0] = ByteBuffer.wrap (bytes (HTTP_404_HDR)); gather [2] = buf; contentLength = msg.length(); contentType = "text/plain"; } StringBuffer sb = new StringBuffer(); sb.append ("Content-Length: " contentLength); sb.append (LINE_SEP); sb.append ("Content-Type: ").append (contentType); sb.append (LINE_SEP).append (LINE_SEP); dynhdrs.put (bytes (sb.toString())); dynhdrs.flip(); FileOutputStream fos = new FileOutputStream (OUTPUT_FILE); FileChannel out = fos.getChannel(); // All the buffers have been prepared; write 'em out while (out.write (gather) > 0) { // Empty body; loop until all buffers are empty } out.close(); System.out.println ("output written to " OUTPUT_FILE); } // Convert a string to its constituent bytes // from the ASCII character set private static byte[] bytes(String string) throws Exception { return (string.getBytes ("US-ASCII")); } } 例 3-5 詮釋了各種模式的內(nèi)存映射如何交互。具體來說,例中代碼詮釋了寫時(shí)拷貝是如何頁導(dǎo)向(page-oriented)的。當(dāng)在使用MAP_MODE.PRIVATE模式創(chuàng)建的MappedByteBuffer對(duì)象上調(diào)用put()方法而引發(fā)更改時(shí),就會(huì)生成一個(gè)受影響頁的拷貝。這份私有的拷貝不僅反映本地更改,而且使緩沖區(qū)免受來自外部對(duì)原來頁更改的影響。然而,對(duì)于被映射文件其他區(qū)域的更改還是可以看到的。 /* *例 3-5 三種類型的內(nèi)存映射緩沖區(qū) */ package com.ronsoft.books.nio.channels; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.io.File; import java.io.RandomAccessFile; /** * Test behavior of Memory mapped buffer types. Create a file, write * some data to it, then create three different types of mappings * to it. Observe the effects of changes through the buffer APIs * and updating the file directly. The data spans page boundaries * to illustrate the page-oriented nature of Copy-On-Write mappings. * * @author Ron Hitchens (ron@ronsoft.com) */ public class MapFile { public static void main (String [] argv) throws Exception { // Create a temp file and get a channel connected to it File tempFile = File.createTempFile ("mmaptest", null); RandomAccessFile file = new RandomAccessFile (tempFile, "rw"); FileChannel channel = file.getChannel(); ByteBuffer temp = ByteBuffer.allocate (100); // Put something in the file, starting at location 0 temp.put ("This is the file content".getBytes()); temp.flip(); channel.write (temp, 0); // Put something else in the file, starting at location 8192. // 8192 is 8 KB, almost certainly a different memory/FS page. // This may cause a file hole, depending on the // filesystem page size. temp.clear(); temp.put ("This is more file content".getBytes()); temp.flip(); channel.write (temp, 8192); // Create three types of mappings to the same file MappedByteBuffer ro = channel.map ( FileChannel.MapMode.READ_ONLY, 0, channel.size()); MappedByteBuffer rw = channel.map ( FileChannel.MapMode.READ_WRITE, 0, channel.size()); MappedByteBuffer cow = channel.map ( FileChannel.MapMode.PRIVATE, 0, channel.size()); // the buffer states before any modifications System.out.println ("Begin"); showBuffers (ro, rw, cow); // Modify the copy-on-write buffer cow.position (8); cow.put ("COW".getBytes()); System.out.println ("Change to COW buffer"); showBuffers (ro, rw, cow); // Modify the read/write buffer rw.position (9); rw.put (" R/W ".getBytes()); rw.position (8194); rw.put (" R/W ".getBytes()); rw.force(); System.out.println ("Change to R/W buffer"); showBuffers (ro, rw, cow); // Write to the file through the channel; hit both pages temp.clear(); temp.put ("Channel write ".getBytes()); temp.flip(); channel.write (temp, 0); temp.rewind(); channel.write (temp, 8202); System.out.println ("Write on channel"); showBuffers (ro, rw, cow); // Modify the copy-on-write buffer again cow.position (8207); cow.put (" COW2 ".getBytes()); System.out.println ("Second change to COW buffer"); showBuffers (ro, rw, cow); // Modify the read/write buffer rw.position (0); rw.put (" R/W2 ".getBytes()); rw.position (8210); rw.put (" R/W2 ".getBytes()); rw.force(); System.out.println ("Second change to R/W buffer"); showBuffers (ro, rw, cow); // cleanup channel.close(); file.close(); tempFile.delete(); } // Show the current content of the three buffers public static void showBuffers(ByteBuffer ro, ByteBuffer rw, ByteBuffer cow) throws Exception { dumpBuffer ("R/O", ro); dumpBuffer ("R/W", rw); dumpBuffer ("COW", cow); System.out.println (""); } // Dump buffer content, counting and skipping nulls public static void dumpBuffer (String prefix, ByteBuffer buffer) throws Exception { System.out.print (prefix ": '"); int nulls = 0; int limit = buffer.limit(); for (int i = 0; i < limit; i ) { char c = (char) buffer.get (i); if (c == '\u0000') { nulls ; continue; } if (nulls != 0) { System.out.print ("|[" nulls " nulls]|"); nulls = 0; } System.out.print(c); } System.out.println ("'"); } } 以下是運(yùn)行上面程序的輸出: BeginR/O: 'This is the file content|[8168 nulls]|This is more file content'R/W: 'This is the file content|[8168 nulls]|This is more file content'COW: 'This is the file content|[8168 nulls]|This is more file content'Change to COW bufferR/O: 'This is the file content|[8168 nulls]|This is more file content'R/W: 'This is the file content|[8168 nulls]|This is more file content'COW: 'This is COW file content|[8168 nulls]|This is more file content'Change to R/W bufferR/O: 'This is t R/W le content|[8168 nulls]|Th R/W more file content'R/W: 'This is t R/W le content|[8168 nulls]|Th R/W more file content'COW: 'This is COW file content|[8168 nulls]|Th R/W more file content'Write on channelR/O: 'Channel write le content|[8168 nulls]|Th R/W moChannel write t'R/W: 'Channel write le content|[8168 nulls]|Th R/W moChannel write t'COW: 'This is COW file content|[8168 nulls]|Th R/W moChannel write t'Second change to COW bufferR/O: 'Channel write le content|[8168 nulls]|Th R/W moChannel write t'R/W: 'Channel write le content|[8168 nulls]|Th R/W moChannel write t'COW: 'This is COW file content|[8168 nulls]|Th R/W moChann COW2 te t'Second change to R/W bufferR/O: ' R/W2 l write le content|[8168 nulls]|Th R/W moChannel R/W2 t'R/W: ' R/W2 l write le content|[8168 nulls]|Th R/W moChannel R/W2 t'COW: 'This is COW file content|[8168 nulls]|Th R/W moChann COW2 te t' Java nio入門教程詳解(二十二) 00 我們認(rèn)為:用戶的主要目的,是為了獲取有用的信息,而不是來點(diǎn)擊廣告的。因此本站將竭力做好內(nèi)容,并將廣告和內(nèi)容進(jìn)行分離,確保所有廣告不會(huì)影響到用戶的正常閱讀體驗(yàn)。用戶僅憑個(gè)人意愿和興趣愛好點(diǎn)擊廣告。 我們堅(jiān)信:只有給用戶帶來價(jià)值,用戶才會(huì)給我們以回報(bào)。 |
|