探索Java NIO的歷程
前段時間有些時間,打算看看NIO的東西,本來以為很快可以了解的東西,卻用了很多時間。
首先Goole NIO可以看到很多的教程,非阻塞,Buffer,內(nèi)存映射,塊讀取前三個很快就有所了解
嘗試著寫了些小程序,學(xué)習(xí)東西的時候總喜歡寫點小例子。
唯獨塊讀取沒有找到對應(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)一次一個字節(jié)地處
理數(shù)據(jù)。一個輸入流產(chǎn)生一個字節(jié)的數(shù)據(jù),一個輸出流消費一個字節(jié)的數(shù)據(jù)。為流式數(shù)據(jù)創(chuàng)建過濾器非常容易。鏈接幾個過濾器,以便每個過濾器只負(fù)責(zé)單個復(fù)雜處理機制的一部分,這樣也是相對簡單的。不利的一面是,面向流的 I/O 通常相當(dāng)慢。 一個 面向塊 的 I/O 系統(tǒng)以塊的形式處理數(shù)據(jù)。每一個操作都在一步中產(chǎn)生或者消費一個數(shù)據(jù)塊。按塊處理數(shù)據(jù)比按(流式的)字節(jié)處理數(shù)據(jù)要快得多。但是面向塊的 I/O 缺少一些面向流的I/O 所具有的優(yōu)雅性和簡單性。
--------------------------------------------------------------------------------
首先簡單的印象是NIO快,所以想寫個程序驗證一下.如下復(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é)果測試結(jié)果出乎意料,函數(shù)一比函數(shù)二要快,就是說Old IO快于NIO ,從此
?也就開始了整個過程:
?為了了解這個問題,仔細(xì)搜索并仔細(xì)再看IBM 的NIO教程,看到如下這段話
?---------------------------------------------
?在 JDK 1.4 中原來的 I/O 包和 NIO 已經(jīng)很好地集成了。 java.io.* 已經(jīng)以 NIO 為基礎(chǔ)重新實現(xiàn)了,
?所以現(xiàn)在它可以利用 NIO 的一些特性。例如, java.io.* 包中的一些類包含以塊的形式讀寫數(shù)據(jù)的方法,
?這使得即使在更面向流的系統(tǒng)中,處理速度也會更快。 也可以用 NIO 庫實現(xiàn)標(biāo)準(zhǔn) I/O 功能。例如,
?可以容易地使用塊 I/O 一次一個字節(jié)地移動數(shù)據(jù)。但是正如您會看到的,NIO 還提供了原 I/O 包中所沒有的許多好處。
??? ---------------------------------------------
??? 所以我想,是否因為InputStream中使用了塊讀取實現(xiàn)了呢,所以進入JDK1.4中的InputStream中
??? 看看source,首先引起我注意的是read函數(shù),當(dāng)參數(shù)是一個byte數(shù)組的時候,直接調(diào)用的native實現(xiàn)
??? 難道是這個,為了驗證,下載了一個JDK1.3下來,發(fā)現(xiàn)JDK1.3是一樣的。
???
??? 繼續(xù),我想是否是JVM底層實現(xiàn)了塊讀取呢,為了證明這個我用JDK1.3和JDK1.4同時實現(xiàn)了類似的函數(shù),??? 測試的結(jié)果再次出乎意料,性能相差不大.那就不是這個了。
? 為此多方查找資料,未果,為此多寫幾個函數(shù),好好測試一下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
????}
首先測試的文件是一個30幾M的文件,測試結(jié)果出乎意料,是否因為文件太小呢 ?用個200M的文件再次
?測試一下,結(jié)果更匪夷所思,transfor的方式快些,exec的方式快些,Old IO和NIO相差不大,而且
?稍快與NIO,拿到這個結(jié)果真讓人氣餒,再次查找資料,但都沒有解決自己的疑問,這個時候看到了
?Think In Java第三版上講到NIO,更讓我興奮的是我看到如下的話,大概意思吧,原文記不住了
?"Java NIO在文件讀取和網(wǎng)絡(luò)方面有很大的性能提高,網(wǎng)絡(luò)方面不說,這里說說文件操作"看到這段話
?真是高興,因為Think In Java一向都是有很多寫的很好的小例子,懷著這種心情,往下看,終于找到了
?一個顯示性能差別小例子,但讓我哭笑不得的是,作者居然是使用的內(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來對比讀寫,本來就是個不公平的事情。內(nèi)存映射的讀取實際是內(nèi)存讀寫
?和傳統(tǒng)IO比肯定有很大差距。到現(xiàn)在,從開始NIO到現(xiàn)在已經(jīng)有1周的時間了,每天我都會在工作之余拿出?1-2個小時看看NIO,但這個問題越來越迷離。
?
?今天有時間把這個過程寫出來,一方面感覺過程無奈,寫出來留個紀(jì)念,另一方面希望和大家交流一下,?如果你沒看過NIO或者沒有作過這個方面的嘗試,如果你有興趣可以一起探討,如果你了解這個問題,請?不吝賜教,或給個提示,或者告訴我我的測試錯在什么地方,不勝感謝。同時,我會繼續(xù)這個問題,直到?找到答案。然后會把答案放上來共享。
#?re: 探索Java NIO的歷程 2006-11-19 15:49
NIO的目的不是在單個IO的時候快過普通I/O,而是在并發(fā)多個I/O的時候減少線程的使用量,從而提高整個系統(tǒng)的可伸縮性。??回復(fù)??更多評論??
#?re: 探索Java NIO的歷程 2006-11-19 19:34
NIO的目的是加速IO,并發(fā)多個IO時候減少線程使用量是其中的一個很重要的部分,這不排除,文章開始我已經(jīng)說了。但是在驗證過程中,其它的問題例如非阻塞,例如內(nèi)存映射,有大量的例子,很容易讓人理解,但我提到的問題,并沒有找到答案。我的意思不是說這個重要,而是疑問在這里。??回復(fù)??更多評論??
#?re: 探索Java NIO的歷程 2006-11-19 21:37
看了兩遍,還是不清楚你的問題是啥,呵呵。??回復(fù)??更多評論??
#?re: 探索Java NIO的歷程 2006-11-19 21:44
謝謝你指出問題,也許我說的不夠清楚.
我的疑問是:
很多地方(IBM NIO入門教程,Think In Java, google到的教材)都說NIO的文件操作有很大幅度的性能提高.但是我自己寫程序證明不出什么地方提高了,我還沒找到方法可以證明性能提高了。我學(xué)習(xí)東西的一個習(xí)慣是寫一些小的例子,證明事實確實如此,但塊讀取提高性能方面,我確實沒有做到,寫出的程序要不互相矛盾,要不是反例。
??回復(fù)??更多評論??
#?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.
??回復(fù)??更多評論??
#?re: 探索Java NIO的歷程 2006-11-19 22:08
首先,謝謝你幫忙解決我的疑問。:)
然后,這個問題如果是這樣的話,所有的現(xiàn)象就說的通了,我再看看。謝謝??回復(fù)??更多評論??
#?re: 探索Java NIO的歷程 2006-11-20 10:20
JDK1.4以上的IO讀取底層使用的實際上都是NIO,要比較得拿1.3里的OLD IO去比較。??回復(fù)??更多評論??
#?re: 探索Java NIO的歷程 2006-11-20 14:18
樓上看我的文章,我確是拿jdk1.3比較了。??回復(fù)??更多評論??
#?re: 探索Java NIO的歷程 2006-11-28 11:09
呵呵,你一定是沒有用對jdk吧我把你上面方法的2,3,4在jdk1.5的環(huán)境中測試拷貝一個20M的文件,時間,分別是297,453,406,不過方法2在jdk1.3的環(huán)境中用時卻是3969。??回復(fù)??更多評論??
#?re: 探索Java NIO的歷程2006-11-28 11:37
首先jdk的使用是肯定沒錯的,我測試了很多次,而且反復(fù)確認(rèn)了這個問題。你的這個數(shù)據(jù)我測試出來過,首先說說一些測試時候的注意的幾個問題吧。
1,不能直接一個main函數(shù)調(diào)用所有的test(),因為會有內(nèi)存回收的問題。可以嘗試把執(zhí)行順序倒過來,會發(fā)現(xiàn)所有的測試數(shù)據(jù)會有大變化
2,可以在每個函數(shù)之間加上System.gc(),但是依然會有問題,大概的規(guī)律是第一次執(zhí)行慢,第二次快,以后還有快的幾次,然後忽然又變慢。這個不是虛擬機load的問題。
3,可以做如下測試,只執(zhí)行一個函數(shù),用手動執(zhí)行5-7次,點的快和慢得到的結(jié)果很大差距。
4,建議用大一點的文件200m,或者更大,因為這樣可以漸少一些特殊因素的影響
5,多次測試,去掉怪異的數(shù)值,平均值
6,這個問題我現(xiàn)在發(fā)現(xiàn)的最穩(wěn)妥的辦法是,執(zhí)行test1(),等半分鐘,再執(zhí)行另一個,雖然這個方法是笨的不行,但是確是得出的數(shù)據(jù)穩(wěn)定些,其他的辦法很難得出
正確的數(shù)據(jù)。
7,我也嘗試了在linux來測試,效果也不明顯。??回復(fù)??更多評論?