Posted on 2010-11-03 23:22
dennis 閱讀(2808)
評論(2) 編輯 收藏 所屬分類:
java
我最近在實現(xiàn)一個基于Kilim的HttpClient,在處理響應body特別大的情形下遇到了kilim的一個BUG,有必要記錄下。
問題是這樣,Kilim將連接封裝為EndPoint對象,EndPoint有個方法fill用于從管道讀數(shù)據(jù)到緩沖區(qū),并且可以指定希望至少讀到多少個字節(jié)(atLeastN)才返回。那么在進入此方法的時候會判斷緩沖區(qū)是否有足夠空間容納atLeastN個字節(jié),如果沒有,則創(chuàng)建一個更大的緩沖區(qū),并將“老”的緩沖區(qū)的數(shù)據(jù)拷貝到新緩沖區(qū),這部分代碼是這樣實現(xiàn):
public ByteBuffer fill(ByteBuffer buf, int atleastN) throws IOException, Pausable {
if (buf.remaining() < atleastN) {
ByteBuffer newbb = ByteBuffer.allocate(Math.max(buf.capacity() * 3 / 2, buf.position() + atleastN));
buf.rewind();
newbb.put(buf);
buf = newbb;
}
……
}
后面的代碼我省略了,這個BUG就出現(xiàn)在這段代碼里。這段代碼的邏輯很簡單,先是創(chuàng)建一個新的更大的緩沖區(qū),然后將老的緩沖區(qū)的數(shù)據(jù)put到新的緩沖區(qū),在put之前調(diào)用rewind方法將老的緩沖區(qū)的position設置為0。查看rewind干了什么:
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
僅僅是將position設置為0,并讓mark失效。position指向下一個讀或者寫的位置,這里在寫入到新緩沖區(qū)之前確實需要將position設置為0,以便寫入從老的緩沖區(qū)第一個位置開始。問題是什么?問題是position僅僅指定了下一個讀取數(shù)據(jù)的位置,卻沒有指定有效數(shù)據(jù)的大小,換句話說,沒有指定老的緩沖區(qū)的limit。因此這里造成的后果是老的緩沖區(qū)整個被寫入到新的老緩沖區(qū),包括有效數(shù)據(jù)和無效數(shù)據(jù),默認情況下緩沖區(qū)的limit等于capacity。
這個bug可以通過下面程序看出來:
ByteBuffer old = ByteBuffer.allocate(8);
old.putInt(99);
ByteBuffer newBuf = ByteBuffer.allocate(16);
old.rewind();
newBuf.put(old);
newBuf.putInt(100);
newBuf.flip();
System.out.println(newBuf.remaining());
System.out.println(newBuf.getInt());
System.out.println(newBuf.getInt());
System.out.println(newBuf.getInt());
先往old寫入一個整數(shù)99,然后創(chuàng)建newBuf并寫入old數(shù)據(jù),并再寫入一個整數(shù)100,最后從newBuf讀數(shù)據(jù)。本來我們預期只應該讀到兩個整數(shù)99和100,但是中間卻插入一個0,輸出如下:
12
99
0
100
12表示緩沖區(qū)可讀的數(shù)據(jù),本來應該是8個字節(jié),卻多了4個字節(jié)的無效數(shù)據(jù)。
這個BUG解決很簡單,將rewind修改為flip方法即可,flip不僅將position設置為0,也將limit設置為當前位置:
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
修改上面的測試程序,符合我們的預期了:
ByteBuffer old = ByteBuffer.allocate(8);
old.putInt(99);
ByteBuffer newBuf = ByteBuffer.allocate(16);
old.flip();
newBuf.put(old);
newBuf.putInt(100);
newBuf.flip();
System.out.println(newBuf.remaining());
System.out.println(newBuf.getInt());
System.out.println(newBuf.getInt());;
輸出:
8
99
100
總結,使用rewind的前提是limit已經(jīng)正確設置,例如你將buffer寫入成功并想記錄這個buffer,可以使用rewind:
while (buffer.hasRemaining()) //發(fā)送數(shù)據(jù)
networkChannel.write(buffer);
buffer.rewind(); // 重置buffer,準備寫入日志管道
while (buffer.hasRemaining()) // 寫入日志
loggerChannel.write(buffer);
而flip用于緩沖區(qū)發(fā)送或者讀取之前,也就是將緩沖區(qū)設置為等待傳出狀態(tài)。