美文网首页Java学习笔记程序员
java 四种io实现速度对比

java 四种io实现速度对比

作者: 徐士林 | 来源:发表于2017-03-02 00:05 被阅读571次
    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()操作时操作系统底层一下操作

    1. 内核通知DMA读取某块数据
    2. 磁盘控制器通过DMA将数据读入到内核的缓冲区
    3. 当缓冲完成是,内核会把数据从内核缓冲区读到进程指定缓冲区。

    这一系列操作涉及到底层调用,而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还是可以在写一篇博客详细论述的。

    这一篇就到这吧,写的越多发现自己不会的就越多,以后再写博客来填了这些坑吧

    参考

    相关文章

      网友评论

        本文标题:java 四种io实现速度对比

        本文链接:https://www.haomeiwen.com/subject/pxwcgttx.html