如何使用 JavaScript 压缩文件?

Posted

技术标签:

【中文标题】如何使用 JavaScript 压缩文件?【英文标题】:How to Zip files using JavaScript? 【发布时间】:2021-12-14 11:50:26 【问题描述】:

有没有办法使用 javascript 压缩文件?例如,在 Yahoo 邮件中,当您选择从电子邮件中下载所有附件时,它会被压缩并下载到单个 zip 文件中。 JavaScript 有能力做到这一点吗?如果有,请提供编码示例。

我找到了这个名为 jszip 的库来完成这项任务,但它存在已知和未解决的问题。

我该如何解决这个问题?

【问题讨论】:

***.com/questions/2095697/…的可能重复 @kvc 我也看到了,但我的问题是压缩文件。不解压。所以想把它作为另一个帖子问:) 您可以创建一个 Windows shell javascript 来执行此操作,但您无法在浏览器中执行此操作。 【参考方案1】:

JSZip 多年来一直在更新。现在你可以在its GitHub repo找到它

可以和FileSaver.js一起使用

您可以使用 npm 安装它们:

npm install jszip --save
npm install file-saver --save

然后导入并使用它们:

import JSZip from 'jszip';
import FileSaver from 'file-saver';

const zip = new JSZip();
zip.file('idlist.txt', 'PMID:29651880\r\nPMID:29303721');
zip.generateAsync( type: 'blob' ).then(function (content) 
    FileSaver.saveAs(content, 'download.zip');
);

然后您将下载一个名为 download.zip 的 zip 文件,一旦您将其解压缩,您可以在一个名为 idlist.txt 的文件中找到它,该文件有两行:

PMID:29651880
PMID:29303721

并且供大家参考,我用以下浏览器测试过,都通过了:

Firefox 59.0.2 (Windows 10) Chrome 65.0.3325.181 (Windows 10) Microsoft Edge 41.16299.371.0 (Windows 10) Internet Explorer 11.0.60 (Windows 10) Opera 52 (Mac OSX 10.13) Safari 11 (Mac OSX 10.13)

【讨论】:

这对我不起作用。我明白了:UnhandledPromiseRejectionWarning: Error: blob is not supported by this platform 另外,如何压缩同一目录中已经存在的文件?【参考方案2】:

如果你不关心 IE,client-zip 比 JSZip 更快更小,旨在解决这个问题(是的,这是我的库的一个无耻但完全相关的插件?)。

您可以这样做(例如,files 可以是 fetch 响应的数组,尽管支持的输入很多):

import  downloadZip  from "client-zip/index.js"
import FileSaver from "file-saver"

const content = await downloadZip(files).blob()
FileSaver.saveAs(content, "download.zip");

与 Yuci 的答案中的用法基本相同,但为 2020 年更新。

【讨论】:

但是client-zip不压缩数据 这不一定是坏事。事实上,许多浏览器会自动解压缩下载的档案,因此压缩只会增加 CPU 负载(用于压缩,然后再次解压缩),根本不会节省带宽或存储空间。此处压缩的主要原因似乎是为了实现多个附件的一键下载。 没说那是件坏事。但是您提到“正是这个问题”,听起来它应该做与 JSZip 相同的事情。 Client-zip 看起来很棒。 好的。需要明确的是:我没有声称 client-zip 解决了与 JSZip 相同的一组问题,只是这个问题中的一个。 更新的客户端压缩似乎更容易,无需文件保护程序【参考方案3】:

通过使用 JSZIP,我们可以在 JavaScript 中生成和下载 zip 文件。为此,您必须按照以下步骤操作

    从http://github.com/Stuk/jszip/zipball/master下载jszip zip文件 解压 zip 并在 dist 文件夹中找到 jszip.js 文件

    在您的 html 文件中导入 jszip.js 文件,如下所示

    <script type="text/javascript" src="jszip.js"></script>
    

    在您的代码中添加以下函数并调用它

    onClickDownload: function () 
        var zip = new JSZip();
        for (var i = 0; i < 5; i++) 
            var txt = 'hello';
            zip.file("file" + i + ".txt", txt);
        
        zip.generateAsync(
            type: "base64"
        ).then(function(content) 
            window.location.href = "data:application/zip;base64," + content;
        );       
    
    

    您可以从我的 git 存储库下载示例代码 (GIT link)

【讨论】:

在 Base64 中生成输出是一个非常糟糕的主意,使用这样的数据 URI 甚至是更糟糕的主意。浪费内存和CPU。请改用 Blob URL。 太好了,我将类型更改为“blob”,它就像一个魅力!我在页面上使用了锚链接来触发下载,而不是更改页面 url。 var uriContent = URL.createObjectURL(contentBlob); var lnkDownload = document.getElementById('lnkDownload'); lnkDownload.download = '我的下载.zip'; lnkDownload.href = uriContent; lnkDownload.click();【参考方案4】:

我建议直接使用 Node's 内置库 Zlib,其中包括图像;使用“缓冲区”以 base 64 编码。而不是使用 npm 包。原因是:

Zlib 是一个 Node 原生库 - 近 10 年来一直保持最新状态 - 所以那里的证明可以长期支持 Node 允许您使用缓冲区 - 即您可以将文本字符串/图像转换为原始二进制数据并使用 Zlib 以这种方式压缩它 轻松压缩和解压缩大文件 - 利用节点流以 MB 或 GB 为单位压缩文件

您使用的是 jszip,这让我猜想您使用的是 npm 和 node;假设您已正确设置环境,即全局安装节点。

例子:input.txt压缩成input.txt.gz

const zlib = require('zlib');
const fs = require('fs');
const gzip = zlib.createGzip();
const input = fs.createReadStream('input.txt');
const output = fs.createWriteStream('input.txt.gz');

input.pipe(gzip).pipe(output);

第 1 步:所以你 require 来自 node 的每个本机模块 - require 是 ES5 的一部分。前面提到的 Zlib 和 fs 模块,文件系统模块。

const zlib = require('zlib');
const fs = require('fs');

第 2 步: fs 模块允许您创建 readstream,专门调用该模块来读取数据块。这将返回一个 readstream 对象;可读流

const input = fs.createReadStream(FILE PATH HERE);

__注意:这个 readstream 对象会再次被管道化; readsteam 对象上的这种管道链接可以无休止地发生,使管道非常灵活。

ReadStream.pipe(DoesSomething).pipe(SomethingElse).pipe(ConvertToWriteStream)

第 3 步: 经过管道和压缩的 readstream 对象随后被转换为 writestream 对象。

const output = fs.createWriteStream('input.txt.gz');

input.pipe(gzip).pipe(output); // returned filename input.txt.gz, within local directory

因此,此库允许您轻松输入文件路径并决定压缩文件的位置。如果需要,您也可以选择反向操作。

【讨论】:

感谢分享。但是,我猜这个问题寻求的是浏览器端的解决方案,而不是服务器端的解决方案。在浏览器端有什么想法吗?谢谢。【参考方案5】:

我开发了一种使用 JavaScript 生成 zip 文件的新解决方案。解决方案在公共领域。

Zip 类。

Zip 

    constructor(name) 
        this.name = name;
        this.zip = new Array();
        this.file = new Array();
    
    
    dec2bin=(dec,size)=>dec.toString(2).padStart(size,'0');
    str2dec=str=>Array.from(new TextEncoder().encode(str));
    str2hex=str=>[...new TextEncoder().encode(str)].map(x=>x.toString(16).padStart(2,'0'));
    hex2buf=hex=>new Uint8Array(hex.split(' ').map(x=>parseInt(x,16)));
    bin2hex=bin=>(parseInt(bin.slice(8),2).toString(16).padStart(2,'0')+' '+parseInt(bin.slice(0,8),2).toString(16).padStart(2,'0'));
    
    reverse=hex=>
        let hexArray=new Array();
        for(let i=0;i<hex.length;i=i+2)hexArray[i]=hex[i]+''+hex[i+1];
        return hexArray.filter((a)=>a).reverse().join(' '); 
    
    
    crc32=r=>
        for(var a,o=[],c=0;c<256;c++)
            a=c;
            for(var f=0;f<8;f++)a=1&a?3988292384^a>>>1:a>>>1;
            o[c]=a;
        
        for(var n=-1,t=0;t<r.length;t++)n=n>>>8^o[255&(n^r[t])];
        return this.reverse(((-1^n)>>>0).toString(16).padStart(8,'0'));
    
    
    fecth2zip(filesArray,folder='')
        filesArray.forEach(fileUrl=>
            let resp;               
            fetch(fileUrl).then(response=>
                resp=response;
                return response.arrayBuffer();
            ).then(blob=>
                new Response(blob).arrayBuffer().then(buffer=>
                    console.log(`File: $fileUrl load`);
                    let uint=[...new Uint8Array(buffer)];
                    uint.modTime=resp.headers.get('Last-Modified');
                    uint.fileUrl=`$this.name/$folder$fileUrl`;                            
                    this.zip[fileUrl]=uint;
                );
            );             
        );
    
    
    str2zip(name,str,folder)
        let uint=[...new Uint8Array(this.str2dec(str))];
        uint.name=name;
        uint.modTime=new Date();
        uint.fileUrl=`$this.name/$folder$name`;
        this.zip[uint.fileUrl]=uint;
    
    
    files2zip(files,folder)
        for(let i=0;i<files.length;i++)
            files[i].arrayBuffer().then(data=>
                let uint=[...new Uint8Array(data)];
                uint.name=files[i].name;
                uint.modTime=files[i].lastModifiedDate;
                uint.fileUrl=`$this.name/$folder$files[i].name`;
                this.zip[uint.fileUrl]=uint;                            
            );
        
    
    
    makeZip()
        let count=0;
        let fileHeader='';
        let centralDirectoryFileHeader='';
        let directoryInit=0;
        let offSetLocalHeader='00 00 00 00';
        let zip=this.zip;
        for(const name in zip)
            let modTime=()=>
                lastMod=new Date(zip[name].modTime);
                hour=this.dec2bin(lastMod.getHours(),5);
                minutes=this.dec2bin(lastMod.getMinutes(),6);
                seconds=this.dec2bin(Math.round(lastMod.getSeconds()/2),5);
                year=this.dec2bin(lastMod.getFullYear()-1980,7);
                month=this.dec2bin(lastMod.getMonth()+1,4);
                day=this.dec2bin(lastMod.getDate(),5);                      
                return this.bin2hex(`$hour$minutes$seconds`)+' '+this.bin2hex(`$year$month$day`);
                               
            let crc=this.crc32(zip[name]);
            let size=this.reverse(parseInt(zip[name].length).toString(16).padStart(8,'0'));
            let nameFile=this.str2hex(zip[name].fileUrl).join(' ');
            let nameSize=this.reverse(zip[name].fileUrl.length.toString(16).padStart(4,'0'));
            let fileHeader=`50 4B 03 04 14 00 00 00 00 00 $modTime $crc $size $size $nameSize 00 00 $nameFile`;
            let fileHeaderBuffer=this.hex2buf(fileHeader);
            directoryInit=directoryInit+fileHeaderBuffer.length+zip[name].length;
            centralDirectoryFileHeader=`$centralDirectoryFileHeader50 4B 01 02 14 00 14 00 00 00 00 00 $modTime $crc $size $size $nameSize 00 00 00 00 00 00 01 00 20 00 00 00 $offSetLocalHeader $nameFile `;
            offSetLocalHeader=this.reverse(directoryInit.toString(16).padStart(8,'0'));
            this.file.push(fileHeaderBuffer,new Uint8Array(zip[name]));
            count++;
        
        centralDirectoryFileHeader=centralDirectoryFileHeader.trim();
        let entries=this.reverse(count.toString(16).padStart(4,'0'));
        let dirSize=this.reverse(centralDirectoryFileHeader.split(' ').length.toString(16).padStart(8,'0'));
        let dirInit=this.reverse(directoryInit.toString(16).padStart(8,'0'));
        let centralDirectory=`50 4b 05 06 00 00 00 00 $entries $entries $dirSize $dirInit 00 00`;
        this.file.push(this.hex2buf(centralDirectoryFileHeader),this.hex2buf(centralDirectory));                
        let a = document.createElement('a');
        a.href = URL.createObjectURL(new Blob([...this.file],type:'application/octet-stream'));
        a.download = `$this.name.zip`;
        a.click();              
    

然后,创建一个新对象 Zip。

  z=new Zip('myZipFileName');

你可以:

使用 fecth2zip(filesArray,folder) 将目录中的文件加载到 zip 对象。
  filesArray=[
    'file01.ext',
    'file02.ext',
    'file...'
  ];
  z.fecth2zip(filesArray,'public/');
使用 str2zip(nameFile,content,directory) 从字符串创建新文件。
  z.str2zip('test.txt','content','public/teste/');

或上传到 zip。

将 onchange 事件放入输入文件并将文件发送到函数 files2zip(this.files)。

  <input type="file" onchange="z.files2zip(this.files)" value='files' multiple>

将所有对象放入 Zip 对象文件后,下载即可。

  <input type="button" onclick="z.makeZip()" value='Zip'>

该课程也可以在这里找到:https://github.com/pwasystem/zip/

【讨论】:

【参考方案6】:

使用新的 HTML5 文件 API 和类型化数组,您几乎可以在 JavaScript 中做任何您想做的事情。但是,浏览器支持不会很好。我猜这就是您所说的“未解决的问题”。我建议暂时在服务器上进行。例如,在 php 中,您可以使用 this extension.

【讨论】:

这个答案是正确的,PHP 会达到它的极限,但当文件变大并且用户数也会变大时。

以上是关于如何使用 JavaScript 压缩文件?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Javascript 下载、压缩和保存多个文件并取得进展?

如何压缩我的 JavaScript 和 CSS 文件?

使用 javascript 压缩文件 - 损坏的存档

如何从 JavaScript 压缩 WebSQL 数据库

JavaScript中怎么获取指定文件的大小

Python压缩JS文件,重点是 slimit