最新补充:发现把文件映射为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是比较耗时的,比文件分割和合并都慢。而合并文件比分割文件要慢很多,这个是什么原理我也不是很清楚。