1. 在JAVA传统的IO系统中,读取磁盘文件数据的过程如下:
以FileInputStream类为例,该类有一个read(byte b[])方法,byte b[]是我们要存储读取
到用户空间的缓冲区。参看read(byte b[])方法的源码,可知,它会在内部再调用readBytes(b, 0, b.length)方法,而且readBytes(b, 0, b.length)方法是一个native方法(即本地方法),最终通过这个本地方法来发起一次系统调用,即调用系统内核的read()方法,内核从磁盘读取数据到内核缓冲区,这个过程由磁盘控制器通过DMA操作将数据从磁盘读取取内核缓冲区,此过程不依赖于CPU。然后用户进程再将数据从内核缓冲区拷贝到用户空间缓冲区。用户进程再从用户空间缓冲区中读取数据。因为用户进程是不可以直接访问硬件的。所以需要通过内核来充当中间人的作用来实现文件的读取。整个过程如下图所示:
2. 自从JAVA 1.4以后,JAVA在NIO在引入了文件通道的概念,在API中有提供了一个
FileChannel类。该类与传统的IO流进行关联。可以由FileInputStream或FileOutputStream获取该文件通道,我们可以通过通道对文件进行读写操作。
3.JAVA NIO中还引入了文件内存映射的概念:现代操作系统大都支持虚拟内存映射,这样,我们可以把内核空间地址与用户空间的虚拟地址映射到同一个物理地址,这样,DMA 硬件(只能访问物理内存地址)就可以填充对内核与用户空间进程同时可见的缓冲区了。如下图所示:
这样做的好处是,我们在读取磁盘文件时,再也不用通过内核缓冲区到用户进程缓冲区的来回拷贝操作了。操作系统会通过一些页面调度算法来将磁盘文件载入对分页区进行高速缓存的物理内存。我们就可以通过映射后物理内存来读取磁盘文件了。
3. 下面我们通过三种不同方式文件拷贝的案例来验证文件通道及文件内存映射在IO
系统中的作用。测试环境为windows 32位系统和JDK1.6。代码中使用的测试文件movie.avi为一个123MB的视频文件。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
package
cn.com.hbust.nio.file;
import
java.io.File;
import
java.io.FileInputStream;
import
java.io.FileOutputStream;
import
java.nio.MappedByteBuffer;
import
java.nio.channels.FileChannel;
public
class
FileCopyTest {
public
static
void
main(String[] args)
throws
Exception {
String sourcePath =
"F:\\mywork\\javademo\\dir1\\movie.avi"
;
String destPath1 =
"F:\\mywork\\javademo\\dir2\\movie1.avi"
;
String destPath2 =
"F:\\mywork\\javademo\\dir2\\movie2.avi"
;
String destPath3 =
"F:\\mywork\\javademo\\dir2\\movie3.avi"
;
long
t1 = System.currentTimeMillis();
traditionalCopy(sourcePath,destPath1);
long
t2 = System.currentTimeMillis();
System.out.println(
"传统IO方法实现文件拷贝耗时:"
+ (t2-t1) +
"ms"
);
nioCopy(sourcePath,destPath2);
long
t3 = System.currentTimeMillis();
System.out.println(
"利用NIO文件通道方法实现文件拷贝耗时:"
+ (t3-t2) +
"ms"
);
nioCopy2(sourcePath,destPath3);
long
t4 = System.currentTimeMillis();
System.out.println(
"利用NIO文件内存映射及文件通道实现文件拷贝耗时:"
+ (t4-t3) +
"ms"
);
}
private
static
void
nioCopy2(String sourcePath, String destPath)
throws
Exception {
File source =
new
File(sourcePath);
File dest =
new
File(destPath);
if
(!dest.exists()) {
dest.createNewFile();
}
FileInputStream fis =
new
FileInputStream(source);
FileOutputStream fos =
new
FileOutputStream(dest);
FileChannel sourceCh = fis.getChannel();
FileChannel destCh = fos.getChannel();
MappedByteBuffer mbb = sourceCh.map(FileChannel.MapMode.READ_ONLY,
0
, sourceCh.size());
destCh.write(mbb);
sourceCh.close();
destCh.close();
}
private
static
void
traditionalCopy(String sourcePath, String destPath)
throws
Exception{
File source =
new
File(sourcePath);
File dest =
new
File(destPath);
if
(!dest.exists()) {
dest.createNewFile();
}
FileInputStream fis =
new
FileInputStream(source);
FileOutputStream fos =
new
FileOutputStream(dest);
byte
[] buf = newbyte [
512
];
int
len =
0
;
while
((len = fis.read(buf)) != -
1
) {
fos.write(buf,
0
, len);
}
fis.close();
fos.close();
}
private
static
void
nioCopy(String sourcePath, String destPath)
throws
Exception{
File source =
new
File(sourcePath);
File dest =
new
File(destPath);
if
(!dest.exists()) {
dest.createNewFile();
}
FileInputStream fis =
new
FileInputStream(source);
FileOutputStream fos =
new
FileOutputStream(dest);
FileChannel sourceCh = fis.getChannel();
FileChannel destCh = fos.getChannel();
destCh.transferFrom(sourceCh,
0
, sourceCh.size());
sourceCh.close();
destCh.close();
}
}
|
每执行完一次拷贝之后,将F:\mywork\javademo\dir2\目录中的内容删除掉,重复执行8次。观察测试结果如下:时间单位为ms(毫秒)
由上表可知,传统IO方式平均拷贝完成时间约为1968ms,NIO文件通道方式平均拷贝完成时间约为1672ms,文件内存映射及文件通道方式平均拷贝完成时间约为1418ms。