天天看點

NIO入門系列之第5章:關于緩沖區的更多内容第5章 關于緩沖區的更多内容

到目前為止,您已經學習了使用緩沖區進行日常工作所需要掌握的大部分内容。我們的例子沒怎麼超出标準的讀/寫過程種類,在原來的 I/O中可以像在 NIO 中一樣容易地實作這樣的标準讀寫過程。

本節将讨論使用緩沖區的一些更複雜的方面,比如緩沖區配置設定、包裝和分片。我們還會讨論 NIO 帶給 Java 平台的一些新功能。您将學到如何建立不同類型的緩沖區以達到不同的目的,如可保護資料不被修改的隻讀緩沖區,和直接映射到底層作業系統緩沖區的直接緩沖區。我們将在本節的最後介紹如何在 NIO 中建立記憶體映射檔案。

在能夠讀和寫之前,必須有一個緩沖區。要建立緩沖區,您必須配置設定它。我們使用靜态方法 allocate() 來配置設定緩沖區:

allocate() 方法配置設定一個具有指定大小的底層數組,并将它包裝到一個緩沖區對象中—在本例中是一個 ByteBuffer。

您還可以将一個現有的數組轉換為緩沖區,如下所示:

本例使用了 wrap() 方法将一個數組包裝為緩沖區。必須非常小心地進行這類操作。一旦完成包裝,底層資料就可以通過緩沖區或者直接通路。

slice() 方法根據現有的緩沖區建立一種子緩沖區。也就是說,它建立一個新的緩沖區,新緩沖區與原來的緩沖區的一部分共享資料。

使用例子可以最好地說明這點。讓我們首先建立一個長度為 10 的 ByteBuffer:

然後使用資料來填充這個緩沖區,在第 n 個槽中放入數字 n:

現在我們對這個緩沖區分片,以建立一個包含槽 3 到槽 6 的子緩沖區。在某種意義上,子緩沖區就像原來的緩沖區中的一個視窗。

視窗的起始和結束位置通過設定position 和 limit 值來指定,然後調用 Buffer 的 slice() 方法:

片是緩沖區的子緩沖區。不過,片段和緩沖區共享同一個底層資料數組,我們在下一節将會看到這一點。

我們已經建立了原緩沖區的子緩沖區,并且我們知道緩沖區和子緩沖區共享同一個底層資料數組。讓我們看看這意味着什麼。

我們周遊子緩沖區,将每一個元素乘以 11 來改變它。例如,5會變成 55。

最後,再看一下原緩沖區中的内容:

結果表明隻有在子緩沖區視窗中的元素被改變了:

緩沖區片對于促進抽象非常有幫助。可以編寫自己的函數處理整個緩沖區,而且如果想要将這個過程應用于子緩沖區上,您隻需取主緩沖區的一個片,并将它傳遞給您的函數。這比編寫自己的函數來取額外的參數以指定要對緩沖區的哪一部分進行操作更容易。

隻讀緩沖區非常簡單—您可以讀取它們,但是不能向它們寫入。可以通過調用緩沖區的 asReadOnlyBuffer() 方法,将任何正常緩沖區轉換為隻讀緩沖區,這個方法傳回一個與原緩沖區完全相同的緩沖區(并與其共享資料),隻不過它是隻讀的。

隻讀緩沖區對于保護資料很有用。在将緩沖區傳遞給某個對象的方法時,您無法知道這個方法是否會修改緩沖區中的資料。建立一個隻讀的緩沖區可以保證該緩沖區不會被修改。

不能将隻讀的緩沖區轉換為可寫的緩沖區。

另一種有用的ByteBuffer 是直接緩沖區。直接緩沖區是為加快 I/O 速度,而以一種特殊的方式配置設定其記憶體的緩沖區。

實際上,直接緩沖區的準确定義是與實作相關的。Sun 的文檔是這樣描述直接緩沖區的:

給定一個直接位元組緩沖區,Java 虛拟機将盡最大努力直接對它執行本機 I/O 操作。也就是說,它會在每一次調用底層作業系統的本機 I/O 操作之前(或之後),嘗試避免将緩沖區的内容拷貝到一個中間緩沖區中(或者從一個中間緩沖區中拷貝資料)。

您可以在例子程式FastCopyFile.java 中看到直接緩沖區的實際應用,這個程式是 CopyFile.java 的另一個版本,它使用了直接緩沖區以提高速度。

還可以用記憶體映射檔案建立直接緩沖區。

記憶體映射檔案 I/O 是一種讀和寫檔案資料的方法,它可以比正常的基于流或者基于通道的 I/O 快得多。

記憶體映射檔案 I/O 是通過使檔案中的資料神奇般地出現為記憶體數組的内容來完成的。這其初聽起來似乎不過就是将整個檔案讀到記憶體中,但是事實上并不是這樣。一般來說,隻有檔案中實際讀取或者寫入的部分才會送入(或者映射)到記憶體中。

記憶體映射并不真的神奇或者多麼不尋常。現代作業系統一般根據需要将檔案的部分映射為記憶體的部分,進而實作檔案系統。Java 記憶體映射機制不過是在底層作業系統中可以采用這種機制時,提供了對該機制的通路。

盡管建立記憶體映射檔案相當簡單,但是向它寫入可能是危險的。僅隻是改變數組的單個元素這樣的簡單操作,就可能會直接修改磁盤上的檔案。修改資料與将資料儲存到磁盤是沒有分開的。

了解記憶體映射的最好方法是使用例子。在下面的例子中,我們要将一個 FileChannel (它的全部或者部分)映射到記憶體中。為此我們将使用FileChannel.map() 方法。下面代碼行将檔案的前 1024 個位元組映射到記憶體中:

map() 方法傳回一個 MappedByteBuffer,它是 ByteBuffer 的子類。是以,您可以像使用其他任何 ByteBuffer 一樣使用新映射的緩沖區,作業系統會在需要時負責執行行映射。

本文出自 “” 部落格,請務必保留此出處