如何利用已打开的Java InputStream同时复制文件并计算哈希值?
能不能用同一个已打开的InputStream同时执行复制文件与计算校验和?
答案是可以,但不能像你现在这样分开调用getFileSHA2Checksum和transferTo两个方法——因为InputStream是单向流式读取的,一旦读完一次,流的指针就会移动到末尾,后续再读取就拿不到数据了。
问题根源
你当前的两个方法都是独立读取InputStream:
getFileSHA2Checksum会把InputStream的内容全部读完来计算校验和- 之后再调用
transferTo时,InputStream已经没有数据可以读取了,复制操作会生成空文件
正确实现方式:一次读取同时完成两个操作
最合理的做法是在同一次循环读取中,既把数据写入目标文件,又更新校验和的MessageDigest,这样只需要读取一次InputStream,效率更高,也避免了流指针的问题。
修改后的Uploader类示例:
public class Uploader { private static final String SHA_256 = "SHA-256"; private File file2; // 确保这个变量已正确初始化 // 新增:同时完成文件复制与校验和计算的方法 public String transferAndCalculateChecksum(InputStream fis) throws IOException { MessageDigest md; try { md = MessageDigest.getInstance(SHA_256); } catch (NoSuchAlgorithmException e) { return "KO"; } byte[] buffer = new byte[8192]; // 8KB缓冲区,可根据需求调整 int bytesRead; // 用try-with-resources自动关闭FileOutputStream try (FileOutputStream fos = new FileOutputStream(file2)) { while ((bytesRead = fis.read(buffer)) != -1) { md.update(buffer, 0, bytesRead); // 更新校验和 fos.write(buffer, 0, bytesRead); // 写入文件 } } // 将字节数组形式的校验和转换成十六进制字符串 StringBuilder checksumSb = new StringBuilder(); for (byte b : md.digest()) { checksumSb.append(String.format("%02x", b)); } return checksumSb.toString(); } // 保留原有方法,但注意:不可用同一个InputStream连续调用这些方法 public String getFileSHA2Checksum(InputStream fis) throws IOException { try { MessageDigest md5Digest = MessageDigest.getInstance(SHA_256); return getFileChecksum(md5Digest, fis); } catch (NoSuchAlgorithmException e) { return "KO"; } } public void transferTo(InputStream fis) throws IOException { FileUtils.copyInputStreamToFile(fis, file2); } private String getFileChecksum(MessageDigest digest, InputStream fis) throws IOException { byte[] byteArray = new byte[1024]; int bytesCount = 0; while ((bytesCount = fis.read(byteArray)) != -1) { digest.update(byteArray, 0, bytesCount); } byte[] bytes = digest.digest(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1)); } return sb.toString(); } }
小文件的折中方案(不推荐用于大文件)
如果你的文件体积很小,可以先把InputStream的全部内容读取到字节数组中,再用ByteArrayInputStream包装成可重复读取的流,分别传入两个方法:
// 仅适用于小文件,大文件会占用过多内存 byte[] fileContent = IOUtils.toByteArray(fis); String checksum = uploader.getFileSHA2Checksum(new ByteArrayInputStream(fileContent)); uploader.transferTo(new ByteArrayInputStream(fileContent));
但这种方式会把整个文件加载到内存,对于大文件(比如几百MB以上)会导致内存占用过高,因此不推荐。
内容的提问来源于stack exchange,提问作者Jordi
相关产品推荐
相关产品推荐

