JavaScript 主要用于处理文本数据,但在更复杂的应用(如图像、音频、视频处理)中,二进制数据的操作变得尤为重要。下面将深入探讨 JavaScript 提供的二进制数据操作 API

  • Blob:用于代表不可变的二进制数据(如文件)
  • File:继承自 Blob,代表用户从文件系统选择的具体文件
  • FileReader:用于读取 File 或 Blob 对象的内容
  • ArrayBuffer:代表通用、固定长度的原始二进制数据缓冲区
  • Typed Arrays:提供了一种操作 ArrayBuffer 中数据的方式,支持多种数据类型(如 Uint8Array、Float32Array 等)
  • DataView:允许以任意字节顺序(endianness)读取和写入 ArrayBuffer 中的数据

1. Blob(Binary Large Object)

Blob 代表不可变的二进制数据块,通常用于处理文件、图像、音频等类型的数据。

Blob 的特性

  • 支持二进制数据:可存储文本、图像、音频等数据。
  • 不可变性:创建后 Blob 数据不能被修改。
  • 分块存储:可使用 slice() 方法将 Blob 拆分为多个小块。

创建 Blob

// 从字符串创建 Blob
const text = "Hello, Blob!";
// 为 Blob 指定正确的 MIME 类型,确保数据在使用时被正确解析和处理
const blob = new Blob([text], { type: 'text/plain' });
console.log(blob.size); // 12
console.log(blob.type); // 'text/plain'

// 从二进制数据创建 Blob
const bytes = new Uint8Array([0x48, 0x65, 0x6C, 0x6C, 0x6F]); // 'Hello'
const binaryBlob = new Blob([bytes], { type: 'application/octet-stream' });
console.log(binaryBlob.size); // 5
console.log(binaryBlob.type); // 'application/octet-stream'

Blob 主要方法

  • slice(start, end, contentType): 截取 Blob 的部分数据,生成新的 Blob 片段
  • arrayBuffer(): 将 Blob 转换为 ArrayBuffer,适用于二进制数据操作
  • text(): 将 Blob 转换为字符串,适用于文本数据读取
  • stream(): 将 Blob 转换为字符串,适用于文本数据读取

Blob 的应用

👇 Blob 对象常用于处理文件上传和下载。例如通过 Blob 可以创建一个包含特定数据的文件,然后通过 URL.createObjectURL 将其转换为下载链接

const data = "Sample data for download";
const blob = new Blob([data], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'sample.txt';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);

👇 Blob 也经常用于处理和操作图像数据,例如将 Canvas 内容转换为图像 Blob,然后显示

const canvas = document.getElementById('myCanvas');
canvas.toBlob((blob) => {
  const url = URL.createObjectURL(blob);
  const img = document.createElement('img');
  img.src = url;
  document.body.appendChild(img);
}, 'image/png');

在不需要时使用 URL.revokeObjectURL() 释放对象 URL,防止内存泄漏

👇 在进行网络通信时,Blob 可以作为数据传输的容器,尤其是在使用 fetch API 时传输二进制数据

fetch('https://example.com/api/upload', {
  method: 'POST',
  body: blob,
  headers: {
    'Content-Type': 'text/plain'
  }
}).then(response => {
  return response.json();
}).then(data => {
  console.log(data);
});

2. File

File 继承自 Blob,表示来自用户文件系统的文件,并包含额外的元数据(如文件名、大小和类型)。

File 对象的属性

属性 描述
name 文件名称,包含扩展名
size 文件大小(字节)
type MIME 类型
lastModified 最后修改时间(时间戳)
lastModifiedDate lastModified 的 Date对象
webkitRelativePath 用户选择文件时的相对路径

获取 File 对象

最常见的 File 对象创建方式是通过用户在网页中上传文件。这通常通过 元素或拖放操作实现

<input type="file" id="fileInput">
<script>
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', (event) => {
    const file = event.target.files[0];
    console.log(`文件名: ${file.name}, 文件大小: ${file.size} 字节`);
});
</script>

3. FileReader

FileReader 允许异步读取 FileBlob 内容,支持文本、Data URL 和 ArrayBuffer 读取。

创建 FileReader

FileReader 对象可以通过调用其构造函数直接创建

const reader = new FileReader();

使用 FileReader.abort() 方法可以取消正在进行的读取操作

主要方法

  • readAsArrayBuffer(blob): 读取 Blob 并返回 ArrayBuffer
  • readAsDataURL(blob): 读取 Blob 并返回 data URL(base64 编码的字符串)。
  • readAsText(blob, encoding): 读取 Blob 并返回文本字符串。

主要事件

当调用上述方法后会触发 FileReader 的事件

事件 描述
onloadstart 开始读取操作时触发
onprogress onprogress 读取过程中定期触发,可用于显示进度
onabort 读取操作被中止时触发
onerror 读取过程中发生错误时触发
onload 读取操作成功完成时触发
onloadend 读取操作完成(无论成功或失败)时触发

文件上传与读取内容示例

👇 在用户上传文件后,使用 FileReader 可以提前读取文件内容,进行预览、验证或处理。

<!DOCTYPE html>
<html>
  <head>
    <title>读取文本文件示例</title>
  </head>
  <body>
    <input type="file" id="textFileInput" accept=".txt, .md" />
    <pre id="fileContent"></pre>

    <script>
      const textFileInput = document.getElementById('textFileInput');
      const fileContent = document.getElementById('fileContent');

      textFileInput.addEventListener('change', (event) => {
        const file = event.target.files[0];
        if (file) {
          const reader = new FileReader();

          reader.onload = (e) => {
            fileContent.textContent = e.target.result;
          };

          reader.onerror = () => {
            fileContent.textContent = '读取文件时发生错误。';
          };

          reader.readAsText(file);
        } else {
          fileContent.textContent = '未选择任何文件。';
        }
      });
    </script>
  </body>
</html>

👇 使用 readAsDataURL 方法,可以将用户上传的图像文件转换为可以直接在网页中显示的 URL

<!DOCTYPE html>
<html>
<head>
  <title>图像预览示例</title>
</head>
<body>
  <input type="file" id="imageInput" accept="image/*" />
  <img id="imagePreview" src="" alt="图像预览" style="max-width: 300px;" />

  <script>
    const imageInput = document.getElementById('imageInput');
    const imagePreview = document.getElementById('imagePreview');

    imageInput.addEventListener('change', (event) => {
      const file = event.target.files[0];
      if (file) {
        const reader = new FileReader();

        reader.onload = (e) => {
          imagePreview.src = e.target.result;
        };

        reader.onerror = () => {
          console.error('读取图像文件时发生错误。');
        };

        reader.readAsDataURL(file);
      } else {
        imagePreview.src = '';
      }
    });
  </script>
</body>
</html>

文件分块读取示例

对于非常大的文件,可以利用 Blob.slice 方法分块读取,避免占用过多内存。

const chunkSize = 1024 * 1024; // 1MB
let offset = 0;

function readChunk(file) {
  if (offset >= file.size) return;

  const slice = file.slice(offset, offset + chunkSize);
  const reader = new FileReader();

  reader.onload = (event) => {
    const text = event.target.result;
    console.log(`读取第 ${offset / chunkSize + 1} 块:`, text);
    offset += chunkSize;
    readChunk(file);
  };

  reader.onerror = () => {
    console.error('读取块时发生错误。');
  };

  reader.readAsText(slice);
}

readChunk(file);

4. ArrayBuffer 与 Typed Arrays、DataView

ArrayBuffer

ArrayBuffer 是一种用于表示通用、固定长度的原始二进制数据缓冲区,它是处理二进制数据的基础,可以用于存储各种类型的数据,必须通过 Typed ArraysDataView 访问。

ArrayBuffer 使用领域
  • 网络通信: WebSockets 和 Fetch API: 在与服务器进行低级网络通信时,ArrayBuffer 可以用于发送和接收二进制数据
  • 科学计算和数据分析: 在需要高效率大量数据运算的应用中(如图像处理、音频处理、科学计算),ArrayBuffer 配合 TypedArray 可实现更高效的数据处理
  • 图像处理: 可以在 Canvas 元素中使用 ArrayBuffer 来高效处理图像数据
  • 大数据处理: ArrayBuffer 提供了对内存中二进制数据的直接访问权限,这在处理大数据集时尤其有用
// 创建一个 8 字节的 ArrayBuffer
const buffer = new ArrayBuffer(8);

console.log(buffer.byteLength); // 输出: 8

ArrayBuffer 本身不能直接读取或写入数据,必须通过 Typed ArraysDataView 对象来操作 👇👇👇

Typed Arrays

TypedArray 提供了一种操作 ArrayBuffer 中数据的方式,允许以特定的字节序和数据类型读写二进制数据

👇 JavaScript 提供了多种类型的 TypedArray ,每种对应不同的数据类型和字节长度,它们为不同的数据类型(如整数、浮点数)提供了高效的操作接口

类型 描述 字节长度
Int8Array 8 位有符号整数 1
Uint8Array 8 位无符号整数 1
Uint8ClampedArray 8 位无符号整数,值被夹断到 [0, 255] 1
Int16Array 16 位有符号整数 2
Uint16Array 16 位无符号整数 2
Int32Array 32 位有符号整数 4
Uint32Array 32 位无符号整数 4
Float32Array 32 位浮点数 4
Float64Array 64 位浮点数 8
BigInt64Array 64 位有符号大整数(ES2020 引入) 8
BigUint64Array 64 位无符号大整数(ES2020 引入) 8
创建 Typed Arrays

Typed Arrays 特别适用于科学计算、图像处理、音频处理等需要高性能的数据操作场景

// 16 字节的缓冲区
const buffer = new ArrayBuffer(16);

// 创建一个 Float32Array 视图
const floatView = new Float32Array(buffer);

floatView[0] = 3.14;
floatView[1] = 2.718;

console.log(floatView); // 输出: Float32Array [ 3.14, 2.718, 0, 0 ]

DataView

DataView 提供了一种更灵活的方式来操作 ArrayBuffer 中的数据,允许以任意的字节序(大端或小端)读写不同类型的数据。

相比 Typed ArraysDataView 更加通用,适用于需要处理复杂数据结构的场景

DataView 示例
const buffer = new ArrayBuffer(16); // 16 字节的缓冲区
const dataView = new DataView(buffer);

// 设置不同类型的数据
dataView.setInt8(0, 127);          // 第一个字节设置为有符号 8 位整数
dataView.setUint16(1, 65000, true); // 从第 1 个字节开始,设置无符号 16 位整数,字节序为小端
dataView.setFloat32(3, 3.14, false); // 从第 3 个字节开始,设置 32 位浮点数,字节序为大端

// 读取数据
console.log(dataView.getInt8(0));      // 输出: 127
console.log(dataView.getUint16(1, true)); // 输出: 65000
console.log(dataView.getFloat32(3, false)); // 输出: 3.140000104904175

Typed Arrays 和 DateView 选择

  • 如果需要处理同一类型的数据,且不需要控制字节序,使用 Typed Arrays 更为方便和高效
  • 如果需要处理复杂的数据结构,或需要在数据中混合多种类型,且需要控制字节序,使用 DataView 更为合适

5. ArrayBuffer vs Blob

ArrayBufferBlob 在 JavaScript 中都是用于处理二进制数据的对象,在部分场景两者都能胜任,但擅长的场景有所区别

特性 ArrayBuffer Blob
可变性 可变 不可变
读取方式 需要 TypedArray 或 DataView 直接使用 FileReader
适用场景 计算、数据分析、网络通信 文件处理、存储、传输

6. 图像颜色反转示例

假设我们从服务器获取了一张灰度图像的二进制数据,我们需要对其进行处理,比如反转颜色。

// 模拟从服务器获取的图像数组(二进制数据)
function fetchImageData() {
  // 假设我们有一个 2x2 的灰度图像示例数据(4 个像素,每个像素 8 位灰度值)
  return new Uint8Array([100, 150, 200, 250]); // 灰度值
}

function processImageData() {
  // 获取图像数据
  let imageData = fetchImageData();

  // 创建一个 ArrayBuffer,长度与 imageData 相同
  let buffer = new ArrayBuffer(imageData.length);

  // 创建一个视图来操作这个缓冲区
  let view = new Uint8Array(buffer);

  // 复制数据到缓冲区
  for (let i = 0; i < imageData.length; i++) {
    view[i] = imageData[i];
  }

  // 处理图像数据(颜色反转)
  for (let i = 0; i < view.length; i++) {
    view[i] = 255 - view[i]; // 反转灰度值
  }

  // 输出处理后的数据
  console.log('Processed Image Data:', view);
}

processImageData();

结论

JavaScript 提供了一系列强大的二进制数据处理 API,能够高效地处理文件、图像、音频等数据。掌握它们能大幅提升 Web 应用的能力,满足更复杂的数据处理需求。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐