Java-缓冲区操作和内存映射

作者:聂勇 欢迎转载,请保留作者信息并说明文章来源!

之前写了一篇文章《NIO入门篇》,介绍了NIO的缓冲区与旧IO流操作的不同以及如何使用缓冲区。Java封装了非常多的细节,为了跨平台,操作系统许多的特性我们无法使用,如内存映射。JDK1.4增加了NIO,使得我们可以在Java中使用文件锁、内存映射等特性。

下面就缓冲区操作原理和实现、内存映射原理和实现进行介绍和比较。

缓冲区操作

其实缓冲区的操作可能理解为:应用将数据写入缓冲区,然后整个缓存区写入磁盘(输出);从磁盘读取数据,填满缓存区,应用从缓存区读取数据(输入)。

以读操作为例:用户进程调用操作系统的read()方法,要求填充缓冲区。内核向磁盘控制器发指令,要求从磁盘读取数据。磁盘控制器从磁盘读取所需数据填充内核的缓冲区(现代操作系统中,这一步是由DMA完成,无需主CPU协助),一旦所需的数据读取完毕,内核将内核缓冲区的数据复制到用户进程的缓冲区。
缓冲区操作

Java示例代码:

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
package cn.aofeng.demo.nio;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* IO-缓冲区操作。
*
* @author NieYong <aofengblog@163.com>
*/
public class BufferIO {
private static Logger logger = Logger.getLogger("bufferio");
// 缓冲区大小
private final static int BUFFER_SIZE = 4096;
public static void close(Closeable c) {
if (null != c) {
try {
c.close();
} catch (IOException e) {
// ingore
}
}
}
/**
* @param args [0]:读取文件的完整路径
*/
public static void main(String[] args) {
String filename = args[0];
File file = new File(filename);
FileChannel channel = null;
try {
channel = new FileInputStream(file).getChannel();
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
long startTime = System.currentTimeMillis();
while (channel.read(buffer) > 0) {
buffer.flip();
buffer.clear();
}
long endTime = System.currentTimeMillis();
logger.info("缓冲区读取文件耗时:" + (endTime-startTime)+"毫秒");
} catch (FileNotFoundException e) {
logger.log(Level.SEVERE, "找不到文件:"+filename, e);
} catch (IOException e) {
logger.log(Level.SEVERE, "读取文件出错", e);
} finally{
close(channel);
}
}
}

内存映射

将文件的某一部分或全部映射到一个内存区域,用操作内存的方式操作文件。和传统的read()方法操作相比,有如下不同点:

  • 用户进程直接把文件当内存操作,不需调用系统的read()方法。
  • 用户进程读取内存映射空间,如果没有数据,会自动产生页错误,从文件将数据读入内存区。
  • 避免了内核空间和用户空间之间的数据复制,更加高效。
  • 节省内存。
内存映射

Java示例代码:

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
package cn.aofeng.demo.nio;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* IO-内存映射。
*
* @author NieYong <aofengblog@163.com>
*/
public class MemoryMapper {
private static Logger logger = Logger.getLogger("bufferio");
// 缓冲区大小
private final static int BUFFER_SIZE = 4096;
public static void close(Closeable c) {
if (null != c) {
try {
c.close();
} catch (IOException e) {
// ingore
}
}
}
/**
* @param args [0]:读取文件的完整路径
*/
public static void main(String[] args) {
String filename = args[0];
File file = new File(filename);
FileChannel channel = null;
try {
channel = new FileInputStream(file).getChannel();
MappedByteBuffer buffer = channel.map(MapMode.READ_ONLY, 0, channel.size());
long startTime = System.currentTimeMillis();
long total = 0;
long len = file.length();
byte[] bs = new byte[BUFFER_SIZE];
for (int index = 0; index < len; index+=BUFFER_SIZE) {
int readSize = BUFFER_SIZE;
if (len-index < BUFFER_SIZE) {
readSize = (int) len-index;
buffer.get(new byte[readSize]);
} else {
buffer.get(bs);
}
total += readSize;
}
long endTime = System.currentTimeMillis();
logger.info("内存映射读取文件耗时:" + (endTime-startTime)+"毫秒,文件长度:"+total+"字节");
} catch (FileNotFoundException e) {
logger.log(Level.SEVERE, "找不到文件:"+filename, e);
} catch (IOException e) {
logger.log(Level.SEVERE, "读取文件出错", e);
} finally{
close(channel);
}
}
}

性能比较

测试环境:
操作系统:Windows 7 64位
CPU:Intel(R) Core(TM) i3 CPU M 380 @2.53GHz 2.53GHz
内存:6GB
硬盘:5400转

分别执行上面的两段代码,结果如下:

1
java cn.aofeng.demo.nio.BufferIO D:\Movie\大明猩.BD1280高清中英双字.rmvb

2013-9-29 18:24:52 cn.aofeng.demo.nio.BufferIO main
信息: 缓冲区读取文件耗时:32463毫秒

1
java cn.aofeng.demo.nio.MemoryMapper D:\Movie\大明猩.BD1280高清中英双字.rmvb

2013-9-29 18:18:20 cn.aofeng.demo.nio.MemoryMapper main
信息: 内存映射读取文件耗时:27956毫秒,文件长度:2006157134字节