I/O
作為開(kāi)發(fā)者横辆,I/O是一定會(huì)遇到的。以常見(jiàn)的文件操作為例困肩,原生的java代碼如下:
// 基本字節(jié)流一次讀寫(xiě)一個(gè)字節(jié)數(shù)組
public static void method2(String srcString, String destString)
throws IOException {
FileInputStream fis = new FileInputStream(srcString);
FileOutputStream fos = new FileOutputStream(destString);
byte[] bys = new byte[1024];
int len = 0;
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}
fos.close();
fis.close();
}
用FileInputStream能完成基本的功能脆侮,但是會(huì)有一個(gè)問(wèn)題就是,性能上還有優(yōu)化的空間潭枣。
問(wèn)題出在哪里幻捏?FileInputStream.read和FileOutputStream.write這兩個(gè)方法,每次執(zhí)行都會(huì)進(jìn)行I/O操作蚣抗,由于I/O操作是比較好資源的瓮下,頻繁的操作必然導(dǎo)致性能上的問(wèn)題。
為什么使用Buffer
上面我們講到FileInputStream.read和FileOutputStream.write會(huì)頻繁地進(jìn)行I/O操作锭魔。為了解決這個(gè)阻塞的問(wèn)題路呜,引入了Buffer這個(gè)概念。
Buffer做了什么事情呢漠秋?有了Buffer以后抵屿,每次的讀取,java會(huì)讀取更多的數(shù)據(jù)到緩沖區(qū)里搂抒,下次再調(diào)用read方法時(shí),如果緩沖區(qū)里的數(shù)據(jù)夠了求晶,就直接返回?cái)?shù)據(jù)芳杏,不用再執(zhí)行I/O操作。寫(xiě)入也是如此蚜锨,通過(guò)這種方式亚再,化零為整晨抡,減低了I/O操作的頻率,提升效率如捅。
buffer 的主要目的進(jìn)行流量整形调煎,把突發(fā)的大數(shù)量較小規(guī)模的 I/O 整理成平穩(wěn)的小數(shù)量較大規(guī)模的 I/O,以減少響應(yīng)次數(shù)(比如從網(wǎng)上下電影悲关,你不能下一點(diǎn)點(diǎn)數(shù)據(jù)就寫(xiě)一下硬盤(pán)娄柳,而是積攢一定量的數(shù)據(jù)以后一整塊一起寫(xiě),不然硬盤(pán)都要被你玩壞了)秫筏。
// 高效字節(jié)流一次讀寫(xiě)一個(gè)字節(jié)數(shù)組:
public static void method4(String srcString, String destString)
throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
srcString));
//為什么不傳遞一個(gè)具體的文件或者文件路徑挎挖,而是傳遞一個(gè)OutputStream對(duì)象?
//因?yàn)樽止?jié)緩沖區(qū)流僅僅提供緩沖區(qū),為高效而設(shè)計(jì)的崔涂。真正的讀寫(xiě)操作還是基本的流對(duì)象實(shí)現(xiàn)墓造。
//構(gòu)造方法可以指定緩沖區(qū)的大小锚烦,但是我們一般不用涮俄,默認(rèn)緩沖區(qū)大小就夠了
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(destString));
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bos.close();
bis.close();
}
ByteBuffer
上面介紹了BufferedOutputStream尸闸,在網(wǎng)絡(luò)I/O方面,用的最多的就是ByteBuffer了苞尝。ByteBuffer的使用方式如下:
ByteBuffer byteBuffer = ByteBuffer.allocate(88);
String value = "netty";
byteBuffer.put(value.getBytes());
byteBuffer.flip();
byte[] valueArray = new byte[byteBuffer.remaining()];
byteBuffer.get(valueArray);
String decodeVaule = new String(valueArray);
但是JDK自帶的ByteBuffer并不足夠完美宦芦,它有以下缺陷:
- ByteBuffer長(zhǎng)度固定,一旦分配完成抡砂,它的容量不能動(dòng)態(tài)擴(kuò)展和收縮恬涧,當(dāng)需要編碼的POJO對(duì)象大于ByteBuffer的容量時(shí),會(huì)發(fā)生索引越界異常丑搔;
- ByteBuffer只有一個(gè)標(biāo)識(shí)位控的指針position,讀寫(xiě)的時(shí)候需要手工調(diào)用flip()和rewind()等提揍,使用者必須小心謹(jǐn)慎地處理這些API,否則很容易導(dǎo)致程序處理失敗顽冶;
- ByteBuffer的API功能有限售碳,一些高級(jí)和實(shí)用的特性它不支持,需要使用者自己編程實(shí)現(xiàn)间景。
ByteBuf
大名鼎鼎的通信框架netty為了解決ByteBuffer的缺陷艺智,重寫(xiě)了一個(gè)新的數(shù)據(jù)接口ByteBuf。
與ByteBuffer相比封拧,ByteBuf提供了兩個(gè)指針,分別記錄讀和寫(xiě)的操作位置曹铃。
初始分配的ByteBuf:
寫(xiě)入N個(gè)字節(jié)之后的ByteBuf:
讀取M(<N)個(gè)字節(jié)之后的ByteBuf:
調(diào)用discardReadBytes操作之后的ByteBuf:
調(diào)用clear操作之后的ByteBuf:
字節(jié)緩沖區(qū)
netty為了進(jìn)一步優(yōu)化提升性能,支持了堆外緩沖區(qū)评甜。
屬性 | Heap buffer | Direct Buffer |
---|---|---|
位置 | 堆內(nèi) | 堆外 |
內(nèi)存分配速度 | 快 | 慢 |
內(nèi)粗能回收速度 | 快 | 慢 |
Socket的I/O讀寫(xiě) | 需要額外的內(nèi)存復(fù)制 | 不需要額外的內(nèi)存復(fù)制 |
netty官方有一句描述了使用直接緩沖區(qū)的風(fēng)險(xiǎn)仔涩。
allocating many short-lived direct NIO buffers often causes an OutOfMemoryError.
為了更高效地使用堆外緩沖區(qū)红柱,netty通過(guò)內(nèi)存池和引用計(jì)數(shù)很好地繞開(kāi)了Direct Buffer的劣勢(shì)蓖乘,發(fā)揚(yáng)了它的優(yōu)勢(shì)。
關(guān)于zero copy
Netty的零拷貝體現(xiàn)在三個(gè)方面:
- Direct Buffers
- Composite Buffers
- FileChannel.transferTo
參考資料
Cache 和 Buffer 都是緩存嘉抒,主要區(qū)別是什么些侍?
IO、NIO岗宣、Netty
Using as a generic library
Netty中的零拷貝