最近在學(xué)習(xí)netty扎拣,其中有對(duì)比bio、nio稽鞭、netty使用上的不同鸟整,也趁此機(jī)會(huì)回顧了相關(guān)知識(shí),加深下理解朦蕴,主要涉及的有FileInputStream篮条、FileOutputStream弟头、BufferedInputStream、BufferedOutputStream涉茧、FileChannel赴恨、MappedByteBuffer等知識(shí)點(diǎn)。
一降瞳、FileIn(Out)putStream(單字節(jié))
public static void fileCopy(String src,String dest) throws IOException{
long t = System.currentTimeMillis();
File file = new File(src);
try(
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(new File(dest));
){
int result = 0;
while((result = fis.read()) != -1){
fos.write(result);
}
}
System.err.println("file copy use time="+(System.currentTimeMillis()-t)+" file.size="+(file.length()/1024/1024)+"MB");
}
控制臺(tái)輸出:
file copy use time=62065 file.size=20MB
從輸出接口可以看到嘱支,20MB文件copy用時(shí)一分鐘多,可見(jiàn)不使用緩沖區(qū)挣饥,單字節(jié)讀寫(xiě)時(shí)執(zhí)行慢的無(wú)法接受。
這里使用了try-with-resources的方式來(lái)進(jìn)行流的自動(dòng)關(guān)閉沛膳,對(duì)該知識(shí)點(diǎn)不熟悉的可參考Java 7中的Try-with-resources;
二扔枫、FileIn(Out)putStream(字節(jié)數(shù)組)
public static void fileCopyWithBuffer(String src,String dest) throws IOException{
long t = System.currentTimeMillis();
File file = new File(src);
try(
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(new File(dest));
){
byte[] buffer = new byte[1024];
int byteRead = 0;
while((byteRead = fis.read(buffer)) != -1){
fos.write(buffer,0,byteRead);
}
}
System.err.println("fileCopyWithBuffer use time="+(System.currentTimeMillis()-t)+" file.size="+(file.length()/1024/1024)+"MB");
}
控制臺(tái)輸出:
fileCopyWithBuffer use time=140 file.size=20MB
可見(jiàn)使用buffer進(jìn)行批量讀寫(xiě)后,性能有了質(zhì)的提高.
下面我們來(lái)調(diào)整buffer數(shù)組的長(zhǎng)度為10240锹安,看下輸出:
fileCopyWithBuffer use time=45 file.size=20MB
經(jīng)過(guò)多次嘗試短荐,發(fā)現(xiàn)適當(dāng)?shù)脑黾觔uffer的長(zhǎng)度,可以明顯提升處理速度叹哭。
三忍宋、BufferedIn(Out)putStream(單字節(jié))
public static void fileCopyWithBufferStream(String src,String dest) throws IOException{
long t = System.currentTimeMillis();
File file = new File(src);
try(
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(new File(dest));
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
){
int result = 0;
while((result =bis.read()) != -1){
bos.write(result);
}
}
System.err.println("fileCopyWithBufferStream use time="+(System.currentTimeMillis()-t)+" file.size="+(file.length()/1024/1024)+"MB");
}
控制臺(tái)輸出:
fileCopyWithBufferStream use time=1199 file.size=20MB
相對(duì)于單字節(jié)的流讀寫(xiě)方式,處理時(shí)間有62s提升至1.2s风罩,提升還是很大的糠排。
BufferedInputStream創(chuàng)建時(shí)內(nèi)部會(huì)默認(rèn)創(chuàng)建一個(gè)長(zhǎng)度8192的byte數(shù)組,read方法實(shí)際上是在這個(gè)內(nèi)存數(shù)組里面讀取數(shù)據(jù)超升,下面我們看下read方法的源碼:
public synchronized int read() throws IOException {
if (pos >= count) { // 當(dāng)數(shù)據(jù)已讀完時(shí)
fill(); // 調(diào)用上層inputStream內(nèi)read方法讀取數(shù)據(jù)填充buf
if (pos >= count) // 如果仍無(wú)可讀數(shù)據(jù)入宦,說(shuō)明數(shù)據(jù)已讀完
return -1;
}
//從此處可看到read操作實(shí)際讀取的是buf數(shù)組的數(shù)據(jù)
//& 0xff的目的是消除后八位之前的位數(shù)據(jù),保證讀出的字節(jié)數(shù)據(jù)無(wú)變化室琢,防止首位為1的情況轉(zhuǎn)為int時(shí)高位為填充為1
return getBufIfOpen()[pos++] & 0xff;
}
- 當(dāng)緩存區(qū)無(wú)可讀數(shù)據(jù)時(shí)乾闰,調(diào)用fill方法,fill方法內(nèi)部會(huì)考慮mark和reset的邏輯盈滴,設(shè)置新讀入數(shù)據(jù)再數(shù)組內(nèi)的存放位置
- 調(diào)用InputStream內(nèi)的read(byte b[], int off, int len)方法進(jìn)行數(shù)據(jù)的“批量”讀妊募纭;
- 調(diào)用bis.read()時(shí)巢钓,從buffer內(nèi)返回?cái)?shù)據(jù)
“批量”的讀數(shù)據(jù)病苗,從而提高了整體的執(zhí)行速度。fill()方法的實(shí)現(xiàn)此處不再展開(kāi)竿报,可參考BufferedInputStream源碼分析.
BufferedOutputStream的write方法铅乡,也是先將數(shù)據(jù)存入buf,當(dāng)buf存滿(mǎn)時(shí)烈菌,將數(shù)據(jù)刷出阵幸;
public synchronized void write(int b) throws IOException {
if (count >= buf.length) {
flushBuffer();
}
buf[count++] = (byte)b;
}
四花履、BufferedIn(Out)putStream(多字節(jié))
public static void fileCopyWithBufferStreamAndBuffer(String src,String dest) throws IOException{
long t = System.currentTimeMillis();
File file = new File(src);
try(
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(new File(dest));
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
){
byte[] buffer = new byte[1024];
int byteRead = 0;
while((byteRead = bis.read(buffer)) != -1){
bos.write(buffer,0,byteRead);// 1
//bos.write(buffer); // 2
// 1和2的區(qū)別是,最后一次讀取的數(shù)據(jù)可能不能放滿(mǎn)buffer挚赊,那樣上一次留存的數(shù)據(jù)就會(huì)
// 被一起寫(xiě)出诡壁,可以通過(guò)定義一個(gè)15長(zhǎng)度的byte數(shù)組,寫(xiě)出時(shí)使用長(zhǎng)度10的byte數(shù)組荠割,就能看到結(jié)果了
}
}
System.err.println("fileCopyWithBufferStreamAndBuffer use time="+(System.currentTimeMillis()-t)+" file.size="+(file.length()/1024/1024)+"MB");
}
控制臺(tái)輸出:
fileCopyWithBufferStreamAndBuffer use time=99 file.size=20MB
可見(jiàn)使用buffer進(jìn)行批量讀寫(xiě)后妹卿,性能有了質(zhì)的提高.
下面我們來(lái)調(diào)整buffer數(shù)組的長(zhǎng)度為10240,看下輸出:
fileCopyWithBufferStreamAndBuffer use time=46 file.size=20MB
經(jīng)過(guò)多次嘗試蔑鹦,發(fā)現(xiàn)適當(dāng)?shù)脑黾觔uffer的長(zhǎng)度夺克,可以明顯提升處理速度。
五嚎朽、FileChannel
public static void fileCopyWithChannel(String src,String dest) throws IOException{
long t = System.currentTimeMillis();
File file = new File(src);
try(
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(new File(dest));
FileChannel in = fis.getChannel();
FileChannel out = fos.getChannel();
){
in.transferTo(0, in.size(), out);
}
System.err.println("fileCopyWithChannel use time="+(System.currentTimeMillis()-t)+" file.size="+(file.length()/1024/1024)+"MB");
}
控制臺(tái)輸出:
fileCopyWithChannel use time=85 file.size=20MB
從輸出結(jié)果看铺纽,單純的copy文件上,使用Channel并無(wú)明顯的性能優(yōu)勢(shì)哟忍。
ps:transferTo方法比較耗費(fèi)內(nèi)存資源狡门,建議只在小文件或小使用量時(shí)使用,避免出現(xiàn)資源不足等異常锅很。
NIO的關(guān)鍵點(diǎn)是通道和緩沖區(qū)其馏,這里不再過(guò)多展開(kāi),NIO入門(mén)推薦Java NIO 系列教程爆安。
六叛复、FileChannel(ByteBuffer)
public static void nioBufferCopy(String src,String dest) throws IOException {
long t = System.currentTimeMillis();
File file = new File(src);
try(
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(new File(dest));
FileChannel in = fis.getChannel();
FileChannel out = fos.getChannel();
){
ByteBuffer buffer = ByteBuffer.allocate(4096);
while (in.read(buffer) != -1) {
buffer.flip();
out.write(buffer);
buffer.clear();
}
}
System.err.println("nioBufferCopy use time="+(System.currentTimeMillis()-t)+" file.size="+(file.length()/1024/1024)+"MB");
}
控制臺(tái)輸出:
nioBufferCopy use time=182 file.size=20MB
下面我們來(lái)調(diào)整buffer數(shù)組的長(zhǎng)度為40960,看下輸出:
nioBufferCopy use time=60 file.size=20MB
七鹏控、FileChannel(MappedByteBuffer)
public static void nioMappedByteBufferCopy(String src,String dest) throws IOException {
long t = System.currentTimeMillis();
File file = new File(src);
try(
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(new File(dest));
FileChannel in = fis.getChannel();
FileChannel out = fos.getChannel();
){
MappedByteBuffer mappedByteBuffer = in.map(MapMode.READ_ONLY, 0, in.size());
out.write(mappedByteBuffer);
}
System.err.println("nioMappedByteBufferCopy use time="+(System.currentTimeMillis()-t)+" file.size="+(file.length()/1024/1024)+"MB");
}
控制臺(tái)輸出:
nioMappedByteBufferCopy use time=50 file.size=20MB
使用內(nèi)存映射文件的方式速度還是比較理想的致扯,更多內(nèi)容推薦閱讀深入淺出MappedByteBuffer,另外,推薦關(guān)注占小狼当辐,他寫(xiě)的精品文章很多抖僵!
八、Buffered(Reader)Writer
字符流的使用相對(duì)簡(jiǎn)單缘揪,這里給出一個(gè)示例代碼耍群。
public static void bufferReaderWriterTest() throws IOException{
List<String> list = new ArrayList<String>(20);
for(int i=0;i<20;i++){
list.add(""+i);
}
File file = new File("brwtest.txt");
try(
BufferedWriter bw = new BufferedWriter(new FileWriter(file));
BufferedReader br = new BufferedReader(new FileReader(file));
){
for (String item : list) {
bw.write(item);
bw.newLine();
}
//此處不調(diào)用flush方法,下邊讀取時(shí)內(nèi)容會(huì)為空
bw.flush();
String line = null;
while ((line = br.readLine()) != null) {
System.err.println(line);
}
//org.apache.commons.io.IOUtils找筝,有許多工具包內(nèi)提供了IO操作方法蹈垢,可以?xún)?yōu)先考慮使用
// List<String> lines = IOUtils.readLines(br);
// for (String string : lines) {
// System.err.println(string);
// }
}
}
小結(jié)
我個(gè)人寫(xiě)這篇文章或者說(shuō)寫(xiě)這些demo代碼的收獲有:
- 自以為很熟悉的I/O類(lèi),動(dòng)起手來(lái)才發(fā)現(xiàn)自己離“順手拈來(lái)”還有一段不小的差距袖裕;
- 從源碼層面了解不同流的實(shí)現(xiàn)方式曹抬,更深一層的提高了自己對(duì)java I/O知識(shí)的理解;
- 發(fā)現(xiàn)了過(guò)往所學(xué)知識(shí)的弱點(diǎn)急鳄,看別人博客進(jìn)行學(xué)習(xí)谤民,大致知其然堰酿,未系統(tǒng)梳理,未動(dòng)手練習(xí)张足,未理解其實(shí)現(xiàn)方式和原理触创,浮于表面,隨著時(shí)間的流逝为牍,這些知識(shí)成了零散模糊的片段哼绑。
I/O是java中基礎(chǔ)的知識(shí)點(diǎn),你不妨也閉起眼思考下你能使用多少種不同的方式實(shí)現(xiàn)文件復(fù)制碉咆,或許你會(huì)有不同的發(fā)現(xiàn)抖韩!
ps:本機(jī)mac air,測(cè)試代碼有其隨意性疫铜,copy速度僅能體現(xiàn)量級(jí)上的趨勢(shì)帽蝶,以供參考。