Posted on 2010-11-03 23:22
dennis 閱讀(2802)
評論(2) 編輯 收藏 所屬分類:
java
我最近在實現一個基于Kilim的HttpClient,在處理響應body特別大的情形下遇到了kilim的一個BUG,有必要記錄下。
問題是這樣,Kilim將連接封裝為EndPoint對象,EndPoint有個方法fill用于從管道讀數據到緩沖區,并且可以指定希望至少讀到多少個字節(atLeastN)才返回。那么在進入此方法的時候會判斷緩沖區是否有足夠空間容納atLeastN個字節,如果沒有,則創建一個更大的緩沖區,并將“老”的緩沖區的數據拷貝到新緩沖區,這部分代碼是這樣實現:
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就出現在這段代碼里。這段代碼的邏輯很簡單,先是創建一個新的更大的緩沖區,然后將老的緩沖區的數據put到新的緩沖區,在put之前調用rewind方法將老的緩沖區的position設置為0。查看rewind干了什么:
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
僅僅是將position設置為0,并讓mark失效。position指向下一個讀或者寫的位置,這里在寫入到新緩沖區之前確實需要將position設置為0,以便寫入從老的緩沖區第一個位置開始。問題是什么?問題是position僅僅指定了下一個讀取數據的位置,卻沒有指定有效數據的大小,換句話說,沒有指定老的緩沖區的limit。因此這里造成的后果是老的緩沖區整個被寫入到新的老緩沖區,包括有效數據和無效數據,默認情況下緩沖區的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寫入一個整數99,然后創建newBuf并寫入old數據,并再寫入一個整數100,最后從newBuf讀數據。本來我們預期只應該讀到兩個整數99和100,但是中間卻插入一個0,輸出如下:
12
99
0
100
12表示緩沖區可讀的數據,本來應該是8個字節,卻多了4個字節的無效數據。
這個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已經正確設置,例如你將buffer寫入成功并想記錄這個buffer,可以使用rewind:
while (buffer.hasRemaining()) //發送數據
networkChannel.write(buffer);
buffer.rewind(); // 重置buffer,準備寫入日志管道
while (buffer.hasRemaining()) // 寫入日志
loggerChannel.write(buffer);
而flip用于緩沖區發送或者讀取之前,也就是將緩沖區設置為等待傳出狀態。