探索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或者沒有作過這個方面的嘗試,如果你有興趣可以一起探討,如果你了解這個問題,請?不吝賜教,或給個提示,或者告訴我我的測試錯在什么地方,不勝感謝。同時,我會繼續這個問題,直到?找到答案。然后會把答案放上來共享。
#?re: 探索Java NIO的歷程 2006-11-19 15:49
NIO的目的不是在單個IO的時候快過普通I/O,而是在并發多個I/O的時候減少線程的使用量,從而提高整個系統的可伸縮性。??回復??更多評論??
#?re: 探索Java NIO的歷程 2006-11-19 19:34
NIO的目的是加速IO,并發多個IO時候減少線程使用量是其中的一個很重要的部分,這不排除,文章開始我已經說了。但是在驗證過程中,其它的問題例如非阻塞,例如內存映射,有大量的例子,很容易讓人理解,但我提到的問題,并沒有找到答案。我的意思不是說這個重要,而是疑問在這里。??回復??更多評論??
#?re: 探索Java NIO的歷程 2006-11-19 21:37
看了兩遍,還是不清楚你的問題是啥,呵呵。??回復??更多評論??
#?re: 探索Java NIO的歷程 2006-11-19 21:44
謝謝你指出問題,也許我說的不夠清楚.
我的疑問是:
很多地方(IBM NIO入門教程,Think In Java, google到的教材)都說NIO的文件操作有很大幅度的性能提高.但是我自己寫程序證明不出什么地方提高了,我還沒找到方法可以證明性能提高了。我學習東西的一個習慣是寫一些小的例子,證明事實確實如此,但塊讀取提高性能方面,我確實沒有做到,寫出的程序要不互相矛盾,要不是反例。
??回復??更多評論??
#?re: 探索Java NIO的歷程 2006-11-19 21:59
其實讀取文件,除非JDK代碼有問題,否則怎么也不會有大幅性能提升了。讀文件,完全是磁盤性能問題了。
下面這段話(來自Java? I/O, 2nd Edition)也許能解決你部分問題:
Nonblocking I/O is primarily relevant to network connections. Pipe channels that move data between two threads also support nonblocking I/O. File channels don't support it at all because file access doesn't block nearly as often as network channels do, and most modern disk controllers can fill a CPU with data fast enough to keep it satisfied. Furthermore, it's uncommon for one program to read or write hundreds of files simultaneously. However, on network servers, this usage pattern is the rule, not the exception.
??回復??更多評論??
#?re: 探索Java NIO的歷程 2006-11-19 22:08
首先,謝謝你幫忙解決我的疑問。:)
然后,這個問題如果是這樣的話,所有的現象就說的通了,我再看看。謝謝??回復??更多評論??
#?re: 探索Java NIO的歷程 2006-11-20 10:20
JDK1.4以上的IO讀取底層使用的實際上都是NIO,要比較得拿1.3里的OLD IO去比較。??回復??更多評論??
#?re: 探索Java NIO的歷程 2006-11-20 14:18
樓上看我的文章,我確是拿jdk1.3比較了。??回復??更多評論??
#?re: 探索Java NIO的歷程 2006-11-28 11:09
呵呵,你一定是沒有用對jdk吧我把你上面方法的2,3,4在jdk1.5的環境中測試拷貝一個20M的文件,時間,分別是297,453,406,不過方法2在jdk1.3的環境中用時卻是3969。??回復??更多評論??
#?re: 探索Java NIO的歷程2006-11-28 11:37
首先jdk的使用是肯定沒錯的,我測試了很多次,而且反復確認了這個問題。你的這個數據我測試出來過,首先說說一些測試時候的注意的幾個問題吧。
1,不能直接一個main函數調用所有的test(),因為會有內存回收的問題。可以嘗試把執行順序倒過來,會發現所有的測試數據會有大變化
2,可以在每個函數之間加上System.gc(),但是依然會有問題,大概的規律是第一次執行慢,第二次快,以后還有快的幾次,然後忽然又變慢。這個不是虛擬機load的問題。
3,可以做如下測試,只執行一個函數,用手動執行5-7次,點的快和慢得到的結果很大差距。
4,建議用大一點的文件200m,或者更大,因為這樣可以漸少一些特殊因素的影響
5,多次測試,去掉怪異的數值,平均值
6,這個問題我現在發現的最穩妥的辦法是,執行test1(),等半分鐘,再執行另一個,雖然這個方法是笨的不行,但是確是得出的數據穩定些,其他的辦法很難得出
正確的數據。
7,我也嘗試了在linux來測試,效果也不明顯。??回復??更多評論?