使用openpgpjs库流式解密大文件时内存占用过高问题求助
问题分析与解决方案
问题根源
- 全量内存缓存解密数据:你的
webStreamToBlob函数通过循环读取流的所有chunk并存储在数组中,最终一次性转换为Blob。对于大文件,这会导致整个文件内容加载到内存中,直接引发内存占用过高。 - 多余的Blob转换:
fileToStream函数将File对象先转为Blob再获取stream,而File本身就是Blob的子类,直接调用file.stream()即可,多余的转换会增加不必要的内存开销。 - 无效配置与冗余状态存储:解密时配置
preferredCompressionAlgorithm是无效的(该配置仅用于加密);同时将解密后的完整File对象存储到React状态中,进一步加剧内存压力。
优化后的代码实现
以下是修复内存问题的完整代码:
import React, { useState } from "react"; import * as openpgp from "openpgp"; const FileEncrypter = () => { const [file, setFile] = useState(null); const [password, setPassword] = useState(""); const [encryptedFile, setEncryptedFile] = useState(null); const handleFileChange = (e) => { const selectedFile = e.target.files[0]; setFile(selectedFile); }; const handlePasswordChange = (e) => { setPassword(e.target.value); }; // 直接使用File的原生stream方法,无需转Blob const fileToStream = (file) => { return file.stream(); } const streamEncryptFile = async (fileToEncrypt) => { const fileName = `${file.name}.pgp`; const readableStream = fileToStream(fileToEncrypt); const message = await openpgp.createMessage({ binary: readableStream }); const encryptedStream = await openpgp.encrypt({ message, passwords: [password], format: 'binary', config: { preferredCompressionAlgorithm: openpgp.enums.compression.zlib } }); // 用浏览器原生Response高效转换流到Blob const blob = await new Response(encryptedStream).blob(); console.log("Encrypt Output Size: ", formatBytes(blob.size)); return new File([blob], fileName, { type: 'application/octet-stream' }); } // 重构解密逻辑:直接流式处理,避免缓存全量数据 const handleDecryptClick = async () => { if (!encryptedFile || !password) return; const readableStream = fileToStream(encryptedFile); const message = await openpgp.readMessage({ binaryMessage: readableStream }); const decrypted = await openpgp.decrypt({ message, passwords: [password], format: 'binary' }); // 直接基于解密流创建下载,无需存储完整文件到内存/状态 const blob = await new Response(decrypted.data).blob(); const downloadUrl = URL.createObjectURL(blob); const downloadLink = document.createElement('a'); downloadLink.href = downloadUrl; downloadLink.download = file.name; downloadLink.click(); // 及时释放URL对象,避免内存泄漏 URL.revokeObjectURL(downloadUrl); }; const formatBytes = (bytes, decimals = 2) => { if (!+bytes) return '0 Bytes' const k = 1024 const dm = decimals < 0 ? 0 : decimals const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] const i = Math.floor(Math.log(bytes) / Math.log(k)) return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}` } const handleEncryptClick = async () => { if (!file || !password) return; const encrypted = await streamEncryptFile(file); setEncryptedFile(encrypted); }; return ( <div> <input type="file" onChange={handleFileChange} /> <input type="text" placeholder="Password" onChange={handlePasswordChange} /> <button onClick={handleEncryptClick}>Encrypt</button> {encryptedFile && <button onClick={handleDecryptClick}>Decrypt & Download</button>} <br /> {encryptedFile && ( <a href={URL.createObjectURL(encryptedFile)} download={`${file.name}.pgp`}> Download encrypted file </a> )} </div> ); }; export default FileEncrypter;
关键优化点说明
- 简化流转换:
fileToStream直接返回file.stream(),去掉多余的Blob转换,减少内存开销。 - 高效流转Blob:使用
new Response(stream).blob()替代手动读取所有chunk,浏览器内部会更高效地处理流数据,减少内存碎片化。 - 避免全量内存缓存:解密后直接创建下载链接,不将完整文件存储到React状态或内存数组中,而是让浏览器流式处理下载过程。
- 及时释放资源:调用
URL.revokeObjectURL()释放下载链接占用的内存,避免内存泄漏。 - 移除无效配置:解密时去掉
preferredCompressionAlgorithm配置,该选项仅对加密过程生效。
内容的提问来源于stack exchange,提问作者Sumit Sahoo
相关产品推荐
相关产品推荐

