探索Java NIO的歷程
前段時間有些時間,打算看看NIO的東西,本來以為很快可以了解的東西,卻用了很多時間。
首先Goole NIO可以看到很多的教程,非阻塞,Buffer,內存映射,塊讀取前三個很快就有所了解
嘗試著寫了些小程序,學習東西的時候總喜歡寫點小例子。
唯獨塊讀取沒有找到對應的東西。(在過程中,主要看了IBM 的NIO入門)
首先,IBM NIO入門中的語句
--------------------------------------------------------------------------------
原來的 I/O 庫(在 java.io.*中) 與 NIO 最重要的區別是數據打包和傳輸的方式。正如前面提到的,
原來的 I/O 以流的方式處理數據,而 NIO 以塊的方式處理數據。 面向流 的 I/O 系統一次一個字節地處
理數據。一個輸入流產生一個字節的數據,一個輸出流消費一個字節的數據。為流式數據創建過濾器非常容易。鏈接幾個過濾器,以便每個過濾器只負責單個復雜處理機制的一部分,這樣也是相對簡單的。不利的一面是,面向流的 I/O 通常相當慢。 一個 面向塊 的 I/O 系統以塊的形式處理數據。每一個操作都在一步中產生或者消費一個數據塊。按塊處理數據比按(流式的)字節處理數據要快得多。但是面向塊的 I/O 缺少一些面向流的I/O 所具有的優雅性和簡單性。
--------------------------------------------------------------------------------
首先簡單的印象是NIO快,所以想寫個程序驗證一下.如下復制:
?
?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
????}
本以為可以結束,結果測試結果出乎意料,函數一比函數二要快,就是說Old IO快于NIO ,從此
?也就開始了整個過程:
?為了了解這個問題,仔細搜索并仔細再看IBM 的NIO教程,看到如下這段話
?---------------------------------------------
?在 JDK 1.4 中原來的 I/O 包和 NIO 已經很好地集成了。 java.io.* 已經以 NIO 為基礎重新實現了,
?所以現在它可以利用 NIO 的一些特性。例如, java.io.* 包中的一些類包含以塊的形式讀寫數據的方法,
?這使得即使在更面向流的系統中,處理速度也會更快。 也可以用 NIO 庫實現標準 I/O 功能。例如,
?可以容易地使用塊 I/O 一次一個字節地移動數據。但是正如您會看到的,NIO 還提供了原 I/O 包中所沒有的許多好處。
??? ---------------------------------------------
??? 所以我想,是否因為InputStream中使用了塊讀取實現了呢,所以進入JDK1.4中的InputStream中
??? 看看source,首先引起我注意的是read函數,當參數是一個byte數組的時候,直接調用的native實現
??? 難道是這個,為了驗證,下載了一個JDK1.3下來,發現JDK1.3是一樣的。
???
??? 繼續,我想是否是JVM底層實現了塊讀取呢,為了證明這個我用JDK1.3和JDK1.4同時實現了類似的函數,??? 測試的結果再次出乎意料,性能相差不大.那就不是這個了。
? 為此多方查找資料,未果,為此多寫幾個函數,好好測試一下IO的不同。于是有了如下的一些函數
??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
????}
首先測試的文件是一個30幾M的文件,測試結果出乎意料,是否因為文件太小呢 ?用個200M的文件再次
?測試一下,結果更匪夷所思,transfor的方式快些,exec的方式快些,Old IO和NIO相差不大,而且
?稍快與NIO,拿到這個結果真讓人氣餒,再次查找資料,但都沒有解決自己的疑問,這個時候看到了
?Think In Java第三版上講到NIO,更讓我興奮的是我看到如下的話,大概意思吧,原文記不住了
?"Java NIO在文件讀取和網絡方面有很大的性能提高,網絡方面不說,這里說說文件操作"看到這段話
?真是高興,因為Think In Java一向都是有很多寫的很好的小例子,懷著這種心情,往下看,終于找到了
?一個顯示性能差別小例子,但讓我哭笑不得的是,作者居然是使用的內存映射的例子。類似這樣
?RandomAccessFile raf1 = new RandomAccessFile(name1, "rw");
???FileChannel fc1 = raf1.getChannel();
???MappedByteBuffer mbb = fc1.map(FileChannel.MapMode.READ_WRITE, 0,
?????1024);
?使用內存映射來和傳統的IO來對比讀寫,本來就是個不公平的事情。內存映射的讀取實際是內存讀寫
?和傳統IO比肯定有很大差距。到現在,從開始NIO到現在已經有1周的時間了,每天我都會在工作之余拿出?1-2個小時看看NIO,但這個問題越來越迷離。
?
?今天有時間把這個過程寫出來,一方面感覺過程無奈,寫出來留個紀念,另一方面希望和大家交流一下,?如果你沒看過NIO或者沒有作過這個方面的嘗試,如果你有興趣可以一起探討,如果你了解這個問題,請?不吝賜教,或給個提示,或者告訴我我的測試錯在什么地方,不勝感謝。同時,我會繼續這個問題,直到?找到答案。然后會把答案放上來共享。