調(diào)用Java NIO提高文件讀寫(xiě)速度
Java NIO的出現(xiàn)旨在提高文件的讀寫(xiě)速度,當(dāng)然IO用NIO重新實(shí)過(guò),所以我們不用顯示的調(diào)用NIO也能享受這種高效的文件讀寫(xiě)。
Java NIO的高效得益于其兩大"助手":Channel(管道)和Buffer(緩沖器)。當(dāng)然這兩個(gè)"得力助手"的"年齡"遠(yuǎn)遠(yuǎn)比java大!力求簡(jiǎn)單易懂的把知識(shí)講解給大家,我舉一個(gè)例子來(lái)說(shuō)明一下這"兩元大將"是如何在java NIO中配合工作的。
中國(guó)古代有一種傳統(tǒng)的吸煙器具---水煙袋。我想用這個(gè)東西來(lái)模擬一下Channel和Buffer的工作原理。不求說(shuō)的好,力求準(zhǔn)確無(wú)誤。
分析一下水煙袋是如何工作的:
***步,準(zhǔn)備工作,準(zhǔn)備好上等煙絲;第二步,將"水斗"中裝入適量的水,煙倉(cāng)中裝滿煙絲并插入水斗中,然后再將煙管插入水斗中;第三步,點(diǎn)燃煙絲并吸氣。香煙從煙倉(cāng)產(chǎn)生,經(jīng)過(guò)水的過(guò)濾進(jìn)入水上的空閑區(qū)。第四步,享受吸煙的快感.....從這個(gè)例子中我們提取出主要對(duì)象"煙",來(lái)分析一下它的運(yùn)動(dòng)軌跡。煙倉(cāng)把煙生產(chǎn)出來(lái),經(jīng)過(guò)水的過(guò)濾飄到水上面的空閑區(qū)域,然后通過(guò)煙管進(jìn)入人的體內(nèi)。
如果上面的過(guò)程大家理解了,明白了,那么java NIO你已經(jīng)了解了50%,至少你已經(jīng)知道它的工作原理了。因?yàn)橛肗IO處理的數(shù)據(jù)和用水煙袋中吸煙很相似。我們分析一下NIO的工作原理,非常簡(jiǎn)單。
當(dāng)然和吸煙一樣我們首先必須有要用NIO來(lái)處理需求的欲望(這好比你想要吸煙了),比方說(shuō)我想要將C盤下面的wk.txt文件進(jìn)行備份,備份文件的名稱為wk-bak.txt。類比剛剛吸煙的那個(gè)過(guò)程:
步驟一:準(zhǔn)備工作,確定文件的位置,并將程序不可直接操作的文件轉(zhuǎn)換成字符流的形式(這一步和上邊吸煙實(shí)例的***步?jīng)]有什么差別,只是進(jìn)行一些簡(jiǎn)單的準(zhǔn)備工作)。
- String inFile = "C:\\wk.txt";
- String outFile = "C:\\wk-bak.txt";
- FileInputStream inf = new FileInputStream(inFile);
- FileOutputStream outf = new FileOutputStream(outFile);
- ByteBuffer buffer = ByteBuffer.allocate(1024);
步驟二:創(chuàng)建文件輸入管道,和文件輸出管道。(這一步與上邊吸煙的第二部稍有差別,因?yàn)镃hannel和Buffer是在讀寫(xiě)的時(shí)候才發(fā)生的"連接"動(dòng)作)
- //準(zhǔn)備文件讀取的管道-->相當(dāng)于煙倉(cāng)和煙管FileChannel inFc = inf.getChannel();
- FileChannel outFc = outf.getChannel();
- Charset charSet = Charset.forName("utf-8");
- //進(jìn)行編碼解碼-->相當(dāng)于水斗中水的過(guò)濾作用
- CharsetDecoder decoder = charSet.newDecoder();
- CharsetEncoder encoder = charSet.newEncoder();
步驟三:開(kāi)始進(jìn)行文件備份工作。
- while(true) {
- //準(zhǔn)備向Buffer中寫(xiě)入數(shù)據(jù)-->相當(dāng)于點(diǎn)燃煙絲,完事具備只欠東風(fēng)
- buffer.clear();
- //進(jìn)行字符編碼 -->相當(dāng)于水的過(guò)濾作用
- CharBuffer cb = decoder.decode(buffer);
- ByteBuffer bb = encoder.encode(cb);
- //數(shù)據(jù)經(jīng)過(guò)編碼以后暫存緩沖區(qū)-->相當(dāng)于經(jīng)過(guò)水過(guò)濾后的煙暫停在水斗中
- int t = inFc.read(bb);
- if(t == -1) {
- break;
- }
- bb.flip();
- //將字節(jié)碼寫(xiě)入目標(biāo)文件-->相當(dāng)于煙已經(jīng)進(jìn)入到嘴里
- outFc.write(bb);
- }
步驟四:檢查文件是否備份成功。發(fā)現(xiàn)C盤下面多了一個(gè)wk-bak.txt的文件,內(nèi)容與wk.txt一摸一樣。接下來(lái)享受java帶給你的快感....
上面的例子估計(jì)大家已經(jīng)理解的差不多了,當(dāng)然如果深究也會(huì)有一些不太妥當(dāng)?shù)牡胤?,但是不要較真,目的是學(xué)習(xí)NIO,并不是吸煙。如果感覺(jué)你可以了那么就請(qǐng)把上面的例子補(bǔ)充完整,運(yùn)行一下,享受一下NIO的威武(當(dāng)然字符編碼并不是必須的,只是讓這個(gè)例子顯得完整一點(diǎn))。
#p#
好吧如果你理解了上面的東西,并且真正的補(bǔ)全了文件備份的小程序,那么就來(lái)進(jìn)行稍微深入一點(diǎn)的學(xué)習(xí)吧。
上文我提到了舉吸煙的例子是有欠妥當(dāng)?shù)?,其中之一就是Buffer的內(nèi)部機(jī)制和"水斗"簡(jiǎn)單的過(guò)濾功能是不一樣的。還有字符編碼那一塊也不是在Buffer內(nèi)部實(shí)現(xiàn)的東西,decoder和encoder是針對(duì)Buffer的兩個(gè)工具。那我們接下來(lái)分析一下Buffer內(nèi)部機(jī)制到底不一樣在哪里呢(主要分析常用的兩個(gè)方法;clear(),flip())?
來(lái)吧,打開(kāi)Buffer的源碼(摘取有用的部分):
- public abstract class Buffer {
- // Invariants: mark <= position <= limit <= capacity
- private int mark = -1;
- private int position = 0;
- private int limit;
- private int capacity;
- public final Buffer clear() {
- position = 0;
- limit = capacity;
- mark = -1;
- return this;
- }
- public final Buffer flip() {
- limit = position;
- position = 0;
- mark = -1;
- return this;
- }
首先我們要明確一點(diǎn),所謂的緩沖器僅僅是一個(gè)"多功能"的數(shù)組??赡茉谶@個(gè)Buffer類中沒(méi)有體現(xiàn),但是如果我們打開(kāi)ByteBuffer的源碼會(huì)有byte[]的數(shù)組,打開(kāi)CharBuffer的源碼會(huì)有char[]的數(shù)組。因?yàn)锽uffer是所有緩沖器的父類,所以他它不能預(yù)計(jì)會(huì)有多少種緩沖器,所以索性讓"兒子"們自己實(shí)現(xiàn)去吧。
既然知道了緩沖器是一個(gè)"多功能的數(shù)組",那么我們用畫(huà)圖的形式來(lái)分析一下上面Buffer的源碼。
假設(shè)我們定義了一個(gè)8個(gè)單位大的緩沖區(qū),如上圖(其實(shí)Buffer也就是這么一個(gè)東西)。首先告訴大家那三個(gè)重要的關(guān)于緩沖區(qū)狀態(tài)的的屬性:
capacity:緩沖區(qū)的容量;
limit:緩沖區(qū)還有多少數(shù)據(jù)能夠取出或者緩沖區(qū)還有多少容量用于存放數(shù)據(jù);
position:相當(dāng)于一個(gè)游標(biāo)(cursor),記錄我們從哪里開(kāi)始寫(xiě)數(shù)據(jù),從哪里開(kāi)始讀數(shù)據(jù)。
剛還說(shuō)到flip()和clear()是Buffer的兩個(gè)重要的方法,因?yàn)樗鼈儍蓚€(gè)方法決定了緩沖是否能正常的進(jìn)行讀寫(xiě)工作。
當(dāng)我們要想從緩沖區(qū)中寫(xiě)數(shù)據(jù)的時(shí)候必須先執(zhí)行flip()方法,當(dāng)我們要想從緩沖區(qū)中讀數(shù)據(jù)時(shí)必須先執(zhí)行clear()方法。
***次向Buffer中寫(xiě)入數(shù)據(jù)時(shí),執(zhí)行一次flip()方法以后,Buffer的結(jié)構(gòu)變成了這樣:position指向了***個(gè)可以存取數(shù)據(jù)的0號(hào)位,limit和capacity同時(shí)指向***位。
假如***次我們向Buffer中寫(xiě)入了3單位的數(shù)據(jù),我們?cè)俅螆?zhí)行flip()方法則Buffer的結(jié)構(gòu)會(huì)變成上圖的所示。但是經(jīng)過(guò)flip()的改造后position總是指向Buffer中***個(gè)可用的位置。那么,未執(zhí)行flip()方法以前position在哪里呢?很簡(jiǎn)單,指向***一個(gè)數(shù)據(jù)的位置。
當(dāng)我們想要從Buffer中讀取數(shù)據(jù)時(shí),執(zhí)行clear()方法,Buffer的內(nèi)部結(jié)構(gòu)變成了上圖所示,position指向了可讀數(shù)據(jù)的首位,limit指向了原來(lái)position的位置。
從上面的幾幅圖中我們看出:capacity代表了Buffer的容量是不變的,limit與position的差總是表示Buffer總可以讀的數(shù)據(jù),或者Buffer中可以寫(xiě)數(shù)據(jù)的容量。還有position總是小于等于limit,limit總是小于等于capacity。
其實(shí)到這里我們已經(jīng)發(fā)現(xiàn),NIO并不像IO那么復(fù)雜,因?yàn)镮O 中的Decorator模式和Adaptor模式確實(shí)讓我們一時(shí)間摸不到頭腦,但是熟悉了會(huì)感覺(jué)到IO的設(shè)計(jì)之精美。
NIO中還有一個(gè)知識(shí)點(diǎn)就是無(wú)阻塞的Socket編程,這里就不說(shuō)了,因?yàn)楸容^復(fù)雜,但是如果我們真正理解了Selector這個(gè)調(diào)度者的工作,那么無(wú)阻塞的實(shí)現(xiàn)機(jī)制我們差不多就掌握了,復(fù)雜也就是編碼上面的事了。
原文鏈接:http://www.cnblogs.com/focusj/archive/2011/11/03/2231583.html
編輯推薦:
- 從過(guò)去5年編程語(yǔ)言的演化看未來(lái)趨勢(shì)
- 什么是JavaScript異步編程
- 多核平臺(tái)下的Java優(yōu)化
- 詳細(xì)介紹Java中的堆和棧
- 談java中類的加載、鏈接和初始化