专栏名称: 安卓开发精选
伯乐在线旗下账号,分享安卓应用相关内容,包括:安卓应用开发、设计和动态等。
目录
相关文章推荐
郭霖  ·  Android Resource资源管理 ·  2 天前  
鸿洋  ·  App前台,Activity会被回收吗? ·  2 天前  
郭霖  ·  一文了解 Gradle 插件 ·  5 天前  
stormzhang  ·  普通人靠利息躺平,可能吗? ·  5 天前  
51好读  ›  专栏  ›  安卓开发精选

Android大文件上传秒传之MD5篇(下)

安卓开发精选  · 公众号  · android  · 2016-11-23 22:50

正文

(点击上方公众号,可快速关注)


来源:伯乐在线专栏作者 - Code4Android 

链接:http://android.jobbole.com/85154/

点击 → 了解如何加入专栏作者


接上文


RandomAccessFile 方式


/**

     * RandomAccessFile 获取文件的MD5值

     *

     * @param file 文件路径

     * @return md5

     */

    public static String getFileMd53(File file) {

        MessageDigest messageDigest;

        RandomAccessFile randomAccessFile = null;

        try {

            messageDigest = MessageDigest.getInstance("MD5");

            if (file == null) {

                return "";

            }

            if (!file.exists()) {

                return "";

            }

            randomAccessFile=new RandomAccessFile(file,"r");

            byte[] bytes=new byte[1024*1024*10];

            int len=0;

            while ((len=randomAccessFile.read(bytes))!=-1){

                messageDigest.update(bytes,0, len);

            }

            BigInteger bigInt = new BigInteger(1, messageDigest.digest());

            String md5 = bigInt.toString(16);

            while (md5.length() 32) {

                md5 = "0" + md5;

            }

            return md5;

        } catch (NoSuchAlgorithmException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        } catch (FileNotFoundException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        } catch (IOException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        } finally {

            try {

                if (randomAccessFile != null) {

                    randomAccessFile.close();

                    randomAccessFile = null;

                }

            } catch (IOException e) {

                // TODO Auto-generated catch block

                e.printStackTrace();

            }

        }

        return "";

    }


性能对比


我们选了一个小的文件,大概1M左右,观察执行时间


11-09 11:49:20.210 12678-12678/com.example.xh I/System.out: FileInputStream执行时间:179

11-09 11:49:20.266 12678-12678/com.example.xh I/System.out: FileChannel执行时间:55

11-09 11:49:20.322 12678-12678/com.example.xh I/System.out: RandomAccessFile执行时间:58


但是我选择大概10M的文件FileChannel+MappedByteBuffer性能并不明显,最后通过查询资料学习发现MappedByteBuffer这个东西很可怕,这个回收是不确定的,在手机上测试FileChannel效率并不是最好的。如果要计算一个几百兆的大文件,发现FileChannel+MappedByteBuffer还很容易OOM,原因就是MappedByteBuffer内存占用、文件关闭不确定,被其打开的文件只有在垃圾回收的才会被关闭,而且这个时间点是不确定的。当文件达到100M时就出现OOM如下


FATAL EXCEPTION: main

java.lang.OutOfMemoryError

at java.security.MessageDigestSpi.engineUpdate(MessageDigestSpi.java:85)

at java.security.MessageDigest.update(MessageDigest.java:369)


所以在Android设备上尽量不要使用nio中的内存映射。在官方文档中有这样的一句话:A mapped byte buffer and the file mapping that it represents remain valid until the buffer itself is garbage-collected.

那么我们来计算一个大文件的MD5,此时我测试的文件是300多兆


11-09 16:06:49.930 3101-3101/com.example.xh I/System.out: FileInputStream执行时间:4219

11-09 16:06:54.490 3101-3101/com.example.xh I/System.out: RandomAccessFile执行时间:2162


通过日志发现RandomAccessFile的效率还是很明显的,此时使用FileChannel+MappedByteBuffer就OOM了,虽然使用了分段映射 也调用了MappedByteBuffer的clear()方法。当然通过日志你肯定明白计算文件MD5值是一个比较耗时的操作,不要再主线程中计算。


计算MD5


我们需要注意对于较大的文件计算MD5,我们不要一次将文件读取然后调用update方法。不然执行update方法时就会出现OOM。我们可以分段读取多次调用update方法,如下


while ((len = fis.read(buffer)) > 0) {

                //该对象通过使用 update()方法处理数据

                messageDigest.update(buffer, 0, len);

            }


你要明白调用执行update并没有计算MD5的值,真正计算的MD5值是调用digest(),该方法返回的是一个byte数组


byte[] bytes = messageDigest.digest();


通常我们一般将MD5用16进制也就是32位表示,所以我们可以将byte数组转化为16进制,此时我们可以使用BigInteger类,他的构造方法可以接收byte数组参数,如下,1表示符号为正数。


BigInteger bigInt = new BigInteger(1, bytes );


BigInteger这个类还提供了一个toString方法该参数可以指定转化数据格式,由于我们转化为16进制,所以参数可以写16,如下


String md5 = bigInt.toString(16);


OK了,MD5的值已经出现了,不过你可能会疑问了,转化为16进制的话,MD5值应该是32位,为何有时候计算的值不是32位,而是31位呢?甚至还可能更少,原因就是digest()返回值的高位包含了0,当然高位0是不写的,所以就出现少位的情况,这也就有了下面的代码,如果不到32位我们再高位补0就好了。


while (md5.length() 32) {

                md5 = "0" + md5;

            }


至此,本篇文章结束,若有不足的地方欢迎指正,谢谢。


专栏作者简介( 点击 → 加入专栏作者 


Code4Android:简介还没来得及写 :)

打赏支持作者写出更多好文章,谢谢!



 关注「安卓开发精选

看更多精选文章
↓↓↓