探索Java NIO的歷程
前段時(shí)間有些時(shí)間,打算看看NIO的東西,本來以為很快可以了解的東西,卻用了很多時(shí)間。
首先Goole NIO可以看到很多的教程,非阻塞,Buffer,內(nèi)存映射,塊讀取前三個(gè)很快就有所了解
嘗試著寫了些小程序,學(xué)習(xí)東西的時(shí)候總喜歡寫點(diǎn)小例子。
唯獨(dú)塊讀取沒有找到對(duì)應(yīng)的東西。(在過程中,主要看了IBM 的NIO入門)
首先,IBM NIO入門中的語句
--------------------------------------------------------------------------------
原來的 I/O 庫(在 java.io.*中) 與 NIO 最重要的區(qū)別是數(shù)據(jù)打包和傳輸?shù)姆绞健U缜懊嫣岬降模?br />原來的 I/O 以流的方式處理數(shù)據(jù),而 NIO 以塊的方式處理數(shù)據(jù)。 面向流 的 I/O 系統(tǒng)一次一個(gè)字節(jié)地處
理數(shù)據(jù)。一個(gè)輸入流產(chǎn)生一個(gè)字節(jié)的數(shù)據(jù),一個(gè)輸出流消費(fèi)一個(gè)字節(jié)的數(shù)據(jù)。為流式數(shù)據(jù)創(chuàng)建過濾器非常容易。鏈接幾個(gè)過濾器,以便每個(gè)過濾器只負(fù)責(zé)單個(gè)復(fù)雜處理機(jī)制的一部分,這樣也是相對(duì)簡(jiǎn)單的。不利的一面是,面向流的 I/O 通常相當(dāng)慢。 一個(gè) 面向塊 的 I/O 系統(tǒng)以塊的形式處理數(shù)據(jù)。每一個(gè)操作都在一步中產(chǎn)生或者消費(fèi)一個(gè)數(shù)據(jù)塊。按塊處理數(shù)據(jù)比按(流式的)字節(jié)處理數(shù)據(jù)要快得多。但是面向塊的 I/O 缺少一些面向流的I/O 所具有的優(yōu)雅性和簡(jiǎn)單性。
--------------------------------------------------------------------------------
首先簡(jiǎn)單的印象是NIO快,所以想寫個(gè)程序驗(yàn)證一下.如下復(fù)制:
?
?1
public
?
static
?
void
?test2(String?name1,?String?name2)?
{
?2
????????
long
?start?
=
?System.currentTimeMillis();
?3
????????
try
?
{
?4
????????????FileInputStream?fis?
=
?
new
?FileInputStream(name1);
?5
????????????FileOutputStream?fos?
=
?
new
?FileOutputStream(name2);
?6
????????????
byte
[]?buf?
=
?
new
?
byte
[
8129
];
?7
????????????
while
?(
true
)?
{????????????????
?8
????????????????
int
?n?
=
?fis.read(buf);
?9
????????????????
if
?(n?
==
?
-
1
)?
{
10
????????????????????
break
;
11
????????????????}
12
????????????????fos.write(buf,
0
,n);
13
????????????}
14
????????????fis.close();
15
????????????fos.close();
16
????????}
?
catch
?(Exception?e)?
{
17
????????????e.printStackTrace();
18
????????}
19
????????
long
?end?
=
?System.currentTimeMillis();
20
????????
long
?time?
=
?end?
-
?start;
21
????????System.out.println(time);
22
????}
23
????
24
????
public
?
static
?
void
?test3(String?name1,?String?name2)?
{
25
????????
long
?start?
=
?System.currentTimeMillis();
26
????????
try
?
{
27
????????????FileInputStream?in?
=
?
new
?FileInputStream(name1);
28
????????????FileOutputStream?out?
=
?
new
?FileOutputStream(name2);
29
????????????FileChannel?fc1?
=
?in.getChannel();
30
????????????FileChannel?fc2?
=
?out.getChannel();
31
????????????ByteBuffer?bb?
=
?ByteBuffer.allocate(
8129
);
32
????????????
while
?(
true
)?
{
33
????????????????bb.clear();
34
????????????????
int
?n?
=
?fc1.read(bb);
35
????????????????
if
?(n?
==
?
-
1
)?
{
36
????????????????????
break
;
37
????????????????}
38
????????????????bb.flip();
39
????????????????fc2.write(bb);
40
????????????}
41
????????????fc1.close();
42
????????????fc2.close();
43
????????}
?
catch
?(IOException?e)?
{
44
45
????????}
46
????????
long
?end?
=
?System.currentTimeMillis();
47
????????
long
?time?
=
?end?
-
?start;
48
????????System.out.println(time);
49
????}
本以為可以結(jié)束,結(jié)果測(cè)試結(jié)果出乎意料,函數(shù)一比函數(shù)二要快,就是說Old IO快于NIO ,從此
?也就開始了整個(gè)過程:
?為了了解這個(gè)問題,仔細(xì)搜索并仔細(xì)再看IBM 的NIO教程,看到如下這段話
?---------------------------------------------
?在 JDK 1.4 中原來的 I/O 包和 NIO 已經(jīng)很好地集成了。 java.io.* 已經(jīng)以 NIO 為基礎(chǔ)重新實(shí)現(xiàn)了,
?所以現(xiàn)在它可以利用 NIO 的一些特性。例如, java.io.* 包中的一些類包含以塊的形式讀寫數(shù)據(jù)的方法,
?這使得即使在更面向流的系統(tǒng)中,處理速度也會(huì)更快。 也可以用 NIO 庫實(shí)現(xiàn)標(biāo)準(zhǔn) I/O 功能。例如,
?可以容易地使用塊 I/O 一次一個(gè)字節(jié)地移動(dòng)數(shù)據(jù)。但是正如您會(huì)看到的,NIO 還提供了原 I/O 包中所沒有的許多好處。
??? ---------------------------------------------
??? 所以我想,是否因?yàn)镮nputStream中使用了塊讀取實(shí)現(xiàn)了呢,所以進(jìn)入JDK1.4中的InputStream中
??? 看看source,首先引起我注意的是read函數(shù),當(dāng)參數(shù)是一個(gè)byte數(shù)組的時(shí)候,直接調(diào)用的native實(shí)現(xiàn)
??? 難道是這個(gè),為了驗(yàn)證,下載了一個(gè)JDK1.3下來,發(fā)現(xiàn)JDK1.3是一樣的。
???
??? 繼續(xù),我想是否是JVM底層實(shí)現(xiàn)了塊讀取呢,為了證明這個(gè)我用JDK1.3和JDK1.4同時(shí)實(shí)現(xiàn)了類似的函數(shù),??? 測(cè)試的結(jié)果再次出乎意料,性能相差不大.那就不是這個(gè)了。
? 為此多方查找資料,未果,為此多寫幾個(gè)函數(shù),好好測(cè)試一下IO的不同。于是有了如下的一些函數(shù)
??1
//
?exec
??2
????
public
?
static
?
void
?test1(String?name1,?String?name2)?
{
??3
????????
long
?start?
=
?System.currentTimeMillis();
??4
????????
try
?
{
??5
????????????String?cmd?
=
?
"
cmd?/c?copy?d:\\out1.txt?d:\\out2.txt
"
;
??6
????????????System.out.println(cmd);
??7
????????????Process?p?
=
?Runtime.getRuntime().exec(cmd);÷
??8
????????????p.waitFor();
??9
????????}
?
catch
?(Exception?e)?
{
?10
????????????e.printStackTrace();
?11
????????}
?12
????????
long
?end?
=
?System.currentTimeMillis();
?13
????????
long
?time?
=
?end?
-
?start;
?14
????????System.out.println(time);
?15
????}
?16
?17
????
//
?old?io
?18
????
public
?
static
?
void
?test2(String?name1,?String?name2)?
{
?19
????????
long
?start?
=
?System.currentTimeMillis();
?20
????????
try
?
{
?21
????????????FileInputStream?fis?
=
?
new
?FileInputStream(name1);
?22
????????????FileOutputStream?fos?
=
?
new
?FileOutputStream(name2);
?23
????????????
while
?(
true
)?
{
?24
????????????????
byte
[]?buf?
=
?
new
?
byte
[
8129
];
?25
????????????????
int
?n?
=
?fis.read(buf);
?26
????????????????
if
?(n?
==
?
-
1
)?
{
?27
????????????????????
break
;
?28
????????????????}
?29
????????????????fos.write(buf);
?30
????????????}
?31
????????????fis.close();
?32
????????????fos.close();
?33
????????}
?
catch
?(Exception?e)?
{
?34
????????????e.printStackTrace();
?35
????????}
?36
????????
long
?end?
=
?System.currentTimeMillis();
?37
????????
long
?time?
=
?end?
-
?start;
?38
????????System.out.println(time);
?39
????}
?40
?41
????
//
?new?io
?42
????
public
?
static
?
void
?test3(String?name1,?String?name2)?
{
?43
????????
long
?start?
=
?System.currentTimeMillis();
?44
????????
try
?
{
?45
????????????FileInputStream?in?
=
?
new
?FileInputStream(name1);
?46
????????????FileOutputStream?out?
=
?
new
?FileOutputStream(name2);
?47
????????????FileChannel?fc1?
=
?in.getChannel();
?48
????????????FileChannel?fc2?
=
?out.getChannel();
?49
????????????ByteBuffer?bb?
=
?ByteBuffer.allocate(
8129
);
?50
????????????
while
?(
true
)?
{
?51
????????????????bb.clear();
?52
????????????????
int
?n?
=
?fc1.read(bb);
?53
????????????????
if
?(n?
==
?
-
1
)?
{
?54
????????????????????
break
;
?55
????????????????}
?56
????????????????bb.flip();
?57
????????????????fc2.write(bb);
?58
????????????}
?59
????????????fc1.close();
?60
????????????fc2.close();
?61
????????}
?
catch
?(IOException?e)?
{
?62
?63
????????}
?64
????????
long
?end?
=
?System.currentTimeMillis();
?65
????????
long
?time?
=
?end?
-
?start;
?66
????????System.out.println(time);
?67
????}
?68
?69
????
//
?fast?copy
?70
????
public
?
static
?
void
?test4(String?name1,?String?name2)?
{
?71
????????
long
?start?
=
?System.currentTimeMillis();
?72
????????
try
?
{
?73
????????????FileInputStream?in?
=
?
new
?FileInputStream(name1);
?74
????????????;
?75
????????????FileOutputStream?out?
=
?
new
?FileOutputStream(name2);
?76
????????????;
?77
????????????FileChannel?fc1?
=
?in.getChannel();
?78
????????????FileChannel?fc2?
=
?out.getChannel();
?79
????????????ByteBuffer?bb?
=
?ByteBuffer.allocateDirect(
8129
);
?80
????????????
while
?(
true
)?
{
?81
????????????????bb.clear();
?82
????????????????
int
?n?
=
?fc1.read(bb);
?83
????????????????
if
?(n?
==
?
-
1
)?
{
?84
????????????????????
break
;
?85
????????????????}
?86
????????????????bb.flip();
?87
????????????????fc2.write(bb);
?88
????????????}
?89
????????????fc1.close();
?90
????????????fc2.close();
?91
????????}
?
catch
?(IOException?e)?
{
?92
?93
????????}
?94
????????
long
?end?
=
?System.currentTimeMillis();
?95
????????
long
?time?
=
?end?
-
?start;
?96
????????System.out.println(time);
?97
????}
?98
?99
????
//
?transfer?,read?and?write?at?same?time
100
????
public
?
static
?
void
?test5(String?name1,?String?name2)?
{
101
????????
long
?start?
=
?System.currentTimeMillis();
102
????????
try
?
{
103
????????????RandomAccessFile?raf1?
=
?
new
?RandomAccessFile(name1,?
"
rw
"
);
104
????????????RandomAccessFile?raf2?
=
?
new
?RandomAccessFile(name2,?
"
rw
"
);
105
????????????FileChannel?fc1?
=
?raf1.getChannel();
106
????????????FileChannel?fc2?
=
?raf2.getChannel();
107
????????????fc1.transferTo(
0
,?raf1.length(),?fc2);
108
????????????fc1.close();
109
????????????fc2.close();
110
????????}
?
catch
?(Exception?e)?
{
111
????????????e.printStackTrace();
112
????????}
113
????????
long
?end?
=
?System.currentTimeMillis();
114
????????
long
?time?
=
?end?
-
?start;
115
????????System.out.println(time);
116
????}
首先測(cè)試的文件是一個(gè)30幾M的文件,測(cè)試結(jié)果出乎意料,是否因?yàn)槲募∧??用個(gè)200M的文件再次
?測(cè)試一下,結(jié)果更匪夷所思,transfor的方式快些,exec的方式快些,Old IO和NIO相差不大,而且
?稍快與NIO,拿到這個(gè)結(jié)果真讓人氣餒,再次查找資料,但都沒有解決自己的疑問,這個(gè)時(shí)候看到了
?Think In Java第三版上講到NIO,更讓我興奮的是我看到如下的話,大概意思吧,原文記不住了
?"Java NIO在文件讀取和網(wǎng)絡(luò)方面有很大的性能提高,網(wǎng)絡(luò)方面不說,這里說說文件操作"看到這段話
?真是高興,因?yàn)門hink In Java一向都是有很多寫的很好的小例子,懷著這種心情,往下看,終于找到了
?一個(gè)顯示性能差別小例子,但讓我哭笑不得的是,作者居然是使用的內(nèi)存映射的例子。類似這樣
?RandomAccessFile raf1 = new RandomAccessFile(name1, "rw");
???FileChannel fc1 = raf1.getChannel();
???MappedByteBuffer mbb = fc1.map(FileChannel.MapMode.READ_WRITE, 0,
?????1024);
?使用內(nèi)存映射來和傳統(tǒng)的IO來對(duì)比讀寫,本來就是個(gè)不公平的事情。內(nèi)存映射的讀取實(shí)際是內(nèi)存讀寫
?和傳統(tǒng)IO比肯定有很大差距。到現(xiàn)在,從開始NIO到現(xiàn)在已經(jīng)有1周的時(shí)間了,每天我都會(huì)在工作之余拿出?1-2個(gè)小時(shí)看看NIO,但這個(gè)問題越來越迷離。
?
?今天有時(shí)間把這個(gè)過程寫出來,一方面感覺過程無奈,寫出來留個(gè)紀(jì)念,另一方面希望和大家交流一下,?如果你沒看過NIO或者沒有作過這個(gè)方面的嘗試,如果你有興趣可以一起探討,如果你了解這個(gè)問題,請(qǐng)?不吝賜教,或給個(gè)提示,或者告訴我我的測(cè)試錯(cuò)在什么地方,不勝感謝。同時(shí),我會(huì)繼續(xù)這個(gè)問題,直到?找到答案。然后會(huì)把答案放上來共享。