zoukankan      html  css  js  c++  java
  • java 分次读取大文件的三种方法

    1. java 读取大文件的困难

    java 读取文件的一般操作是将文件数据全部读取到内存中,然后再对数据进行操作。例如

    Path path = Paths.get("file path");
    byte[] data = Files.readAllBytes(path);
    • 1
    • 2

    这对于小文件是没有问题的,但是对于稍大一些的文件就会抛出异常

    Exception in thread "main" java.lang.OutOfMemoryError: Required array size too large
        at java.nio.file.Files.readAllBytes(Files.java:3156)
    • 1
    • 2

    从错误定位看出,Files.readAllBytes 方法最大支持 Integer.MAX_VALUE - 8 大小的文件,也即最大2GB的文件。一旦超过了这个限度,java 原生的方法就不能直接使用了。

    2. 分次读取大文件

    既然不能直接全部读取大文件到内存中,那么就应该把文件分成多个子区域分多次读取。这就会有多种方法可以使用。

    (1) 文件字节流

    对文件建立 java.io.BufferedInputStream ,每次调用 read() 方法时会接连取出文件中长度为 arraySize 的数据到 array 中。这种方法可行但是效率不高。

    import java.io.BufferedInputStream;
    import java.io.FileInputStream;
    import java.io.IOException;
    
    /**
     * Created by zfh on 16-4-19.
     */
    public class StreamFileReader {
        private BufferedInputStream fileIn;
        private long fileLength;
        private int arraySize;
        private byte[] array;
    
        public StreamFileReader(String fileName, int arraySize) throws IOException {
            this.fileIn = new BufferedInputStream(new FileInputStream(fileName), arraySize);
            this.fileLength = fileIn.available();
            this.arraySize = arraySize;
        }
    
        public int read() throws IOException {
            byte[] tmpArray = new byte[arraySize];
            int bytes = fileIn.read(tmpArray);// 暂存到字节数组中
            if (bytes != -1) {
                array = new byte[bytes];// 字节数组长度为已读取长度
                System.arraycopy(tmpArray, 0, array, 0, bytes);// 复制已读取数据
                return bytes;
            }
            return -1;
        }
    
        public void close() throws IOException {
            fileIn.close();
            array = null;
        }
    
        public byte[] getArray() {
            return array;
        }
    
        public long getFileLength() {
            return fileLength;
        }
    
        public static void main(String[] args) throws IOException {
            StreamFileReader reader = new StreamFileReader("/home/zfh/movie.mkv", 65536);
            long start = System.nanoTime();
            while (reader.read() != -1) ;
            long end = System.nanoTime();
            reader.close();
            System.out.println("StreamFileReader: " + (end - start));
        }
    }
    • 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

    (2) 文件通道

    对文件建立 java.nio.channels.FileChannel ,每次调用 read() 方法时会先将文件数据读取到分配的长度为 arraySizejava.nio.ByteBuffer 中,再从中将已经读取到的文件数据转化到 array 中。这种利用了NIO中的通道的方法,比传统的字节流读取文件是要快一些。

    import java.io.FileInputStream;
    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.nio.channels.FileChannel;
    
    /**
     * Created by zfh on 16-4-18.
     */
    public class ChannelFileReader {
        private FileInputStream fileIn;
        private ByteBuffer byteBuf;
        private long fileLength;
        private int arraySize;
        private byte[] array;
    
        public ChannelFileReader(String fileName, int arraySize) throws IOException {
            this.fileIn = new FileInputStream(fileName);
            this.fileLength = fileIn.getChannel().size();
            this.arraySize = arraySize;
            this.byteBuf = ByteBuffer.allocate(arraySize);
        }
    
        public int read() throws IOException {
            FileChannel fileChannel = fileIn.getChannel();
            int bytes = fileChannel.read(byteBuf);// 读取到ByteBuffer中
            if (bytes != -1) {
                array = new byte[bytes];// 字节数组长度为已读取长度
                byteBuf.flip();
                byteBuf.get(array);// 从ByteBuffer中得到字节数组
                byteBuf.clear();
                return bytes;
            }
            return -1;
        }
    
        public void close() throws IOException {
            fileIn.close();
            array = null;
        }
    
        public byte[] getArray() {
            return array;
        }
    
        public long getFileLength() {
            return fileLength;
        }
    
        public static void main(String[] args) throws IOException {
            ChannelFileReader reader = new ChannelFileReader("/home/zfh/movie.mkv", 65536);
            long start = System.nanoTime();
            while (reader.read() != -1) ;
            long end = System.nanoTime();
            reader.close();
            System.out.println("ChannelFileReader: " + (end - start));
        }
    }
    • 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

    (3) 内存文件映射

    这种方法就是把文件的内容被映像到计算机虚拟内存的一块区域,从而可以直接操作内存当中的数据而无需每次都通过 I/O 去物理硬盘读取文件。这是由当前 java 态进入到操作系统内核态,由操作系统读取文件,再返回数据到当前 java 态的过程。这样就能大幅提高我们操作大文件的速度。

    import java.io.FileInputStream;
    import java.io.IOException;
    import java.nio.MappedByteBuffer;
    import java.nio.channels.FileChannel;
    
    /**
     * Created by zfh on 16-4-19.
     */
    public class MappedFileReader {
        private FileInputStream fileIn;
        private MappedByteBuffer mappedBuf;
        private long fileLength;
        private int arraySize;
        private byte[] array;
    
        public MappedFileReader(String fileName, int arraySize) throws IOException {
            this.fileIn = new FileInputStream(fileName);
            FileChannel fileChannel = fileIn.getChannel();
            this.fileLength = fileChannel.size();
            this.mappedBuf = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileLength);
            this.arraySize = arraySize;
        }
    
        public int read() throws IOException {
            int limit = mappedBuf.limit();
            int position = mappedBuf.position();
            if (position == limit) {
                return -1;
            }
            if (limit - position > arraySize) {
                array = new byte[arraySize];
                mappedBuf.get(array);
                return arraySize;
            } else {// 最后一次读取数据
                array = new byte[limit - position];
                mappedBuf.get(array);
                return limit - position;
            }
        }
    
        public void close() throws IOException {
            fileIn.close();
            array = null;
        }
    
        public byte[] getArray() {
            return array;
        }
    
        public long getFileLength() {
            return fileLength;
        }
    
        public static void main(String[] args) throws IOException {
            MappedFileReader reader = new MappedFileReader("/home/zfh/movie.mkv", 65536);
            long start = System.nanoTime();
            while (reader.read() != -1);
            long end = System.nanoTime();
            reader.close();
            System.out.println("MappedFileReader: " + (end - start));
        }
    }
    • 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

    看似问题完美解决了,我们肯定会采用内存文件映射的方法去处理大文件。但是运行结果发现,这个方法仍然不能读取超过2GB的文件,明明 FileChannel.map() 方法传递的文件长度是 long 类型的,怎么和 Integer.MAX_VALUE 有关系?

    Exception in thread "main" java.lang.IllegalArgumentException: Size exceeds Integer.MAX_VALUE
        at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:868)
    • 1
    • 2

    再从错误定位可以看到

    size - The size of the region to be mapped; must be non-negative and no greater than Integer.MAX_VALUE

    这可以归结到一些历史原因,还有 int 类型在 java 中的深入程度,但是本质上由于 java.nio.MappedByteBuffer 是直接继承自 java.nio.ByteBuffer 的,而后者的索引变量是 int 类型的,所以前者也只能最大索引到 Integer.MAX_VALUE 的位置。这样的话我们是不是就没有办法了?当然不是,一个内存文件映射不够用,那么试一试用多个就可以了。

    import java.io.FileInputStream;
    import java.io.IOException;
    import java.nio.MappedByteBuffer;
    import java.nio.channels.FileChannel;
    
    /**
     * Created by zfh on 16-4-19.
     */
    public class MappedBiggerFileReader {
        private MappedByteBuffer[] mappedBufArray;
        private int count = 0;
        private int number;
        private FileInputStream fileIn;
        private long fileLength;
        private int arraySize;
        private byte[] array;
    
        public MappedBiggerFileReader(String fileName, int arraySize) throws IOException {
            this.fileIn = new FileInputStream(fileName);
            FileChannel fileChannel = fileIn.getChannel();
            this.fileLength = fileChannel.size();
            this.number = (int) Math.ceil((double) fileLength / (double) Integer.MAX_VALUE);
            this.mappedBufArray = new MappedByteBuffer[number];// 内存文件映射数组
            long preLength = 0;
            long regionSize = (long) Integer.MAX_VALUE;// 映射区域的大小
            for (int i = 0; i < number; i++) {// 将文件的连续区域映射到内存文件映射数组中
                if (fileLength - preLength < (long) Integer.MAX_VALUE) {
                    regionSize = fileLength - preLength;// 最后一片区域的大小
                }
                mappedBufArray[i] = fileChannel.map(FileChannel.MapMode.READ_ONLY, preLength, regionSize);
                preLength += regionSize;// 下一片区域的开始
            }
            this.arraySize = arraySize;
        }
    
        public int read() throws IOException {
            if (count >= number) {
                return -1;
            }
            int limit = mappedBufArray[count].limit();
            int position = mappedBufArray[count].position();
            if (limit - position > arraySize) {
                array = new byte[arraySize];
                mappedBufArray[count].get(array);
                return arraySize;
            } else {// 本内存文件映射最后一次读取数据
                array = new byte[limit - position];
                mappedBufArray[count].get(array);
                if (count < number) {
                    count++;// 转换到下一个内存文件映射
                }
                return limit - position;
            }
        }
    
        public void close() throws IOException {
            fileIn.close();
            array = null;
        }
    
        public byte[] getArray() {
            return array;
        }
    
        public long getFileLength() {
            return fileLength;
        }
    
        public static void main(String[] args) throws IOException {
            MappedBiggerFileReader reader = new MappedBiggerFileReader("/home/zfh/movie.mkv", 65536);
            long start = System.nanoTime();
            while (reader.read() != -1) ;
            long end = System.nanoTime();
            reader.close();
            System.out.println("MappedBiggerFileReader: " + (end - start));
        }
    }
    • 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

    3. 运行结果比较

    用上面三种方法读取1GB文件,运行结果如下

    StreamFileReader:  11494900386
    ChannelFileReader: 11329346316
    MappedFileReader:  11169097480
    • 1
    • 2
    • 3

    读取10GB文件,运行结果如下

    StreamFileReader:       194579779394
    ChannelFileReader:      190430242497
    MappedBiggerFileReader: 186923035795
    • 1
    • 2
    • 3
  • 相关阅读:
    Atitit s2018.6 s6 doc list on com pc.docx Atitit s2018.6 s6 doc list on com pc.docx  Aitit algo fix 算法系列补充.docx Atiitt 兼容性提示的艺术 attilax总结.docx Atitit 应用程序容器化总结 v2 s66.docx Atitit file cms api
    Atitit s2018.5 s5 doc list on com pc.docx  v2
    Atitit s2018.5 s5 doc list on com pc.docx  Acc 112237553.docx Acc baidu netdisk.docx Acc csdn 18821766710 attilax main num.docx Atiitt put post 工具 开发工具dev tool test.docx Atiitt 腾讯图像分类相册管家.docx
    Atitit s2018 s4 doc list dvchomepc dvccompc.docx .docx s2018 s4 doc compc dtS44 s2018 s4 doc dvcCompc dtS420 s2018 s4f doc homepc s2018 s4 doc compc dtS44(5 封私信 _ 44 条消息)WebSocket 有没有可能取代 AJAX
    Atitit s2018 s3 doc list alldvc.docx .docx s2018 s3f doc compc s2018 s3f doc homepc sum doc dvcCompc dtS312 s2018 s3f doc compcAtitit PathUtil 工具新特性新版本 v8 s312.docx s2018 s3f doc compcAtitit 操作日
    Atitit s2018.2 s2 doc list on home ntpc.docx  Atiitt uke制度体系 法律 法规 规章 条例 国王诏书.docx Atiitt 手写文字识别 讯飞科大 语音云.docx Atitit 代码托管与虚拟主机.docx Atitit 企业文化 每日心灵 鸡汤 值班 发布.docx Atitit 几大研发体系对比 Stage-Gat
    Atitit 文员招募规范 attilax总结
    Atitit r2017 r6 doc list on home ntpc.docx
    atitit r9 doc on home ntpc .docx
    Atitit.如何文章写好 论文 文章 如何写好论文 技术博客 v4
  • 原文地址:https://www.cnblogs.com/firstdream/p/8667871.html
Copyright © 2011-2022 走看看