Buffer
java NIO庫是在jdk1.4中引入的褐桌,NIO與IO之間的第一個區(qū)別在于勘究,IO是面向流的抒倚,而NIO是面向塊的躬充。
所謂的面向流是指:系統(tǒng)一次一個字節(jié)的處理數(shù)據(jù),一個輸入流產(chǎn)生一個字節(jié)的數(shù)據(jù)鸿染,一個輸出流消費一個字節(jié)的數(shù)據(jù)指蚜。
所謂的面向塊是指:以塊的形式處理數(shù)據(jù)。每一個操作都在一步中產(chǎn)生或者消費一個數(shù)據(jù)塊涨椒。
按塊的方式處理數(shù)據(jù)要比按流的方式處理數(shù)據(jù)快摊鸡,因為按塊的方式讀取或?qū)懭霐?shù)據(jù)所執(zhí)行的系統(tǒng)調(diào)用要遠少于一次一個字節(jié)的方式,類似于BufferedInputStream的方式蚕冬。
上面所說的塊免猾,在NIO中就是Buffer對象。
一個 Buffer(緩沖區(qū)) 實質(zhì)上是一個容器對象囤热,它包含一些要寫入或者剛讀出的數(shù)據(jù)猎提。在 NIO 庫中,所有數(shù)據(jù)都是用緩沖區(qū)處理的旁蔼。在讀取數(shù)據(jù)時锨苏,它是直接讀到緩沖區(qū)中的。在寫入數(shù)據(jù)時棺聊,它是寫入到緩沖區(qū)中的伞租。緩沖區(qū)實質(zhì)上是一個數(shù)組。通常它是一個字節(jié)數(shù)組限佩,但是也可以使用其他種類的數(shù)組葵诈。但是一個緩沖區(qū)不 僅僅 是一個數(shù)組。緩沖區(qū)提供了對數(shù)據(jù)的結構化訪問祟同,而且還可以跟蹤系統(tǒng)的讀/寫進程作喘。
舉例來說,ByteBuffer實質(zhì)上是對byte數(shù)組進行了封裝晕城,其內(nèi)部是一個byte數(shù)組泞坦,ByteBuffer對象提供了一些實用的API供我們?nèi)ゲ僮鬟@個數(shù)組,完成一些讀取或?qū)懭氲墓δ茏┣辍N覀兯獙W習的暇矫,就是理解在調(diào)用這些API的時候主之,Buffer處理數(shù)組的方式。
除了boolean類型之外李根,java為每種基本類型都封裝了對應的Buffer對象。
狀態(tài)變量
Buffer使用四個值指定了緩沖區(qū)在某個時刻的狀態(tài):
容量(Capacity):緩沖區(qū)能夠容納的數(shù)據(jù)元素的最大數(shù)量
實際上几睛,這個值指定了底層數(shù)組的大小房轿。這一值在緩沖區(qū)創(chuàng)建時被設定,并且永遠不能被改變所森。
位置(Position):下一個要被讀或?qū)懙脑氐乃饕?/p>
position 變量跟蹤已經(jīng)寫了多少數(shù)據(jù)囱持。更準確地說,它指定了下一個字節(jié)將放到數(shù)組的哪一個元素中焕济。比如纷妆,從通道中讀三個字節(jié)到緩沖區(qū)中,那么緩沖區(qū)的 position 將會設置為3晴弃,指向數(shù)組中第四個元素掩幢。
初始的position值為0。
邊界(Limit):緩沖區(qū)的第一個不能被讀或?qū)懙脑厣暇稀际邻;蛘哒f,緩沖區(qū)中現(xiàn)存元素的計數(shù)芍阎。
在寫模式下世曾,Buffer的limit表示你最多能往Buffer里寫多少數(shù)據(jù)。
當切換Buffer到讀模式時谴咸, limit表示你最多能讀到多少數(shù)據(jù)轮听。因此,當切換Buffer到讀模式時岭佳,limit會被設置成寫模式下的position值血巍。
標記(Mark):一個備忘位置。
調(diào)用 mark()來設定 mark = postion驼唱。調(diào)用 reset()設定 position = mark藻茂。
初始的mark值為-1。
上面四個屬性遵循以下的關系:
0 <= mark <= position <= limit <= capacity
API
- 創(chuàng)建
在了解這些api之前玫恳,首先需要知道如何創(chuàng)建一個Buffer對象:
在上一個小節(jié)中提到的7種緩沖區(qū)類沒有一種是可以直接實例化的辨赐,他們都是抽象類,但都包含了靜態(tài)工廠方法創(chuàng)建相應的實例京办。以ByteBuffer為例:(對于其他六中緩沖區(qū)類也適用)
ByteBuffer buffer = ByteBuffer.allocate(1024);
allocate方法分配了一個具有指定大小底層數(shù)組的緩沖區(qū)對象掀序,這個大小也就是上面提到的Capacity。
我們也可以使用已經(jīng)存在的數(shù)組來作為緩沖區(qū)對象的底層數(shù)組:
byte array[] = new byte[1024];
ByteBuffer buffer = ByteBuffer.wrap(array);
此時惭婿,buffer對象的底層數(shù)組指向了array不恭,這意味著直接修改array數(shù)組也會使buffer對象讀取的數(shù)據(jù)產(chǎn)生變化叶雹。
byte[] bs = new byte[10];
ByteBuffer buffer = ByteBuffer.wrap(bs);
System.out.println(buffer.toString());
打印如下:
java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
可見,新初始化的Buffer實例中换吧,position = 0折晦,limit=capacity=10
- 存取
注意到Buffer類中并沒有提供get或者put函數(shù)。實際上每一個Buffer對象都有這兩個函數(shù)沾瓦,但它們所采用的參數(shù)類型满着,以及它們返回的數(shù)據(jù)類型,對每個子類來說都是唯一的贯莺,所以它們不能在頂層Buffer類中被抽象地聲明风喇。這些存取方法被定義在Buffer類的子類當中,我們一ByteBuffer為例:
public abstract byte get();
public abstract byte get (int index);
public abstract ByteBuffer put (byte b);
public abstract ByteBuffer put (int index, byte b);
ByteBuffer實際上還提供了 get(byte[] dst, int offset, int length)
這樣的接口缕探,其內(nèi)部實現(xiàn)也是循環(huán)調(diào)用了get()方法魂莫。
get和put可以是相對的或者是絕對的。
相對方案是不帶有索引參數(shù)的函數(shù)爹耗。當相對函數(shù)被調(diào)用時耙考,位置在返回時前進一。
絕對存取不會影響緩沖區(qū)的位置屬性(Position鲸沮、Limit琳骡、Capacity、Mark)讼溺。
buffer.put((byte)'h').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'o');
print(buffer, bs);
buffer.put(0, (byte)'y').put((byte)'y');
print(buffer, bs);
//觀察Buffer底層存儲情況
public static void print(Buffer buffer, byte[] bs) {
System.out.println(buffer.toString());
for (int i = 0; i < bs.length; i++) {
if (bs[i] != 0) {
char c = (char)bs[i];
System.out.print(c);
} else {
System.out.print("$");
}
}
System.out.println("");
}
打印如下:
java.nio.HeapByteBuffer[pos=5 lim=10 cap=10]
hello$$$$$
java.nio.HeapByteBuffer[pos=6 lim=10 cap=10]
yelloy$$$$
可以看到楣号,存入5個字節(jié)之后,position增加為5怒坯,limit與capacity不變炫狱。
調(diào)用buffer.put(0, (byte)'y'),將bs[0]的數(shù)據(jù)改寫為(byte)'y'剔猿,position并沒有改變视译。
- Buffer.flip()
我們想要將剛剛寫入的數(shù)據(jù)讀出的話應該怎么做?應該將position設為0:buffer.position(0)归敬,就可以從正確的位置開始獲取數(shù)據(jù)酷含。但是它是怎樣知道何時到達我們所插入數(shù)據(jù)末端的呢?這就是邊界屬性被引入的目的汪茧。邊界屬性指明了緩沖區(qū)有效內(nèi)容的末端椅亚。我們需要將limit設置為當前位置:buffer.limit(buffer.position())。
buffer.limit(buffer.position()).position(0);
Buffer已經(jīng)提供了一個方法封裝了這些操作:
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
buffer.flip();
print(buffer, bs);
打印如下:
java.nio.HeapByteBuffer[pos=0 lim=6 cap=10]
yelloy$$$$
調(diào)用buffer.flip()后舱污,limit設置為當前position值呀舔,position重置為0.
- Buffer.rewind()
緊接著上面的程序:
System.out.println((char)buffer.get());
System.out.println((char)buffer.get(3));
print(buffer, bs);
buffer.rewind();
print(buffer, bs);
打印如下:
y
l
java.nio.HeapByteBuffer[pos=1 lim=6 cap=10]
yelloy$$$$
java.nio.HeapByteBuffer[pos=0 lim=6 cap=10]
yelloy$$$$
可以看到,rewind()方法與filp()相似扩灯,但是不影響limit媚赖,他只是將position設為0霜瘪,這樣就可以從新讀取已經(jīng)讀過的數(shù)據(jù)了。
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
- Buffer.mark()惧磺、Buffer.reset()
public final Buffer mark() {
mark = position;
return this;
}
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
Buffer.mark(),使緩沖區(qū)能夠記住一個位置并在之后將其返回颖对。
緩沖區(qū)的標記在mark()函數(shù)被調(diào)用之前是未定義的,調(diào)用時標記被設為當前位置的值磨隘。reset()函數(shù)將位置設為當前的標記值惜互。如果標記值未定義,調(diào)用reset()將導致InvalidMarkException異常琳拭。
buffer.position(2);
buffer.mark();
print(buffer, bs);
buffer.position(4);
print(buffer, bs);
buffer.reset();
print(buffer, bs);
打印如下:
java.nio.HeapByteBuffer[pos=2 lim=6 cap=10]
yelloy$$$$
java.nio.HeapByteBuffer[pos=4 lim=6 cap=10]
yelloy$$$$
java.nio.HeapByteBuffer[pos=2 lim=6 cap=10]
yelloy$$$$
- Buffer.remaining()、Buffer.hasRemaining()
remaining()函數(shù)將返回從當前位置到上界還剩余的元素數(shù)目描验。
hasRemaining()會返回是否已經(jīng)達到緩沖區(qū)的邊界白嘁。
public final int remaining() {
return limit - position;
}
public final boolean hasRemaining() {
return position < limit;
}
有兩種方法讀取緩沖區(qū)的所有剩余數(shù)據(jù):
// 第一種
for (int i = 0; buffer.hasRemaining(), i++) {
myByteArray [i] = buffer.get();
}
// 第二種
int count = buffer.remaining();
for (int i = 0; i < count, i++) {
myByteArray [i] = buffer.get();
}
- Buffer.clear()
clear()函數(shù)將緩沖區(qū)重置為空狀態(tài)。它并不改變緩沖區(qū)中的任何數(shù)據(jù)元素膘流,而是僅僅將上界設為容量的值絮缅,并把位置設回 0。
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
- ByteBuffer.compact()
compact()方法并不是Buffer接口中定義的呼股,而是屬于ByteBuffer耕魄。
如果Buffer中仍有未讀的數(shù)據(jù),且后續(xù)還需要這些數(shù)據(jù)彭谁,但是此時想要先寫些數(shù)據(jù)吸奴,那么使用compact()方法。
compact()方法將所有未讀的數(shù)據(jù)拷貝到Buffer起始處缠局。然后將position設到最后一個未讀元素正后面则奥。limit屬性依然像clear()方法一樣,設置成capacity∠猎埃現(xiàn)在Buffer準備好寫數(shù)據(jù)了读处,但是不會覆蓋未讀的數(shù)據(jù)。
print(buffer, bs);
System.out.println(buffer.remaining());
buffer.compact();
print(buffer, bs);
打印如下:
java.nio.HeapByteBuffer[pos=2 lim=6 cap=10]
yelloy$$$$
4
java.nio.HeapByteBuffer[pos=4 lim=10 cap=10]
lloyoy$$$$
- ByteBuffer.equals()唱矛、ByteBuffer.compareTo()
可以使用equals()和compareTo()方法兩個Buffer罚舱。
下面提到的剩余元素是從 position到limit之間的元素。
equals()
當滿足下列條件時绎谦,表示兩個Buffer相等:
有相同的類型(byte管闷、char、int等)燥滑。
Buffer中剩余的byte渐北、char等的個數(shù)相等。
Buffer中所有剩余的byte铭拧、char等都相同赃蛛。
在每個緩沖區(qū)中應被get()函數(shù)返回的剩余數(shù)據(jù)元素序列必須一致恃锉。
equals只是比較Buffer的一部分,不是每一個在它里面的元素都比較呕臂。實際上破托,它只比較Buffer中的剩余元素。
compareTo()
compareTo()方法比較兩個Buffer的剩余元素(byte歧蒋、char等)土砂, 如果滿足下列條件,則認為一個Buffer“小于”另一個Buffer:
第一個不相等的元素小于另一個Buffer中對應的元素 谜洽。
所有元素都相等萝映,但第一個Buffer比另一個先耗盡(第一個Buffer的元素個數(shù)比另一個少)。
只讀緩沖區(qū)
可以使用asReadOnlyBuffer()函數(shù)來生成一個只讀的緩沖區(qū)視圖阐虚。
這個新的緩沖區(qū)不允許使用put()序臂,并且其isReadOnly()函數(shù)將會返回true。對這一只讀緩沖區(qū)的put()函數(shù)的調(diào)用嘗試會導致拋出ReadOnlyBufferException異常实束。
兩個緩沖區(qū)共享數(shù)據(jù)元素奥秆,擁有同樣的容量,但每個緩沖區(qū)擁有各自的位置咸灿,上界和標記屬性构订。對一個緩沖區(qū)內(nèi)的數(shù)據(jù)元素所做的改變會反映在另外一個緩沖區(qū)上。
復制緩沖區(qū)
duplicate()函數(shù)創(chuàng)建了一個與原始緩沖區(qū)相似的新緩沖區(qū)避矢。兩個緩沖區(qū)共享數(shù)據(jù)元素悼瘾,擁有同樣的容量,但每個緩沖區(qū)擁有各自的位置谷异,上界和標記屬性分尸。對一個緩沖區(qū)內(nèi)的數(shù)據(jù)元素所做的改變會反映在另外一個緩沖區(qū)上。這一副本緩沖區(qū)具有與原始緩沖區(qū)同樣的數(shù)據(jù)視圖歹嘹。如果原始的緩沖區(qū)為只讀箩绍,或者為直接緩沖區(qū),新的緩沖區(qū)將繼承這些屬性尺上。
復制一個緩沖區(qū)會創(chuàng)建一個新的Buffer對象材蛛,但并不復制數(shù)據(jù)。原始緩沖區(qū)和副本都會操作同樣的數(shù)據(jù)元素怎抛。
直接緩沖區(qū)
直接ByteBuffer是通過調(diào)用ByteBuffer.allocateDirect(int capacity)函數(shù)來創(chuàng)建的卑吭。
什么是直接緩沖區(qū)(DirectByteBuffer)呢?直接緩沖區(qū)意味著所分配的這段內(nèi)存是堆外內(nèi)存马绝,而我們通過ByteBuffer.allocate(int capacity)或者ByteBuffer.wrap(byte[] array)分配的內(nèi)存是堆內(nèi)存豆赏,其返回的實例為HeapByteBuffer,HeapByteBuffer中持有一個byte數(shù)組,這個數(shù)組所占有的內(nèi)存是堆內(nèi)內(nèi)存掷邦。
Netty之Java堆外內(nèi)存掃盲貼了解java堆外內(nèi)存白胀。
sun.nio.ch.FileChannelImpl.read(ByteBuffer dst)
sun.nio.ch.IOUtil.read(FileDescriptor fd, ByteBuffer dst, long position,NativeDispatcher nd, Object lock)
觀察上面兩段代碼發(fā)現(xiàn),我們通過一個文件通道去填充一個ByteBuffer時抚岗,先執(zhí)行sun.nio.ch.FileChannelImpl.read(ByteBuffer dst)
方法或杠,其中調(diào)用了sun.nio.ch.IOUtil.read(FileDescriptor fd, ByteBuffer dst, long position,NativeDispatcher nd, Object lock)
方法,觀察這個方法宣蔚,發(fā)現(xiàn)其中會做一個判斷:如果是直接緩沖區(qū)(DirectBuffer)向抢,直接調(diào)用readIntoNativeBuffer(fd, dst, position, nd, lock)
并返回;如果是非直接緩沖區(qū)(HeapByteBuffer)胚委,先獲取一個直接緩沖區(qū)挟鸠,然后使用該直接緩沖區(qū)作為參數(shù)調(diào)用readIntoNativeBuffer(fd, dst, position, nd, lock)
,然后將填充完畢的DirectBuffer的內(nèi)容復制到HeapByteBuffer當中亩冬,然后返回兄猩。
直接緩沖區(qū)的內(nèi)存分配調(diào)用了sun.misc.Unsafe.allocateMemory(size),返回了內(nèi)存基地址,實際上就是malloc鉴未。
看一下java doc對DirectBuffer的說明:
A byte buffer is either direct or non-direct. Given a direct byte buffer, the Java virtual machine will make a best effort to perform native I/O operations directly upon it. That is, it will attempt to avoid copying the buffer's content to (or from) an intermediate buffer before (or after) each invocation of one of the underlying operating system's native I/O operations.
給定一個直接字節(jié)緩沖區(qū),Java 虛擬機將盡最大努力直接對它執(zhí)行本機 I/O 操作鸠姨。也就是說铜秆,它會在每一次調(diào)用底層操作系統(tǒng)的本機 I/O 操作之前(或之后),嘗試避免將緩沖區(qū)的內(nèi)容拷貝到一個中間緩沖區(qū)中(或者從一個中間緩沖區(qū)中拷貝數(shù)據(jù))讶迁。
結合上面的代碼连茧,就可以理解這段話的含義。
那么巍糯,為什么需要直接緩沖區(qū)啸驯,也就是堆外內(nèi)存來執(zhí)行IO呢?
以讀操作為例祟峦,數(shù)據(jù)從底層硬件讀到內(nèi)核緩沖區(qū)之后罚斗,操作系統(tǒng)會從內(nèi)核空間復制數(shù)據(jù)到用戶空間,此時的用戶進程空間就是jvm宅楞,這意味著 I/O 操作的目標內(nèi)存區(qū)域必須是連續(xù)的字節(jié)序列针姿。在 Java 中,數(shù)組是對象厌衙,在 JVM 中距淫,字節(jié)數(shù)組可能不會在內(nèi)存中連續(xù)存儲。因此婶希,這個連續(xù)的字節(jié)序列就是直接緩沖區(qū)中分配的內(nèi)存空間榕暇。需要直接緩沖區(qū)來當一個中間人,完成數(shù)據(jù)的寫入或者讀取。
其實彤枢,在傳統(tǒng)BIO中狰晚,也是這么做的,同樣需要一個堆外內(nèi)存來充當這個中間人:比如FileInputStream.read(byte b[], int off, int len):
FileInputStream.read(byte b[], int off, int len)調(diào)用了readBytes(byte b[], int off, int len)方法堂污,這個方法是一個本地方法:
JNIEXPORT jint JNICALL
Java_java_io_FileInputStream_readBytes(JNIEnv *env, jobject this,
jbyteArray bytes, jint off, jint len) {//除了前兩個參數(shù)家肯,后三個就是readBytes方法傳遞進來的,字節(jié)數(shù)組盟猖、起始位置讨衣、長度三個參數(shù)
return readBytes(env, this, bytes, off, len, fis_fd);
}
jint
readBytes(JNIEnv *env, jobject this, jbyteArray bytes,
jint off, jint len, jfieldID fid)
{
jint nread;
char stackBuf[BUF_SIZE];
char *buf = NULL;
FD fd;
if (IS_NULL(bytes)) {
JNU_ThrowNullPointerException(env, NULL);
return -1;
}
if (outOfBounds(env, off, len, bytes)) {
JNU_ThrowByName(env, "java/lang/IndexOutOfBoundsException", NULL);
return -1;
}
if (len == 0) {
return 0;
} else if (len > BUF_SIZE) {
buf = malloc(len);// buf的分配
if (buf == NULL) {
JNU_ThrowOutOfMemoryError(env, NULL);
return 0;
}
} else {
buf = stackBuf;
}
fd = GET_FD(this, fid);
if (fd == -1) {
JNU_ThrowIOException(env, "Stream Closed");
nread = -1;
} else {
nread = IO_Read(fd, buf, len);// buf是使用malloc分配的直接緩沖區(qū),也就是堆外內(nèi)存
if (nread > 0) {
(*env)->SetByteArrayRegion(env, bytes, off, nread, (jbyte *)buf);// 將直接緩沖區(qū)的內(nèi)容copy到bytes數(shù)組中
} else if (nread == JVM_IO_ERR) {
JNU_ThrowIOExceptionWithLastError(env, "Read error");
} else if (nread == JVM_IO_INTR) {
JNU_ThrowByName(env, "java/io/InterruptedIOException", NULL);
} else { /* EOF */
nread = -1;
}
}
if (buf != stackBuf) {
free(buf);
}
return nread;
}
可以看到式镐,這個方法其實最關鍵的就是IO_Read這個宏定義的處理反镇,而IO_Read其實只是代表了一個方法名稱叫handleRead,我們?nèi)タ匆幌耯andleRead的源碼娘汞。
JNIEXPORT
size_t
handleRead(jlong fd, void *buf, jint len)
{
DWORD read = 0;
BOOL result = 0;
HANDLE h = (HANDLE)fd;
if (h == INVALID_HANDLE_VALUE) {
return -1;
}
result = ReadFile(h,
buf,
len,
&read,
NULL);
if (result == 0) {
int error = GetLastError();
if (error == ERROR_BROKEN_PIPE) {
return 0;
}
return -1;
}
return read;
}
通過上面的代碼可以發(fā)現(xiàn)歹茶,傳統(tǒng)的BIO也是把操作系統(tǒng)返回的數(shù)據(jù)放到直接緩沖區(qū)當中,然后在copy回我們傳入的byte數(shù)組當中。
所有的緩沖區(qū)都提供了一個叫做isDirect()的boolean函數(shù)肴焊,來測試特定緩沖區(qū)是否為直接緩沖區(qū)署照。
A direct byte buffer may be created by invoking the allocateDirect factory method of this class. The buffers returned by this method typically have somewhat higher allocation and deallocation costs than non-direct buffers. The contents of direct buffers may reside outside of the normal garbage-collected heap, and so their impact upon the memory footprint of an application might not be obvious. It is therefore recommended that direct buffers be allocated primarily for large, long-lived buffers that are subject to the underlying system's native I/O operations. In general it is best to allocate direct buffers only when they yield a measureable gain in program performance.
直接緩沖區(qū)雖然避免了復制內(nèi)存帶來的消耗,但直接緩沖區(qū)使用的內(nèi)存是通過調(diào)用本地操作系統(tǒng)方面的代碼分配的尸昧,繞過了標準 JVM 堆棧。建立和銷毀直接緩沖區(qū)會明顯比具有堆棧的緩沖區(qū)更加破費旷偿,并且可能帶來不易察覺的內(nèi)存泄漏烹俗,或oom問題。所以萍程,如果對于性能要求不是很嚴格幢妄,一般情況下,使用非直接緩沖區(qū)就足夠了茫负。
緩沖區(qū)分片
slice() 方法根據(jù)現(xiàn)有的緩沖區(qū)創(chuàng)建一種 子緩沖區(qū) 蕉鸳。也就是說,它創(chuàng)建一個新的緩沖區(qū)忍法,新緩沖區(qū)與原來的緩沖區(qū)的一部分共享數(shù)據(jù)置吓。
現(xiàn)在我們對這個緩沖區(qū) 分片 ,以創(chuàng)建一個包含槽 3 到槽 6 的子緩沖區(qū)缔赠。在某種意義上衍锚,子緩沖區(qū)就像原來的緩沖區(qū)中的一個 窗口。
窗口的起始和結束位置通過設置 position 和 limit 值來指定嗤堰,然后調(diào)用 Buffer 的 slice() 方法:
print(buffer, bs);
buffer.position( 3 ).limit( 7 );
ByteBuffer slice = buffer.slice();
print(slice, slice.array());
打印如下:
java.nio.HeapByteBuffer[pos=4 lim=10 cap=10]
lloyoy$$$$
java.nio.HeapByteBuffer[pos=0 lim=4 cap=4]
lloyoy$$$$
slice 是緩沖區(qū)的 子緩沖區(qū) 戴质。不過度宦, slice 和 buffer 共享同一個底層數(shù)據(jù)數(shù)組。
類型視圖緩沖區(qū)
我們知道告匠,Buffer可以作為通道執(zhí)行IO的源頭或者目標戈抄,但是通道只接受ByteBuffer類型的參數(shù)。比如read(ByteBuffer dst)后专。
我們在進行IO操作時划鸽,可能會使用各種ByteBuffer類去讀取文件內(nèi)容,接收來自網(wǎng)絡連接的數(shù)據(jù)使用各種ByteBuffer類去讀取文件內(nèi)容戚哎,接收來自網(wǎng)絡連接的數(shù)據(jù)等裸诽。一旦數(shù)據(jù)到達了您的 ByteBuffer,我們需要對他進行一些操作型凳。ByteBuffer類允許創(chuàng)建視圖來將byte型緩沖區(qū)字節(jié)數(shù)據(jù)映射為其它的原始數(shù)據(jù)類型丈冬。例如,asLongBuffer()函數(shù)創(chuàng)建一個將八個字節(jié)型數(shù)據(jù)當成一個 long 型數(shù)據(jù)來存取的視圖緩沖區(qū)甘畅。
public abstract class ByteBuffer extends Buffer implements Comparable{
// 這里僅列出部分API
public abstract CharBuffer asCharBuffer();
public abstract ShortBuffer asShortBuffer();
public abstract IntBuffer asIntBuffer();
public abstract LongBuffer asLongBuffer();
public abstract FloatBuffer asFloatBuffer();
public abstract DoubleBuffer asDoubleBuffer();
}
buffer.clear();
buffer.order(ByteOrder.BIG_ENDIAN);//指定字節(jié)序
buffer.put (0, (byte)0);
buffer.put (1, (byte)'H');
buffer.put (2, (byte)0);
buffer.put (3, (byte)'i');
buffer.put (4, (byte)0);
buffer.put (5, (byte)'!');
buffer.put (6, (byte)0);
CharBuffer charBuffer = buffer.asCharBuffer();
System.out.println("pos=" + charBuffer.position() + " limit=" + charBuffer.limit() + " cap=" + charBuffer.capacity());;
print(charBuffer, bs);
打印如下:
pos=0 limit=5 cap=5
Hi!
$H$i$!$$$$
新的緩沖區(qū)的容量是字節(jié)緩沖區(qū)中存在的元素數(shù)量除以視圖類型中組成一個數(shù)據(jù)類型的字節(jié)數(shù)埂蕊。視圖緩沖區(qū)的第一個元素從創(chuàng)建它的ByteBuffer對象的位置開始(positon()函數(shù)的返回值)。
測試代碼
前面提到的測試代碼匯總:
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;
public class Test {
public static void main(String[] args) {
byte[] bs = new byte[10];
ByteBuffer buffer = ByteBuffer.wrap(bs);
System.out.println(buffer.toString());
// put
buffer.put((byte)'h').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'o');
print(buffer, bs);
buffer.put(0, (byte)'y').put((byte)'y');
print(buffer, bs);
//flip
buffer.flip();
print(buffer, bs);
// rewind
System.out.println((char)buffer.get());
System.out.println((char)buffer.get(3));
print(buffer, bs);
buffer.rewind();
print(buffer, bs);
// mark reset
buffer.position(2);
buffer.mark();
print(buffer, bs);
buffer.position(4);
print(buffer, bs);
buffer.reset();
print(buffer, bs);
// compact
System.out.println(buffer.remaining());
buffer.compact();
print(buffer, bs);
// slice
buffer.position( 3 ).limit( 7 );
ByteBuffer slice = buffer.slice();
print(slice, slice.array());
// asCharBuffer
buffer.clear();
buffer.order(ByteOrder.BIG_ENDIAN);
buffer.put (0, (byte)0);
buffer.put (1, (byte)'H');
buffer.put (2, (byte)0);
buffer.put (3, (byte)'i');
buffer.put (4, (byte)0);
buffer.put (5, (byte)'!');
buffer.put (6, (byte)0);
CharBuffer charBuffer = buffer.asCharBuffer();
System.out.println("pos=" + charBuffer.position() + " limit=" + charBuffer.limit() + " cap=" + charBuffer.capacity());;
print(charBuffer, bs);
}
public static void print(Buffer buffer, byte[] bs) {
System.out.println(buffer.toString());
for (int i = 0; i < bs.length; i++) {
if (bs[i] != 0) {
char c = (char)bs[i];
System.out.print(c);
} else {
System.out.print("$");
}
}
System.out.println("");
}
}