文件下载

460 阅读11分钟

一、字节

1.1 概念

字节(Byte)是计算机信息技术用于计量存储容量的一种计量单位,也表示一些计算机编程语言中的数据类型和语言字符。

一个字节存储8位无符号数,储存的数值范围为0-255。

1.2 Byte与bit

数据存储是以“字节”(Byte)为单位,数据传输大多是以“位”(bit,又名“比特”)为单位,一个位就代表一个0或1(即二进制),每8个位(bit,简写为b)组成一个字节(Byte,简写为B),是最小一级的信息单位。

有控制码或非美标码的文件,通常不能在不同电脑系统间直接交换。这类文件有一个通称,叫“二进制文件”(Binary Files)。

1字节(Byte)=8位(bit)
1KB( Kilobyte,千字节)=1024B
1MB( Megabyte,兆字节)=1024KB
1GB( Gigabyte,吉字节,千兆)=1024MB
1TB( Trillionbyte,万亿字节,太字节)=1024GB
1PB( Petabyte,千万亿字节,拍字节)=1024TB
1EB( Exabyte,百亿亿字节,艾字节)=1024PB
1ZB(Zettabyte,十万亿亿字节,泽字节)=1024EB
1YB( Yottabyte,一亿亿亿字节,尧字节)=1024ZB
1BB( Brontobyte,千亿亿亿字节)=1024YB

二、编码

2.1 概念

在计算机中,所有的数据在存储和运算时都要使用二进制数表示(因为计算机用高电平和低电平分别表示1和0),例如,像a、b、c、d等52个字母(包括大写)以及0、1等数字还有一些常用的符号(例如*、#、@等)在计算机中存储时也要使用二进制数来表示。

而具体用哪些二进制数字表示哪个符号,当然每个人都可以约定自己的一套(这就叫编码),而如果要想互相通信而不造成混乱,那么就必须使用相同的编码规则。美国有关的标准化组织就出台了ASCII编码,统一规定了上述常用符号用哪些二进制数来表示。

2.2 编码标准

文字编码标准主要有ASCIIGB2312GBKUnicode等。

ASCII:编码是美国信息交换标准编码(美标)。

GB2312GBKGB18030:汉字字符编码方案的国家标准。

BIG5码:针对繁体汉字的汉字编码,在台湾、香港的电脑系统中普遍应用。

Unicode:宽字节字符集,它对每个字符都固定使用两个字节即16位表示(普及到东亚国家发展出的编码标准)(UCS4、UTF-8、UTF-16都是Unicode的编码方案。其中UTF-8因可以兼容ASCII而被广泛使用。)。


Base64编码:现有的字符集非常多,常用的有 UTF-8 / GBK 等, 这里面的某些字节在某些传输渠道。比如邮件传输就不支持上面ASCII码中的控制字符, Base64的创建就是为了解决此问题。

Base64内的64是指64个字符, 分别是 A-Z, a-z, 0-9, +, /,Base64 相应的索引表如下:

777.jpg

HTML 中可以嵌入 base64 编码的图片,可以减少网络请求。

浏览器都支持 Data URLs,允许使用base64对图片或其他文件的二进制数据进行编码,就可以将其作为文本字符串嵌入网页中。

Data URLs 由四个部分组成:前缀(data:)、指示数据类型的 MIME 类型、如果非文本则为可选的 base64 标记、数据本身:

data:[<mediatype>][;base64],<data>

<img alt="logo" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...">

但需要注意的是:如果图片较大则不适合使用这种方式,因为图片经过 base64 编码后的字符串至少变大约三分之一,影响HTML 页面加载速度。

2.3 ASCII码与Base64的编解码

Base64有64个字符, 2^6 = 64,所以每个Base64编码字符可以用一个 6位的二进制来表示。这样的话如果有3个字节的二进制, 可以用4位Base64字符表示。

编码流程如下:

  1. ASCII码字符串根据ASCII码对照表转换为二进制数值
  2. 二进制数值按每6位进行划分;
  3. 然后6位二进制转化为十进制根据对照表找到Base64编码字符

777.png

由图可知,Man(3 字节)编码的结果为 TWFu(4 字节),经过 base64 编码后体积会增加 1/3。Man 这个字符串的长度刚好是 3,可以用 4 个 base64 单元来表示。

如果待编码字符串的长度不是3的倍数,为了可以整除6编译出完整的字节数, 就需要使用 0 字节值在末尾补足,使其能够被 3 整除,然后再进行 base64 的编码。

如果有连续6位都是0的话, 就用“=”来表示

777.png

777.jpg

base64编码与解码

  • btoa():该函数能够基于二进制数据 “字符串” 创建一个 base64 编码的 ASCII 字符串。
  • atob(): 该函数能够解码通过 base64 编码的字符串数据。

三、下载相关函数

3.1 Blob

3.1.1 概念

Blob(Binary Large Object)表示二进制类型的大对象。在数据库管理系统中,将二进制数据存储为一个单一个体的集合。Blob 通常是影像、声音或多媒体文件。「在 JavaScript 中 Blob 类型的对象表示不可变的类似文件对象的原始数据。」

Blob 对象含有两个属性:size 和 type。其中 size 属性用于表示数据的大小(以字节为单位),typeMIME 类型的字符串。

777.png

3.1.2 构造函数

777.jpg

var aBlob = new Blob(blobParts, options);
  • blobParts:是一个由 ArrayBufferArrayBufferViewBlobDOMString 等对象构成的数组。DOMStrings 会被编码为 UTF-8

  • options:一个可选的对象,包含以下两个属性:

    • type —— 默认值为 "",代表了将会被放入到 blob 中的数组内容的 MIME 类型。
    • endings —— 默认值为 "transparent",用于指定包含行结束符 \n 的字符串如何被写入。 它是以下两个值中的一个: "native",代表行结束符会被更改为适合宿主操作系统文件系统的换行符,或者 "transparent",代表会保持 blob 中保存的结束符不变。
let myBlobParts = ['<html><h2>Hello Semlinker</h2></html>']; // an array consisting of a single DOMString
let myBlob = new Blob(myBlobParts, {type : 'text/html', endings: "transparent"}); // the blob

console.log(myBlob.size + " bytes size");
// Output: 37 bytes size
console.log(myBlob.type + " is the type");
// Output: text/html is the type

3.2 Object URL

Object URL 是一种伪协议,也被称为 Blob URL。允许 BlobFile 对象用作图像,下载二进制数据链接等的 URL 源。在浏览器中,使用 URL.createObjectURL 方法来创建 Blob URL,该方法接收一个 Blob 对象,并为其创建一个唯一的 URL,形式为 blob:<origin>/<uuid>,对应的示例如下:

blob:https://example.org/40a5fb5a-d56d-4a33-b4e2-0acf6a8e5f641

虽然存储了URL → Blob的映射,但 Blob 本身仍驻留在内存中,浏览器无法释放它。映射在文档卸载时自动清除,因此 Blob 对象随后被释放。但是,如果应用程序寿命很长,那不会很快发生。因此,如果创建了一个Blob URL,即使不再需要该Blob,它也会存在内存中。

针对这个问题,可以调用 URL.revokeObjectURL(url) 方法,从内部映射中删除引用,从而允许删除 Blob(如果没有其他引用),并释放内存。

3.3 ArrayBuffer

ArrayBuffer 对象用来表示**「通用的、固定长度的」原始二进制数据缓冲区。 「ArrayBuffer 不能直接操作,而是要通过类型数组对象 或 DataView对象来操作」,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。

3.4 Unit8Array

Uint8Array 数组类型表示一个 8 位无符号整型数组,创建时内容被初始化为 0。创建完后,可以以**「对象的方式或使用数组下标索引的方式」** 引用数组中的元素。

new Uint8Array(); // ES2017 最新语法
new Uint8Array(length); // 创建初始化为0的,包含length个元素的无符号整型数组
new Uint8Array(typedArray);
new Uint8Array(object);
new Uint8Array(buffer [, byteOffset [, length]]);

3.5 Blob 与 ArrayBuffer

  • 除非需要使用 ArrayBuffer 提供的写入/编辑的能力,否则 Blob 格式可能是最好的。
  • Blob 对象是不可变的,而 ArrayBuffer 是可以通过 TypedArrays 或 DataView 来操作。
  • ArrayBuffer 是存在内存中的,可以直接操作。而 Blob 可以位于磁盘、高速缓存内存和其他不可用的位置。
  • 虽然 Blob 可以直接作为参数传递给其他函数,比如 window.URL.createObjectURL()。但是,可能仍需要 FileReader 之类的 File API 才能与 Blob 一起使用。
  • Blob 与 ArrayBuffer 对象之间是可以相互转化的:
    • 使用 FileReader 的 readAsArrayBuffer() 方法,可以把 Blob 对象转换为 ArrayBuffer 对象;
    • 使用 Blob 构造函数,如 new Blob([new Uint8Array(data]);,可以把 ArrayBuffer 对象转换为 Blob 对象。

四、文件下载

4.1 a标签下载

4.1.1 文件合成

merge-images 这个库提供 mergeImages(images, [options]) 方法,可以实现图片合成的功能。调用该方法后会返回一个 Promise 对象,当异步操作完成后,合成的图片会以 Data URLs 的格式返回。

const mergePicEle = document.querySelector("#mergedPic");
const images = ["/body.png", "/eyes.png", "/mouth.png"].map(
  (path) => "../images" + path
);
let imgDataUrl = null;

async function merge() {
  imgDataUrl = await mergeImages(images);
  mergePicEle.src = imgDataUrl;
}

4.1.2 文件下载

function dataUrlToBlob(base64, mimeType) {
  let bytes = window.atob(base64.split(",")[1]);
  let ab = new ArrayBuffer(bytes.length);
  let ia = new Uint8Array(ab);
  for (let i = 0; i < bytes.length; i++) {
    ia[i] = bytes.charCodeAt(i);
  }
  return new Blob([ab], { type: mimeType });
}

// 保存文件
function saveFile(blob, filename) {
  const a = document.createElement("a");
  a.download = filename;
  a.href = URL.createObjectURL(blob);
  a.click();
  URL.revokeObjectURL(a.href) // 内部映射中删除引用,从而允许删除 Blob,并释放内存
}

4.2 showSaveFilePicker API 下载

showSaveFilePicker 方法支持一个对象类型的可选参数,可包含以下属性:

  • excludeAcceptAllOption:布尔类型,默认值为 false。默认情况下,选择器应包含一个不应用任何文件类型过滤器的选项(由下面的 types 选项启用)。将此选项设置为 true 意味着 types 选项不可用。

  • types:数组类型,表示允许保存的文件类型列表。数组中的每一项是包含以下属性的配置对象:

    • description(可选):用于描述允许保存文件类型类别。
    • accept:是一个对象,该对象的 keyMIME 类型,值是文件扩展名列表。
async function saveFile(blob, filename) {
  try {
    const handle = await window.showSaveFilePicker({
      suggestedName: filename,
      types: [
        {
          description: "PNG file",
          accept: {
            "image/png": [".png"],
          },
        },
        {
          description: "Jpeg file",
          accept: {
            "image/jpeg": [".jpeg"],
          },
         },
      ],
     });
    const writable = await handle.createWritable();
    await writable.write(blob);
    await writable.close();
    return handle;
  } catch (err) {
     console.error(err.name, err.message);
  }
}

function download() {
  if (!imgDataUrl) {
    alert("请先合成图片");
    return;
  }
  const imgBlob = dataUrlToBlob(imgDataUrl, "image/png");
  saveFile(imgBlob, "face.png");
}

a 标签下载 相比,showSaveFilePicker API 允许选择文件的下载目录、选择文件的保存格式和更改存储的文件名称。

4.3 FileSaver 下载

FileSaver.js 是在客户端保存文件的解决方案,非常适合在客户端上生成文件的 Web 应用程序。它是 HTML5 版本的 saveAs() FileSaver 实现,支持大多数主流的浏览器,其兼容性如下图所示:

FileSaver saveAs(
 Blob/File/Url, 
 optional DOMString filename, 
 optional Object { autoBom }
)
// 保存文本
let blob = new Blob(["文本数据!"], { type: "text/plain;charset=utf-8" });
saveAs(blob, "hello.txt");

// 保存线上资源
saveAs("https://z3.ax1x.com/2021/09/08/hbKjAO.png", "image.png");

// 保存 canvas 画布内容
let canvas = document.getElementById("my-canvas");
canvas.toBlob(function(blob) {
  saveAs(blob, "abao.png");
});

4.4 base64 格式下载

Base64 是一种基于 64 个可打印字符来表示二进制数据的表示方法。由于 2⁶ = 64 ,所以每 6 个比特为一个单元,对应某个可打印字符。3 个字节有 24 个比特,对应于 4 个 base64 单元,即 3 个字节可由 4 个可打印字符来表示。相应的转换过程如下图所示:

Base64 常用在处理文本数据的场合,表示、传输、存储一些二进制数据,包括 MIME 的电子邮件及 XML 的一些复杂数据。MIME 格式的电子邮件中,base64 可以用来将二进制的字节序列数据编码成 ASCII 字符序列构成的文本。使用时,在传输编码方式中指定 base64。使用的字符包括大小写拉丁字母各 26 个、数字 10 个、加号 + 和斜杠 /,共 64 个字符,等号 = 用来作为后缀用途。

async function download() {
  const response = await request.get("/file", {
    params: {
      filename: picSelectEle.value + ".png",
    },
  });
  if (response && response.data && response.data.code === 1) {
    const fileData = response.data.data;
    const { name, type, content } = fileData;
    const imgBlob = base64ToBlob(content, type);
    saveAs(imgBlob, name);
  }
}

因为返回的是 base64 格式的图片,所以在调用 FileSaver 提供的 saveAs 方法前,需要将 base64 字符串转换成 blob 对象,该转换是通过以下的 base64ToBlob 函数来完成。

function base64ToBlob(base64, mimeType) {
  let bytes = window.atob(base64);
  let ab = new ArrayBuffer(bytes.length);
  let ia = new Uint8Array(ab);
  for (let i = 0; i < bytes.length; i++) {
    ia[i] = bytes.charCodeAt(i);
  }
  return new Blob([ab], { type: mimeType });
}