探索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或者没有作过这个方面的尝试,如果你有兴趣可以一起探讨,如果你了解这个问题,请 不吝赐教,或给个提示,或者告诉我我的测试错在什么地方,不胜感谢。同时,我会继续这个问题,直到 找到答案。然后会把答案放上来共享。