基本概念描述
1.1 NIO
NIO即New IO,在KJDK1.4中引入复唤,IO和NIO具有相同的作用和目的站欺,但I(xiàn)O用到的是流,而NIO用到的是塊夸溶,所以NIO的效率要比IO高。
在JAVA API中提供了兩套NIO凶硅,分別是標(biāo)準(zhǔn)輸入輸出NIO缝裁,以及網(wǎng)絡(luò)編程N(yùn)IO。
Java NIO是同步非阻塞通信足绅。舉個(gè)例子就是叫一個(gè)線程不停地觀察IO操作所處的狀態(tài)捷绑,根據(jù)狀態(tài)去處理。同步的原因是它的read/write/accept方法的內(nèi)核IO操作都會(huì)阻塞當(dāng)前線程氢妈。
1.2 流和塊的比較
IO是以流的方式處理數(shù)據(jù)粹污,而NIO是以塊的方式處理數(shù)據(jù)。
面向流的IO一次一個(gè)字節(jié)的處理數(shù)據(jù)首量,一個(gè)輸入流產(chǎn)生一個(gè)字節(jié)壮吩,一個(gè)輸出流消費(fèi)一個(gè)字節(jié)进苍,有利于使用過濾器,但是面向流的IO通常處理得很慢鸭叙。
面向塊的IO以塊的方式處理數(shù)據(jù)觉啊,每一個(gè)操作都在一步中產(chǎn)生或者消費(fèi)一個(gè)數(shù)據(jù)塊。所以面向塊的IO通常處理得很快沈贝。
NIO基礎(chǔ)
Buffer和Channel是標(biāo)準(zhǔn)NIO中的核心對(duì)象杠人,Selector是網(wǎng)絡(luò)NIO的核心對(duì)象。
Channel是對(duì)原IO中流的模擬宋下,任何來源和目的數(shù)據(jù)都必須通過一個(gè)Channel對(duì)象嗡善,一個(gè)Buffer實(shí)質(zhì)上是一個(gè)容器對(duì)象,發(fā)給Channel的所有對(duì)象都必須先放到Buffer中学歧;同樣的罩引,從Channel中讀取的任何數(shù)據(jù)都要讀到Buffer中。
關(guān)于Buffer
Buffer是一個(gè)對(duì)象撩满,它包含一些要寫入的或讀出的數(shù)據(jù)蜒程,在NIO中,數(shù)據(jù)是放入Buffer對(duì)象的伺帘,而在IO中昭躺,數(shù)據(jù)是直接寫入或者讀到Stream對(duì)象的,應(yīng)用程序不能直接對(duì)Channel對(duì)象進(jìn)行讀寫操作伪嫁,而必須通過Buffer來進(jìn)行领炫,即Channel是通過Buffer來讀寫數(shù)據(jù)的。
在NIO中张咳,所有的數(shù)據(jù)都是用Buffer處理的帝洪,他是NIO讀寫數(shù)據(jù)的中轉(zhuǎn)池。Buffer實(shí)質(zhì)上是一個(gè)數(shù)組脚猾,通常是一個(gè)字節(jié)數(shù)據(jù)葱峡,但也可以是其他類型的數(shù)組。
使用Buffer讀寫數(shù)據(jù)一般遵循四個(gè)步驟:
- 寫入數(shù)據(jù)到Buffer
- 調(diào)用flip()方法
- 從Buffer中讀取數(shù)據(jù)
- 調(diào)用clear()方法或者compact()方法
當(dāng)向Buffer寫入數(shù)據(jù)時(shí)龙助,Buffer會(huì)記錄寫了多少數(shù)據(jù)砰奕。一旦要讀取數(shù)據(jù),需要通過flip()方法將Buffer從寫模式切換到讀模式提鸟。在讀模式下军援,可以讀取之前寫入到Buffer中的所有數(shù)據(jù)。
讀完了數(shù)據(jù)之后称勋,可以采用clear()或者compact()方法清空整個(gè)緩沖區(qū)胸哥。clear()會(huì)清空整個(gè)緩沖區(qū)。compact()方法只會(huì)清除已經(jīng)讀過的數(shù)據(jù)赡鲜,任何未讀的數(shù)據(jù)都被移到緩沖區(qū)的起始處空厌,新寫入的數(shù)據(jù)將被放到緩沖區(qū)未讀數(shù)據(jù)的后面庐船。
關(guān)于Channel
Channel是一個(gè)對(duì)象,可以通過它讀取和寫入數(shù)據(jù)嘲更,可以看作IO中的流醉鳖。
- Channel是雙向的,既可以讀又可以寫哮内,而流是單向的
- Channel可以進(jìn)行異步的讀寫
- 對(duì)Channel的讀寫必須通過buffer對(duì)象
在Java NIO中,Channel主要有以下幾種類型:
- FileChannel: 從文件中讀取數(shù)據(jù)的
- DatagramChannel:讀取UDP網(wǎng)絡(luò)協(xié)議數(shù)據(jù)
- SocketChannel:讀寫TCP網(wǎng)絡(luò)協(xié)議數(shù)據(jù)
- ServerSocketChannel:可以監(jiān)聽TCP連接
NIO的讀寫
從文件中讀取
在NIO系統(tǒng)中壮韭,任何時(shí)候執(zhí)行一個(gè)操作北发,都是從Channel讀取數(shù)據(jù)到Buffer
從文件讀取數(shù)據(jù)的步驟:
- 從FileInputStream獲取Channel
- 創(chuàng)建Buffer
- 從Channel讀取數(shù)據(jù)到Buffer
具體的實(shí)現(xiàn)過程
第一步: 獲取通道
FileInputStream fin = new FileInputStream("...");
FileChannel fc = fin.getChannel();
第二步:創(chuàng)建緩沖區(qū)
ByteBuffer buffer = ByteBuffer.allocate(1024);
第三步: 將數(shù)據(jù)從通道寫入緩沖區(qū)
fc.read(buffer);
寫入數(shù)據(jù)到文件
第一步: 獲取一個(gè)通道
FileOutputStream fout = new FileOutputStream("...");
FileChannel fc = fout.getChannel();
第二步:創(chuàng)建緩沖區(qū),將數(shù)據(jù)放入緩沖區(qū)
ByteBuffer buffer = ByteBuffer.allocate(1024);
for(int i = 0; i < message.length; i++){
buffer.put(message[i]);
}
buffer.flip();
第三步:把緩沖區(qū)數(shù)據(jù)寫入通道中
fc.write(buffer);
注意點(diǎn): InputStream輸入流喷屋,用于讀入數(shù)據(jù)
OutputStream輸出流琳拨,用于寫出數(shù)據(jù)。
讀寫結(jié)合
public static void copyFileWithNIO(String src, String dst) throws IOException {
FileInputStream fin = new FileInputStream(new File(src));
FileOutputStream fout = new FileOutputStream(new File(dst));
FileChannel finChannel = fin.getChannel();
FileChannel foutChannel = fout.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
int eof = finChannel.read(buffer);
if (eof == -1) {
break;
}
// 重新設(shè)置一下buffer的position=0屯曹, limit=position
buffer.flip();
// 開始寫
foutChannel.write(buffer);
// 寫完要重置buffer,重新設(shè)置position=0, limit=capacity
buffer.clear();
}
finChannel.close();
foutChannel.close();
fin.close();
fout.close();
}
需要注意的點(diǎn)
4.1檢查狀態(tài)
當(dāng)沒有更多的數(shù)據(jù)時(shí)狱庇,拷貝就算完成,此時(shí)恶耽,read()方法會(huì)返回-1,用該方法判斷是否讀完密任。
int r = fin.read(buffer);
if(r == -1) {
System.out.println("文件讀取結(jié)束");
break;
}
4.2 Buffer類的flip偷俭、clear方法
控制buffer狀態(tài)的三個(gè)變量
- position:跟蹤已經(jīng)寫了多少數(shù)據(jù)或者讀了多少數(shù)據(jù)浪讳,它指向的是下一個(gè)字節(jié)來自哪個(gè)位置
- limit:代表還有多少數(shù)據(jù)可以取出,或者還有多少空間可以寫入涌萤,它的值小于等于capacity
- capacity:代表緩沖區(qū)的最大容量淹遵,一般新建一個(gè)緩沖區(qū)的時(shí)候,limit的值和capacity的值是相等的
flip()负溪、clear()用于設(shè)置這些值
flip()方法的源碼
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
flip()方法把當(dāng)前的指針位置position設(shè)置成了limit透揣,再將當(dāng)前指針position指向數(shù)據(jù)的最開始端,然后就可以將數(shù)據(jù)從緩沖區(qū)寫入通道川抡。position被設(shè)置為0,意味著我們得到的下一個(gè)字節(jié)是第一個(gè)字節(jié)辐真。limit已被設(shè)置為原來的position,這意味著它包括以前讀到的所有字節(jié)猖腕,并且一個(gè)字節(jié)也不少拆祈。
clear()方法的源碼
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
clear()方法重新設(shè)置了緩沖區(qū)以便接收更多的字節(jié)。
原文出處:http://blog.csdn.net/suifeng3051/article/details/48160753