在实现文件分片上传,需要将文件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的文件成功: