package com.qiniu.android.utils; import com.qiniu.android.storage.Configuration; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** * 计算文件内容或者二进制数据的etag, etag算法是七牛用来标志数据唯一性的算法。 * 文档:<a href="https://github.com/qiniu/qetag">etag算法</a> */ public final class Etag { /** * 计算二进制数据的etag * * @param data 二进制数据 * @param offset 起始字节索引 * @param length 需要计算的字节长度 * @return 二进制数据的etag */ public static String data(byte[] data, int offset, int length) { try { return stream(new ByteArrayInputStream(data, offset, length), length); } catch (IOException e) { e.printStackTrace(); } // never reach return null; } /** * 计算二进制数据的etag * * @param data 二进制数据 * @return 二进制数据的etag */ public static String data(byte[] data) { return data(data, 0, data.length); } /** * 计算文件内容的etag * * @param file 文件对象 * @return 文件内容的etag * @throws IOException 文件读取异常 */ public static String file(File file) throws IOException { FileInputStream fi = new FileInputStream(file); return stream(fi, file.length()); } /** * 计算文件内容的etag * * @param filePath 文件路径 * @return 文件内容的etag * @throws IOException 文件读取异常 */ public static String file(String filePath) throws IOException { File f = new File(filePath); return file(f); } /** * 计算输入流的etag * * @param in 数据输入流 * @param len 数据流长度 * @return 数据流的etag值 * @throws IOException 文件读取异常 */ public static String stream(InputStream in, long len) throws IOException { if (len == 0) { return "Fto5o-5ea0sNMlW_75VgGJCv2AcJ"; } byte[] buffer = new byte[64 * 1024]; byte[][] blocks = new byte[(int) ((len + Configuration.BLOCK_SIZE - 1) / Configuration.BLOCK_SIZE)][]; for (int i = 0; i < blocks.length; i++) { long left = len - (long) Configuration.BLOCK_SIZE * i; long read = left > Configuration.BLOCK_SIZE ? Configuration.BLOCK_SIZE : left; blocks[i] = oneBlock(buffer, in, (int) read); } return resultEncode(blocks); } /** * 单块计算hash * * @param buffer 数据缓冲区 * @param in 输入数据 * @param len 输入数据长度 * @return 计算结果 * @throws IOException 读取出错 */ private static byte[] oneBlock(byte[] buffer, InputStream in, int len) throws IOException { MessageDigest sha1 = null; try { sha1 = MessageDigest.getInstance("sha-1"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); //never reach return null; } int buffSize = buffer.length; while (len != 0) { int next = buffSize > len ? len : buffSize; //noinspection ResultOfMethodCallIgnored in.read(buffer, 0, next); sha1.update(buffer, 0, next); len -= next; } return sha1.digest(); } /** * 合并结果 * * @param sha1s 每块计算结果的列表 * @return 最终的结果 */ private static String resultEncode(byte[][] sha1s) { byte head = 0x16; byte[] finalHash = sha1s[0]; int len = finalHash.length; byte[] ret = new byte[len + 1]; if (sha1s.length != 1) { head = (byte) 0x96; MessageDigest sha1 = null; try { sha1 = MessageDigest.getInstance("sha-1"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); // never reach return null; } for (byte[] s : sha1s) { sha1.update(s); } finalHash = sha1.digest(); } ret[0] = head; System.arraycopy(finalHash, 0, ret, 1, len); return UrlSafeBase64.encodeToString(ret); } }