最新补充:发现把文件映射为MapedByteBuffer后,即使输入流关闭,通道关闭,它还是引用着文件句柄,这样我们无法删除文件或者重命名,网上有一些解决方法,但是不推荐使用,必竟不是官方出来的方法,所以在官方解决这个问题之前,还是使用普通的方式吧,如下:
object FileUtil {
/** 获取文件md5(异步方式) */
fun getFileMd5(file: File, callback: (String) -> Unit) {
thread {
val fileMd5 = getFileMd5(file)
callback(fileMd5)
}
}
/** 获取文件md5(同步方式) */
fun getFileMd5(file: File): String {
val messageDigest = MessageDigest.getInstance("MD5")
FileInputStream(file).use { fis ->
val buf = ByteArray(8192)
var length: Int
while (fis.read(buf, 0, 8192).also { length = it } != -1) {
messageDigest.update(buf, 0, length)
}
}
// 获取md5签名(16个字节)
val md5Bytes = messageDigest.digest()
// 将byte数组的签名转换为16进制的字符串
val bigInteger = BigInteger(1, md5Bytes) // 把16个字节当成一个无符号的大整数
var md5String = bigInteger.toString(16) // 把大整数转换为16进制的字符串形式
repeat(32 - md5String.length) { // 预防字符串不够32位。1个byte需要两位16进制数,而md5Bytes的长度为16,所以需要32位的16进制数来表示。
md5String = "0$md5String"
}
return md5String
}
}
NIO就是香啊,不但效率高,而且写起来代码也少(注:下面获取md5的代码不要采用,有Bug),示例如下:
object FileUtil {
fun getFileMd5(file: File): String {
// 获取md5签名
val md5Bytes = FileInputStream(file).channel.use { channel ->
val byteBuffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size())
MessageDigest.getInstance("MD5").run {
update(byteBuffer)
digest()
}
}
// 将签名转换为16进制的字符串
val bigInteger = BigInteger(1, md5Bytes) // 把16个字节当成一个无符号的大整数
var md5String = bigInteger.toString(16) // 把大整数转换为16进制的字符串形式
repeat(32 - md5String.length) { // 预防字符串不够32位。1个byte需要两位16进制数,而md5Bytes的长度为16,所以需要32位的16进制数来表示。
md5String = "0$md5String"
}
return md5String
}
fun splitFile(file: File, splitCount: Int): MutableList<File> {
val splitFiles = mutableListOf<File>()
val fileLength = file.length()
val avgSize = fileLength / splitCount
val lastPartSize = fileLength - avgSize * (splitCount - 1)
FileInputStream(file).channel.use { wavChannel ->
for (i in 0 until splitCount) {
val splitFile = File("${file.absolutePath}.$i")
splitFiles.add(splitFile)
FileOutputStream(splitFile).channel.use {
val position = i * avgSize
val count = if (i == splitCount - 1) lastPartSize else avgSize
wavChannel.transferTo(position, count, it)
}
}
}
return splitFiles
}
fun mergeFiles(files: List<File>, targetFile: File) {
FileOutputStream(targetFile, true).channel.use { targetChannel ->
files.forEach { file ->
FileInputStream(file).channel.use { sourceChannel ->
targetChannel.transferFrom(sourceChannel, targetChannel.size(), sourceChannel.size())
}
}
}
}
}
fun main() {
var start = System.currentTimeMillis()
val sourceFile = File("D:\\功夫.HD720高清国语中字版.mp4")
val splitCount = 3
val fileMd5 = FileUtil.getFileMd5(sourceFile)
println("计算原文件md5 = $fileMd5, 所花时间:${System.currentTimeMillis() - start}")
start = System.currentTimeMillis()
val files = FileUtil.splitFile(sourceFile, splitCount)
println("文件分割完成,所花时间:${System.currentTimeMillis() - start}")
start = System.currentTimeMillis()
val newFile = File("D:\\haha.mp4")
FileUtil.mergeFiles(files, newFile)
println("文件合并完成,所花时间:${System.currentTimeMillis() - start}")
start = System.currentTimeMillis()
val newFileMd5 = FileUtil.getFileMd5(newFile)
println("计算新文件md5 = $newFileMd5, 所花时间:${System.currentTimeMillis() - start}")
}
运行结果如下:
计算原文件md5 = 870257cd37f47a81d528e5c871dc3901, 所花时间:3981
文件分割完成,所花时间:615
文件合并完成,所花时间:2610
计算新文件md5 = 870257cd37f47a81d528e5c871dc3901, 所花时间:3711
分割与合并的文件如下:
功夫.HD720高清国语中字版.mp4是一个1.03G的视频文件,分割成了3个,然后又合并为1个。从打印的结果来看,计算md5是比较耗时的,比文件分割和合并都慢。而合并文件比分割文件要慢很多,这个是什么原理我也不是很清楚。