大文件分片
本文主要介绍将大文件分片的实现
展示
代码实现
vue
<template>
<div>
<input type="file" @change="handleFileChange" />
</div>
</template>
<script setup lang="ts">
import { cutFile } from "./utils";
const handleFileChange = async (event: any) => {
const file = event.target.files[0];
console.log("==file==", file, typeof file);
console.time("==curFile==");
const chunks = await cutFile(file);
console.timeEnd("==curFile==");
console.log("==result==", chunks);
};
</script>
ts
import SparkMD5 from "spark-md5";
export interface Chunk {
blob: Blob; // 文件的二进制格式
start: number; // 从第几个字节处开始
end: number; // 从第几个字节处结束
hash: string; // 文件的唯一指纹,用来做检验,和服务器协商好算法,判断文件有没有上传过
index: number; // 分片的下标
}
// 每个 chunk 大小
const CHUNK_SIZE = 1024 * 1024 * 5; // 5MB
// 线程数
const THREAD_COUNT = navigator.hardwareConcurrency || 4;
export const createChunk = (
file: File,
index: number,
chunkSize: number
): Promise<Chunk> => {
return new Promise((resolve) => {
// 开始的 chunk 下标
const start = index * chunkSize;
// 结束的 chunk 下标
const end = start + chunkSize;
const spark = new SparkMD5.ArrayBuffer();
const fileReader = new FileReader();
const blob = file.slice(start, end);
fileReader.onload = (e) => {
spark.append(e.target!.result as ArrayBuffer);
resolve({
start,
end,
index,
hash: spark.end(),
blob,
} as Chunk);
};
fileReader.readAsArrayBuffer(blob);
});
};
export const cutFile = async (file: File) => {
return new Promise((resolve, reject) => {
console.log("==file==", file);
const chunkCount = Math.ceil(file.size / CHUNK_SIZE);
const result: Chunk[][] = [];
// 已经完成的线程数
let finishedCount = 0;
/* for (let i = 0; i < chunkCount; i++) {
const chunk = await createChunk(file, i, CHUNK_SIZE);
result.push(chunk);
} */
/**
* 这里用如下 Promise.all 并发也没有解决慢的问题。计算 MD5 是 CPU 密集型任务,因为慢的主要瓶颈在计算 MD5,。
* 要减少处理时间,只能开启多线程。
* 开启多线程的前提一般是遇到 CPU 密集型任务。
*/
/* const result = [];
for (let i = 0; i < chunkCount; i++) {
const p = createChunk(file, i, CHUNK_SIZE);
result.push(p);
}
await Promise.all(result); */
// 每个线程处理的分片数
const threadChunkCount = Math.ceil(chunkCount / THREAD_COUNT);
for (let i = 0; i < THREAD_COUNT; i++) {
// 分配线程任务
const worker = new Worker("./worker.ts", {
type: "module",
});
// 开始的 chunk 下标
const start = i * threadChunkCount;
// 结束的 chunk 下标
const end = Math.min((i + 1) * threadChunkCount, chunkCount);
worker.postMessage({
file,
start,
end,
CHUNK_SIZE,
});
// 线程处理完成后,回传分片结果
worker.onmessage = (e) => {
result[i] = e.data;
worker.terminate();
finishedCount++;
// 全部线程都已经处理完成
if (finishedCount === THREAD_COUNT) {
resolve(result.flat());
}
};
}
});
};
ts
import { createChunk } from "./utils";
import type { Chunk } from "./utils";
onmessage = async (e) => {
const { file, start, end, CHUNK_SIZE } = e.data;
const result: Promise<Chunk>[] = [];
for (let i = start; i < end; i++) {
const p = createChunk(file, i, CHUNK_SIZE);
result.push(p);
}
const chunks = await Promise.all(result);
postMessage(chunks);
};
- 计算 MD5 是 CPU 密集型任务,慢的主要瓶颈在计算 MD5。
- 要减少处理时间,只能开启多线程。需要开启多线程一般也是因为遇到了 CPU 密集型任务。
TODO
- [ ] 总的 hash 计算(可以将各个 chunk 的 hash 加起来再次 hash),请求上传接口
- [ ] 断点续传
- [ ] 边分片边上传