import java.io.*;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class SpeedTest {
private static final String INPUT_FILE_PATH = "io_speed.pdf";
private static final String OUTPUT_FILE_PATH = "io_speed_copy.pdf";
public static void main(String[] args) {
long ioStreamTime1 = ioStreamCopy();
System.out.println("io stream copy:" + ioStreamTime1);
long ioStreamTime2 = bufferedStreamCopy();
System.out.println("buffered stream copy:" + ioStreamTime2);
long ioStreamTime3 = nioStreamCopy();
System.out.println("nio stream copy:" + ioStreamTime3);
long ioStreamTime4 = nioMemoryStreamCopy();
System.out.println("nio memory stream copy:" + ioStreamTime4);
}
private static long ioStreamCopy() {
long costTime = -1;
FileInputStream is = null;
FileOutputStream os = null;
try {
long startTime = System.currentTimeMillis();
is = new FileInputStream(INPUT_FILE_PATH);
os = new FileOutputStream(OUTPUT_FILE_PATH);
int read = is.read();
while (read != -1) {
os.write(read);
read = is.read();
}
long endTime = System.currentTimeMillis();
costTime = endTime - startTime;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
if (os != null) {
os.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return costTime;
}
private static long bufferedStreamCopy() {
long costTime = -1;
FileReader reader = null;
FileWriter writer = null;
try {
long startTime = System.currentTimeMillis();
reader = new FileReader(INPUT_FILE_PATH);
writer = new FileWriter(OUTPUT_FILE_PATH);
int read = -1;
while ((read = reader.read()) != -1) {
writer.write(read);
}
writer.flush();
long endTime = System.currentTimeMillis();
costTime = endTime - startTime;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();
}
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return costTime;
}
private static long nioStreamCopy() {
long costTime = -1;
FileInputStream is = null;
FileOutputStream os = null;
FileChannel fi = null;
FileChannel fo = null;
try {
long startTime = System.currentTimeMillis();
is = new FileInputStream(INPUT_FILE_PATH);
os = new FileOutputStream(OUTPUT_FILE_PATH);
fi = is.getChannel();
fo = os.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
buffer.clear();
int read = fi.read(buffer);
if (read == -1) {
break;
}
buffer.flip();
fo.write(buffer);
}
long endTime = System.currentTimeMillis();
costTime = endTime - startTime;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fi != null) {
fi.close();
}
if (fo != null) {
fo.close();
}
if (is != null) {
is.close();
}
if (os != null) {
os.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return costTime;
}
private static long nioMemoryStreamCopy() {
long costTime = -1;
FileInputStream is = null;
//映射文件输出必须用RandomAccessFile
RandomAccessFile os = null;
FileChannel fi = null;
FileChannel fo = null;
try {
long startTime = System.currentTimeMillis();
is = new FileInputStream(INPUT_FILE_PATH);
os = new RandomAccessFile(OUTPUT_FILE_PATH, "rw");
fi = is.getChannel();
fo = os.getChannel();
IntBuffer iIb = fi.map(FileChannel.MapMode.READ_ONLY, 0, fi.size()).asIntBuffer();
IntBuffer oIb = fo.map(FileChannel.MapMode.READ_WRITE, 0, fo.size()).asIntBuffer();
while (iIb.hasRemaining()) {
int read = iIb.get();
oIb.put(read);
}
long endTime = System.currentTimeMillis();
costTime = endTime - startTime;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fi != null) {
fi.close();
}
if (fo != null) {
fo.close();
}
if (is != null) {
is.close();
}
if (os != null) {
os.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return costTime;
}
}
复制大小约90MB的文件所需的时间(单位:毫秒)
io stream copy:182797
buffered stream copy:14699
nio stream copy:1251
nio memory stream copy:884
很显然传统的IO方式效率很低,甚至于差了nio两个数量级。nio内存映射访问速度最快。
java I/O的工作机制
data-buffering-at-os-level.png上图很简单的显示了数据传输的指示图,当应用进程执行到read()操作时操作系统底层一下操作
- 内核通知DMA读取某块数据
- 磁盘控制器通过DMA将数据读入到内核的缓冲区
- 当缓冲完成是,内核会把数据从内核缓冲区读到进程指定缓冲区。
这一系列操作涉及到底层调用,而jvm进程属于用户进程,一旦涉及到系统调用就会从用户态切换到内核态。这个切换也是一个相当耗时的操作。
其实还有比较重要的是,用户进程一次读取一个字节或者读到一个字节数组中,但是内核通过磁盘控制器一般是读取某一块或者是某几块的数据。
不然每次都进如内核态不是一个很愚蠢的事情呒?所以第一次read()的时候会访问到磁盘数据,后续可能直接从内核缓冲中拿数据。
如果数据不可用,process将会被挂起,并需要等待内核从磁盘上把数据取到内核缓冲区中。
由于DMA不能直接访问用户空间(用户缓冲区),普通IO操作需要将数据来回地在 用户缓冲区 和 内核缓冲区移动,这在一定程序上影响了IO的速度,并且这是一种阻塞式的数据读取方式,并不能很有效的利用cpu。
而java io流中的缓冲流输入输出时都会把数据储存一个数组中,也就是进程的缓冲区(默认是8192字节),这样就可以等数据满了以后一次性切换到内核态并写入磁盘,相比于io流的多次切换到内核态,90M的文件读写就可以提升一个数量级了。
IO 是基于流来读取的,而NIO则是基于块读取,面向流 的 I/O 系统一次一个字节地处理数据。 面向块 的 I/O 系统以块的形式处理数据。按块处理数据比按(流式的)字节处理数据要快得多。 所以NIO通过buffer和channel读取数据又要比IO流快上一个数量级。并且 NIO 支持 Direct Memory, 可以减少一次数据拷贝。可见通过FileChannel读取数据还是有很大优势的,并且NIO可以通过selector实现异步读取的操作。
那么速度最快的内存映射又是什么呢?
715283-20160804114034684-1256880404.png
首先NIO提供了内存映射或者说是直接内存,这可以使得数据少一次数据的拷贝,又可以节省大量时间。
内核空间和用户空间会映射到同一块物理内存,这样减少一次数据的拷贝,所以这种方式的文件操作速度会非常快(和文件大小也有关系,并没有绝对好的方式)
MappedByteBuffer可以通过java.nio.channels.FileChannel的 map方法创建。具体关于MappedByteBuffer还是可以在写一篇博客详细论述的。
这一篇就到这吧,写的越多发现自己不会的就越多,以后再写博客来填了这些坑吧
网友评论