java.nio.channels.FileChannel.map读取超过2G文件的解决方案

问题来源:

在实现文件分片上传,需要将文件md5加密,其中加密用到java.nio.channels.FileChannel.map读取文件。FileChannel.map源码如下:

public MappedByteBuffer map(MapMode var1, long var2, long var4) throws IOException {
        this.ensureOpen();
        if (var1 == null) {
            throw new NullPointerException("Mode is null");
        } else if (var2 < 0L) {
            throw new IllegalArgumentException("Negative position");
        } else if (var4 < 0L) {
            throw new IllegalArgumentException("Negative size");
        } else if (var2 + var4 < 0L) {
            throw new IllegalArgumentException("Position + size overflow");
        } else if (var4 > 2147483647L) {
            throw new IllegalArgumentException("Size exceeds Integer.MAX_VALUE");
        } else {
            byte var6 = -1;
            if (var1 == MapMode.READ_ONLY) {
                var6 = 0;
            } else if (var1 == MapMode.READ_WRITE) {
                var6 = 1;
            } else if (var1 == MapMode.PRIVATE) {
                var6 = 2;
            }

            assert var6 >= 0;

            if (var1 != MapMode.READ_ONLY && !this.writable) {
                throw new NonWritableChannelException();
            } else if (!this.readable) {
                throw new NonReadableChannelException();
            } else {
                long var7 = -1L;
                int var9 = -1;

                try {
                    this.begin();
                    var9 = this.threads.add();
                    if (!this.isOpen()) {
                        Object var34 = null;
                        return (MappedByteBuffer)var34;
                    } else {
                        MappedByteBuffer var37;
                        long var10;
                        int var12;
                        synchronized(this.positionLock) {
                            long var14;
                            do {
                                var14 = this.nd.size(this.fd);
                            } while(var14 == -3L && this.isOpen());

                            if (!this.isOpen()) {
                                var37 = null;
                                return var37;
                            }

                            MappedByteBuffer var17;
                            if (var14 < var2 + var4) {
                                if (!this.writable) {
                                    throw new IOException("Channel not open for writing - cannot extend file to required size");
                                }

                                int var16;
                                do {
                                    var16 = this.nd.truncate(this.fd, var2 + var4);
                                } while(var16 == -3 && this.isOpen());

                                if (!this.isOpen()) {
                                    var17 = null;
                                    return var17;
                                }
                            }

                            if (var4 == 0L) {
                                var7 = 0L;
                                FileDescriptor var38 = new FileDescriptor();
                                if (this.writable && var6 != 0) {
                                    var17 = Util.newMappedByteBuffer(0, 0L, var38, (Runnable)null);
                                    return var17;
                                }

                                var17 = Util.newMappedByteBufferR(0, 0L, var38, (Runnable)null);
                                return var17;
                            }

                            var12 = (int)(var2 % allocationGranularity);
                            long var36 = var2 - (long)var12;
                            var10 = var4 + (long)var12;

                            try {
                                var7 = this.map0(var6, var36, var10);
                            } catch (OutOfMemoryError var31) {
                                System.gc();

                                try {
                                    Thread.sleep(100L);
                                } catch (InterruptedException var30) {
                                    Thread.currentThread().interrupt();
                                }

                                try {
                                    var7 = this.map0(var6, var36, var10);
                                } catch (OutOfMemoryError var29) {
                                    throw new IOException("Map failed", var29);
                                }
                            }
                        }

                        FileDescriptor var13;
                        try {
                            var13 = this.nd.duplicateForMapping(this.fd);
                        } catch (IOException var28) {
                            unmap0(var7, var10);
                            throw var28;
                        }

                        assert IOStatus.checkAll(var7);

                        assert var7 % allocationGranularity == 0L;

                        int var35 = (int)var4;
                        FileChannelImpl.Unmapper var15 = new FileChannelImpl.Unmapper(var7, var10, var35, var13);
                        if (this.writable && var6 != 0) {
                            var37 = Util.newMappedByteBuffer(var35, var7 + (long)var12, var13, var15);
                            return var37;
                        } else {
                            var37 = Util.newMappedByteBufferR(var35, var7 + (long)var12, var13, var15);
                            return var37;
                        }
                    }
                } finally {
                    this.threads.remove(var9);
                    this.end(IOStatus.checkAll(var7));
                }
            }
        }
    }

在对参数var4的校验中,只允许最大值为:2147483647L,也就是:Integer.MAX_VALUE。如果文件大于Integer.MAX_VALUE就会抛出“Size exceeds Integer.MAX_VALUE”的异常。所有我们改造,循环读取处理。

解决方案

对于超大文件,需要进行分割成小于Integer.MAX_VALUE,循环处理,具体实现如下:

private static String doMessageDigestUpdate(File file, MessageDigest digest) {
        FileInputStream inputStream = null;
        FileChannel channel = null;
        try {
            inputStream = new FileInputStream(file);
            channel = inputStream.getChannel();

            long position = 0;
            long remaining = file.length();
            MappedByteBuffer byteBuffer = null;
            while (remaining > 0) {
                long size = Integer.MAX_VALUE / 2;
                if (size > remaining) {
                    size = remaining;
                }
                byteBuffer = channel.map(FileChannel.MapMode.READ_ONLY, position, size);
                digest.update(byteBuffer);
                position += size;
                remaining -= size;
                unMapBuffer(byteBuffer, channel.getClass());
            }
            return DigestUtils.md5Hex(digest.digest());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (channel != null) {
                    channel.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * JDK不提供MappedByteBuffer的释放,但是MappedByteBuffer在Full GC时才被回收,通过手动释放的方式让其回收
     *
     * @param buffer
     */
    private static void unMapBuffer(MappedByteBuffer buffer, Class channelClass) throws IOException {
        if (buffer == null) {
            return;
        }

        Throwable throwable = null;
        try {
            Method unmap = channelClass.getDeclaredMethod("unmap", MappedByteBuffer.class);
            unmap.setAccessible(true);
            unmap.invoke(channelClass, buffer);
        } catch (NoSuchMethodException e) {
            throwable = e;
        } catch (IllegalAccessException e) {
            throwable = e;
        } catch (InvocationTargetException e) {
            throwable = e;
        }

        if (throwable != null) {
            throw new IOException("MappedByte buffer unmap error", throwable);
        }
    }

经过处理后测试传了超过4G的文件成功:

你可能感兴趣的:(java,java,nio,开发语言)