關(guān)于NIO Buffer中4個(gè)重要狀態(tài)屬性的含義:position、limit更舞、capacity與mark
Buffer本身是一個(gè)容器坎缭,稱作緩沖區(qū),里面包裝了特定的一種原生類型盘榨,其子類包括ByteBuffer、CharBuffer蟆融、LongBuffer草巡、IntBuffer、DoubleBuffer型酥、ShortBuffer山憨、FloatBuffer. 注意是沒有Boolean類型的Buffer
Buffer是一種特定原生類型線性的有限的元素序列,Buffer中比較重要的4個(gè)屬性:position弥喉、limit郁竟、capacity、mark. 在使用 Buffer 時(shí)由境,我們實(shí)際操作的就是這四個(gè)屬性的值.
具體介紹下4個(gè)屬性:
-
capacity(容量):一個(gè)buffer能夠容納數(shù)據(jù)元素的最大數(shù)量棚亩,capacity不會(huì)為負(fù)數(shù),且永遠(yuǎn)不能被改變.
假設(shè):IntBuffer.allocate(1024), 分配了大小為1024的元素個(gè)數(shù)虏杰,則capacity就等于1024. -
limit(上界):一個(gè)buffer的limit指的是第一個(gè)不能在讀也不能在寫的元素索引. limit不會(huì)為負(fù)數(shù)讥蟆,并且一定是小于capacity的.
假設(shè):IntBuffer.allocate(1024), 我們?cè)诔绦蛑性O(shè)置limit=512,說明Buffer的容量是1024嘹屯,但是從512之后既不能讀也不能寫了攻询,進(jìn)一步說明該Buffer的實(shí)際可用大小是512. - position(位置):一個(gè)buffer的position指的是下一個(gè)將要讀或者寫的元素的索引.position不會(huì)為負(fù)數(shù),并且一定是小于limit的. position的位置主要由get()和put()方法的調(diào)用來更新.
-
mark(標(biāo)記):一個(gè)備忘地址州弟,作為臨時(shí)標(biāo)記位置使用钧栖,標(biāo)記在設(shè)定前是未定義的.
mark的使用場(chǎng)景:
假設(shè)IntBuffer.allocate(1024)低零,現(xiàn)在position位置為10,現(xiàn)在只想發(fā)送512到1024之間的緩沖數(shù)據(jù)拯杠,此時(shí)我們可以buffer.mark(buffer.position())既將position記入mark位置掏婶,然后buffer.postion(512),此時(shí)發(fā)送的數(shù)據(jù)就是512到1024之間的數(shù)據(jù)潭陪。發(fā)送完成后雄妥,調(diào)用buffer.reset()將mark臨時(shí)標(biāo)記賦值給position使得position=mark。注意如果未設(shè)定mark依溯,而調(diào)用了buffer.reset()方法則會(huì)拋出InvalidMarkException.
不變式:
0 <= mark <= position <= limit <= capacity
傳輸數(shù)據(jù):
Buffer中的每個(gè)子類中都有g(shù)et()和put()方法.
帶參數(shù)的put和get方法稱作絕對(duì)存入/取出老厌,位置是通過參數(shù)指定的.
絕對(duì)操作不影響position位置,但是如果索引位置超出limit黎炉,則會(huì)拋出IndexOutOfBoundsException枝秤;
不帶參數(shù)的put和get稱作相對(duì)存入/取出,即position位置自動(dòng)前進(jìn).
對(duì)于get相對(duì)操作慷嗜,如果位置超過了limit淀弹,則會(huì)拋出BufferUnderflowException;
對(duì)于put相對(duì)操作庆械,如果位置超過了limit薇溃,則會(huì)拋出BufferOverflowException;
線程安全性:
buffer在多線程并發(fā)下并不是安全的缭乘。如果一個(gè)buffer會(huì)在多個(gè)線程使用沐序,那么需要使用恰當(dāng)?shù)耐讲僮鱽碓L問buffer。也就是buffer本身并不是線程安全的忿峻。
常用方法:
這部分我們以實(shí)際代碼為例來說明:
clear()方法薄啥,清除,將buffer重置為空狀態(tài)逛尚,它并不會(huì)更改緩沖區(qū)內(nèi)的任何數(shù)據(jù)元素. 如果此時(shí)還沒有讀取的數(shù)據(jù),則就無法讀取到了.
public final Buffer clear() {
position = 0; // 位置重置為0
limit = capacity; // limit重置為capacity
mark = -1; // 丟棄標(biāo)記
return this;
}
flip()方法刁愿,翻轉(zhuǎn)绰寞,使buffer從寫模式轉(zhuǎn)換到讀模式.
public final Buffer flip() {
limit = position; // 將limit設(shè)置為position
position = 0; // position重置為0
mark = -1; // 丟棄標(biāo)記
return this;
}
rewind()方法,重繞铣口,重置position為0滤钱,limit保持不變,此時(shí)調(diào)用rewind前buffer已處于讀模式下了, 可以重新讀取buffer中的數(shù)據(jù).
public final Buffer rewind() {
position = 0; // 重置position為0
mark = -1; // 丟棄標(biāo)記
return this;
}
compact()方法脑题,將所有未讀的數(shù)據(jù)拷貝到Buffer起始處. 然后將position設(shè)到最后一個(gè)未讀元素正后面. limit屬性依然像clear()方法一樣件缸,設(shè)置成capacity. 現(xiàn)在Buffer準(zhǔn)備好寫數(shù)據(jù)了,但是不會(huì)覆蓋未讀的數(shù)據(jù).
注意compact方法的實(shí)現(xiàn)是由原生類型的子類實(shí)現(xiàn)叔遂,比如ByteBuffer則由HeapByteBuffer中實(shí)現(xiàn).
public ByteBuffer compact() {
System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
position(remaining());
limit(capacity());
discardMark();
return this;
}
hasRemaining()方法他炊,會(huì)在讀取緩沖區(qū)時(shí)告訴你是否已經(jīng)達(dá)到緩沖區(qū)的上界. 可以通過remaining()高效讀取buffer數(shù)據(jù).
int count = buffer.remaining();
for (int i = 0; i < count; i++) {
myByteArray[i] = buffer.get();
}
slice()方法争剿,是對(duì)原有數(shù)據(jù)的一個(gè)快照,共享相同的底層數(shù)據(jù)元素. 調(diào)用slice方法后痊末,會(huì)得到大于等于position且小于limit之間的數(shù)據(jù)蚕苇,對(duì)于改變slice方法獲得大buffer數(shù)據(jù),也能夠反映到原buffer上凿叠。
舉個(gè)例子:
public class NioTest6_slice {
public static void main(String[] args) {
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
int capacity = byteBuffer.capacity();
for (int i = 0 ; i < capacity; i++) {
byteBuffer.put((byte)i);
}
// 設(shè)置position涩笤、limit
byteBuffer.position(2);
byteBuffer.limit(6);
//slice方法是前閉后開的 大于等于position,小于limit
ByteBuffer subByteBuffer = byteBuffer.slice();
// 改變subByteBuffer內(nèi)容盒件,也能反映到byteBuffer上
for (int j = 0; j < subByteBuffer.capacity(); j++) {
// 2到5位置的數(shù)*2
subByteBuffer.put((byte)(2 * subByteBuffer.get(j)));
}
// 設(shè)置回原來的值蹬碧,打印輸出看下byteBuffer數(shù)據(jù)變化
byteBuffer.position(0);
byteBuffer.limit(capacity);
while (byteBuffer.hasRemaining()) {
System.out.println(byteBuffer.get());
}
}
}
輸出結(jié)果中看到第3個(gè)位置到第5個(gè)位置的數(shù)據(jù)都??2了,返回4,6,8.
0
1
4
6
8
10
6
7
8
9
duplicate()方法炒刁,創(chuàng)建了一個(gè)與原始緩沖區(qū)相似的新緩沖區(qū)锰茉,與調(diào)用slice方法一樣也是共享相同的底層數(shù)據(jù)元素, 擁有同樣的容量, 但每個(gè)緩沖區(qū)擁有各自的 position、limit 和 mark 屬性.
public class NioTest6_duplicate {
public static void main(String[] args) {
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
int capacity = byteBuffer.capacity();
for (int i = 0 ; i < capacity; i++) {
byteBuffer.put((byte)i);
}
ByteBuffer subByteBuffer = byteBuffer.duplicate();
subByteBuffer.position(0); // 單獨(dú)給duplicate出來的buffer設(shè)置position
System.out.println("byteBuffer position:" + byteBuffer.position() + "--subByteBuffer position:" + subByteBuffer.position());
// 改變subByteBuffer內(nèi)容切心,也能反映到byteBuffer上
for (int j = 0; j < subByteBuffer.capacity(); j++) {
// 2到5位置的數(shù)*2
subByteBuffer.put((byte)(2 * subByteBuffer.get(j)));
}
// 切換到讀模式
byteBuffer.flip();
while (byteBuffer.hasRemaining()) {
System.out.println(byteBuffer.get());
}
}
}
輸出結(jié)果能看到原buffer數(shù)據(jù)元素變化
byteBuffer position:10--subByteBuffer position:0
0
2
4
6
8
10
12
14
16
18
asReadOnlyBuffer()方法飒筑,我們可以隨時(shí)將一個(gè)普通的Buffer調(diào)用asReadOnlyBuffer方法返回一個(gè)只讀Buffer. 但是,不能將一個(gè)只讀Buffer轉(zhuǎn)換為讀寫B(tài)uffer.
關(guān)于Buffer的Scattering 和 Gathering
scatter / gather經(jīng)常用于需要將傳輸?shù)臄?shù)據(jù)分開處理的場(chǎng)合绽昏,例如傳輸一個(gè)由消息頭和消息體組成的消息协屡,你可能會(huì)將消息體和消息頭分散到不同的buffer中,這樣你可以方便的處理消息頭和消息體.
Scattering:
Scattering Reads在移動(dòng)下一個(gè)buffer前全谤,必須填滿當(dāng)前的buffer肤晓,這也意味著它不適用于動(dòng)態(tài)消息(譯者注:消息大小不固定). 換句話說,如果存在消息頭和消息體认然,消息頭必須完成填充(例如 128byte)补憾,Scattering Reads才能正常工作。
Gattering:
buffer數(shù)組是write()方法的入?yún)⒕碓保瑆rite()方法會(huì)按照buffer在數(shù)組中的順序盈匾,將數(shù)據(jù)寫入到channel,注意只有position和limit之間的數(shù)據(jù)才會(huì)被寫入毕骡。
因此削饵,如果一個(gè)buffer的容量為128byte,但是僅僅包含58byte的數(shù)據(jù)未巫,那么這58byte的數(shù)據(jù)將被寫入到channel中窿撬。因此與Scattering Reads相反,Gathering Writes能較好的處理動(dòng)態(tài)消息.
舉個(gè)例子:
public class NioTest10_scatteringandgathering {
public static void main(String[] args) throws Exception {
try(ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();) {
InetSocketAddress inetSocketAddress = new InetSocketAddress(8899);
serverSocketChannel.socket().bind(inetSocketAddress); // 綁定到8899端口
int messageLength = 2 + 3 + 4;
ByteBuffer[] byteBuffers = new ByteBuffer[3];
byteBuffers[0] = ByteBuffer.allocate(2);
byteBuffers[1] = ByteBuffer.allocate(3);
byteBuffers[2] = ByteBuffer.allocate(4);
SocketChannel socketChannel = serverSocketChannel.accept();
do {
int byteRead = 0;
while (byteRead < messageLength) {
long r = socketChannel.read(byteBuffers);
System.out.println("--------------r:" + r);
byteRead += r;
System.out.println("byteRead:" + byteRead);
Arrays.asList(byteBuffers).stream().map(buffer -> "position:" + buffer.position() + ", limit:" + buffer.limit())
.forEach(System.out::println);
}
Arrays.asList(byteBuffers).forEach(Buffer::flip);
// 實(shí)際寫的個(gè)數(shù)
int byteWrites = 0;
while (byteWrites < messageLength) {
long r = socketChannel.write(byteBuffers);
byteWrites += r;
}
Arrays.asList(byteBuffers).forEach(Buffer::clear);
System.out.println("byteRead:" + byteRead + ", byteWrite:" + byteWrites + ", messageLength:" + messageLength);
} while (true);
}
}
}
以上服務(wù)啟動(dòng)后叙凡,我們通過nc localhost 8899或者telecom localhost 8899,然后輸入Hello world字符串劈伴,在控制臺(tái)上能看到輸出:
--------------r:9
byteRead:9
position:2, limit:2
position:3, limit:3
position:4, limit:4
byteRead:9, byteWrite:9, messageLength:9
參考文章:
http://zachary-guo.iteye.com/blog/1457542
http://www.reibang.com/p/1af407c043cb