摘要:文章介紹了I/O在計(jì)算機(jī)系統(tǒng)中的重要性,指出Java語(yǔ)言早期的Stream式I/O模型存在性能問(wèn)題,引出NIO模型及其兩個(gè)關(guān)鍵接口Channel和Buffer,介紹了它們的創(chuàng)建和使用方法,并從源代碼的角度給出其具體說(shuō)明。
關(guān)鍵詞:輸入輸出 NIO 塊操作 Channel Buffer
0 引言
I/O是“輸入輸出”的英文縮寫(xiě),指系統(tǒng)間數(shù)據(jù)交換的過(guò)程。I/O系統(tǒng)是計(jì)算機(jī)系統(tǒng)的重要組成部分,承擔(dān)著計(jì)算機(jī)系統(tǒng)內(nèi)部之間或與外部溝通的橋梁。由于其重要性,現(xiàn)代操作系統(tǒng)均內(nèi)置了I/O操作的底層實(shí)現(xiàn),軟件開(kāi)發(fā)人員的任務(wù)是設(shè)計(jì)具體的調(diào)用形式和過(guò)程。
在早期的Java語(yǔ)言中,I/O操作主要依靠Stream模型實(shí)現(xiàn),類(lèi)庫(kù)中包含了一些特色鮮明的Stream類(lèi)供開(kāi)發(fā)人員反復(fù)利用。該模型將I/O看作字節(jié)的單列順序移動(dòng),這種思想便于軟件開(kāi)發(fā)人員學(xué)習(xí)和使用,能夠方便地建立編程框架,編寫(xiě)出簡(jiǎn)潔而優(yōu)美的代碼。但是,有一個(gè)不容忽視的問(wèn)題——在數(shù)據(jù)密集型應(yīng)用中,通常不能滿(mǎn)足性能需求,這是因?yàn)镾tream模型每次只讀取一個(gè)字節(jié)。當(dāng)時(shí),開(kāi)發(fā)人員通過(guò)編寫(xiě)Native代碼繞過(guò)Stream模型,缺點(diǎn)是付出安全性和可移植性等方面的代價(jià)。
為了解決上述問(wèn)題,Java語(yǔ)言引入了NIO,它以標(biāo)準(zhǔn)Java代碼的方式彌補(bǔ)了Stream模型的不足,并提供了一些新的功能。
1 NIO簡(jiǎn)介
NIO是“新的輸入輸出”的英文縮寫(xiě),通過(guò)利用操作系統(tǒng)的底層優(yōu)化,以塊的方式實(shí)現(xiàn)高速的I/O操作。它為所有的原始類(lèi)型提供Buffer支持、字符集編碼解碼解決方案、鎖功能、內(nèi)存映射文件以及多路非阻塞式網(wǎng)絡(luò)I/O 。NIO的核心是Channel和Buffer兩個(gè)接口,幾乎在每個(gè)I/O操作中都要使用它們。
2 Buffer與Channel
在NIO中,所有數(shù)據(jù)都是緩沖處理的,Buffer接口充當(dāng)著容器的角色。本質(zhì)上,Buffer實(shí)現(xiàn)類(lèi)包裝一個(gè)特定長(zhǎng)度的基本數(shù)據(jù)類(lèi)型數(shù)組,提供高級(jí)的訪問(wèn)方法,跟蹤讀、寫(xiě)進(jìn)程。在I/O操作時(shí),數(shù)據(jù)首先進(jìn)入Buffer對(duì)象,然后從Buffer對(duì)象中讀取或者向其中寫(xiě)入。
Channel的角色類(lèi)似于Stream,但前者有兩個(gè)明顯優(yōu)勢(shì),其一是可以一次讀取或?qū)懭胍粋€(gè)Buffer的數(shù)據(jù)而非一個(gè)Byte;其二是Channel是雙向的,既可以讀,也可以寫(xiě),甚至同時(shí)讀和寫(xiě),這更符合一些操作系統(tǒng)的底層實(shí)現(xiàn)。
3 輸入數(shù)據(jù)
這里以讀入文件數(shù)據(jù)為例說(shuō)明NIO輸入功能的使用方法,分為三個(gè)簡(jiǎn)單步驟。第一步,獲取FileChannel:
FileInputStream fis = new FileInputStream( \"foo\" );
FileChannel fc = fis.getChannel();
第二步,創(chuàng)建一個(gè)緩沖區(qū),緩沖區(qū)的類(lèi)型和大小依實(shí)際需求而定,這里創(chuàng)建一個(gè)可容納1024字節(jié)的緩沖區(qū):
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
第三步,使用Channel將數(shù)據(jù)讀入到Buffer中:
fc.read( buffer );
在這個(gè)過(guò)程中,不需要告訴Channel需要讀多少數(shù)據(jù),Buffer內(nèi)部機(jī)制會(huì)決定待讀取的數(shù)據(jù)量。
通過(guò)以上三步,文件中的數(shù)據(jù)已經(jīng)以字節(jié)的形式存儲(chǔ)在Buffer中,可以使用get方法取出并做后續(xù)處理。
4 輸出數(shù)據(jù)
與輸入數(shù)據(jù)類(lèi)似,這里同樣以文件數(shù)據(jù)的形式說(shuō)明NIO輸出功能的使用方法,分為三個(gè)簡(jiǎn)單步驟。第一步,獲取FileChannel:
FileOutputStream fos = new FileOutputStream( \"foo\" );
FileChannel fc = fos.getChannel();
第二步,創(chuàng)建一個(gè)緩沖區(qū),緩沖區(qū)的類(lèi)型和大小依實(shí)際需求而定,這里創(chuàng)建一個(gè)可容納1024字節(jié)的緩沖區(qū),然后向其中填入待寫(xiě)入的數(shù)據(jù):
其中,put方法與上文get方法相對(duì),用于向Buffer中添加數(shù)據(jù),新的flip方法設(shè)置Buffer的內(nèi)部狀態(tài),通知Buffer準(zhǔn)備好即將進(jìn)行的輸出操作。
第三步,使用Channel將從Buffer中寫(xiě)出:
fc.write( buffer );
如果Buffer中的數(shù)據(jù)小于創(chuàng)建時(shí)的長(zhǎng)度,寫(xiě)出操作會(huì)根據(jù)實(shí)際長(zhǎng)度大小進(jìn)行調(diào)整。
5 輸入輸出結(jié)合應(yīng)用
這里給出一個(gè)數(shù)據(jù)中轉(zhuǎn)的例子,用以說(shuō)明如何將一個(gè)Buffer實(shí)例同時(shí)應(yīng)用到讀和寫(xiě)的過(guò)程中。
其中,如果read方法的返回值為-1,表明數(shù)據(jù)已經(jīng)讀完;新的clear方法與flip方法類(lèi)似,重設(shè)Buffer內(nèi)部狀態(tài),使它準(zhǔn)備接收新的數(shù)據(jù)。
6 總結(jié)
以上示例中沒(méi)有包含異常處理的代碼,目的在于向讀者清晰明了地說(shuō)明NIO的使用方法,從中可以看出,NIO并沒(méi)有明顯增加Java語(yǔ)言程序的代碼量,與Native代碼方式比較,在滿(mǎn)足安全性和可移植性要求的情況下,提供了相似的性能,降低了后期維護(hù)成本,不失為一種優(yōu)秀的I/O模型,應(yīng)該被推廣使用。
參考文獻(xiàn):
[1]李曉華.深入Java編程技術(shù)講座之六-Java輸入輸出.北京:電腦編程技巧與維護(hù)[J].1999,3.
[2]封瑋,周世平.基于Java NIO的非阻塞通信的研究與實(shí)現(xiàn)。北京:計(jì)算機(jī)系統(tǒng)應(yīng)用[J].2004,9.
[3]姜力.基于Java NIO反應(yīng)器模式設(shè)計(jì)與實(shí)現(xiàn).大慶:大慶師范學(xué)院學(xué)報(bào)[N].2008,3.
[4]曾自強(qiáng).基于NIO的java高性能網(wǎng)絡(luò)應(yīng)用的技術(shù)研究[J].北京:北京郵電大學(xué).2009,2.
[5]張雷.基于JAVA技術(shù)的輸入輸出流系統(tǒng)應(yīng)用性分類(lèi)研究.重慶:自動(dòng)化與儀器儀表[J].2011,4.
[6]蔡天鳴,王若愚.Java SE7平臺(tái)下的NIO.2探析.武漢:軟件導(dǎo)刊[J].2011,8.