大文件直接整包传,容易卡死、超时、断了重来。前端可用 文件流(Blob / ArrayBuffer / FileReader)处理二进制,再用 切片上传 / 切片下载 提高吞吐和断点续传体验。

文件传输是常见需求。大文件用传统方式会碰到性能和体验问题。下面按文件流、切片下载、大文件上传与断点续传、应用场景展开,示例沿用原文 React 代码。
一、前端文件流操作
文件流是用数据流去 读取、写入、展示 文件。数据流是连续数据序列;文件处理包括读内容、写数据、删除和重命名。
Blob 与 ArrayBuffer
Blob(Binary Large Object)表示大块二进制,可用构造函数或 FormData 拿到的文件生成。ArrayBuffer 是定长二进制缓冲区,用来操作文件字节。
import React, { useState } from 'react';
function FileInput() {
const [fileContent, setFileContent] = useState('');
// 读取文件内容到ArrayBuffer
function readFileToArrayBuffer(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
// 注册文件读取完成后的回调函数
reader.onload = function(event) {
const arrayBuffer = event.target.result;
resolve(arrayBuffer);
};
// 读取文件内容到ArrayBuffer
reader.readAsArrayBuffer(file);
});
}
// 将ArrayBuffer转为十六进制字符串
function arrayBufferToHexString(arrayBuffer) {
const uint8Array = new Uint8Array(arrayBuffer);
let hexString = '';
for (let i = 0; i < uint8Array.length; i++) {
const hex = uint8Array[i].toString(16).padStart(2, '0');
hexString += hex;
}
return hexString;
}
// 处理文件选择事件
function handleFileChange(event) {
const file = event.target.files[0]; // 获取选中的文件
if (file) {
readFileToArrayBuffer(file)
.then(arrayBuffer => {
const hexString = arrayBufferToHexString(arrayBuffer);
setFileContent(hexString);
})
.catch(error => {
console.error('文件读取失败:', error);
});
} else {
setFileContent('请选择一个文件');
}
}
return (
<div>
<input type="file" onChange={handleFileChange} />
<div>
<h4>文件内容:</h4>
<pre>{fileContent}</pre>
</div>
</div>
);
}
export default FileInput;
FileInput 里用文件选择框和 <pre> 展示内容。选中后 FileReader.readAsArrayBuffer,再转十六进制字符串显示。
FileReader
FileReader 异步读文件,转成文本或二进制。常用 readAsText()、readAsArrayBuffer()。
把文件流展示到页面
文本可放文本框;图片用 <img>;音视频用 <video> / <audio>,实现在线预览。
小结:大文件常用流式处理。Blob / ArrayBuffer 管二进制,FileReader 负责读入。
二、文件切片下载
先看整体流程:

传统下载的问题
切片下载把大文件切成小段,多请求并发拉,加快整体速度。整包下发会碰到:
-
-
-
切片如何提速

每片通常几百 KB 到几 MB,客户端并发拉:
-
-
-
切片上传代码示例:
const [selectedFile, setSelectedFile] = useState(null);
const [progress, setProgress] = useState(0);
// 处理文件选择事件
function handleFileChange(event) {
setSelectedFile(event.target.files[0]);
}
// 处理文件上传事件
function handleFileUpload() {
if (selectedFile) {
// 计算切片数量和每个切片的大小
const fileSize = selectedFile.size;
const chunkSize = 1024 * 1024; // 设置切片大小为1MB
const totalChunks = Math.ceil(fileSize / chunkSize);
// 创建FormData对象,并添加文件信息
const formData = new FormData();
formData.append('file', selectedFile);
formData.append('totalChunks', totalChunks);
// 循环上传切片
for (let chunkNumber = 0; chunkNumber < totalChunks; chunkNumber++) {
const start = chunkNumber * chunkSize;
const end = Math.min(start + chunkSize, fileSize);
const chunk = selectedFile.slice(start, end);
formData.append(`chunk-${chunkNumber}`, chunk, selectedFile.name);
}
// 发起文件上传请求
axios.post('/upload', formData, {
onUploadProgress: progressEvent => {
const progress = Math.round((progressEvent.loaded / progressEvent.total) * 100);
setProgress(progress);
}
})
.then(response => {
console.log('文件上传成功:', response.data);
})
.catch(error => {
console.error('文件上传失败:', error);
});
}
}
切片上传/下载一般是前端文件 API + 后端配合。上面这段在讲如何切片上传:
-
handleFileChange 把选中文件存进 selectedFile
-
-
按 1MB 切片,用
FormData 带上文件信息和各片数据
客户端切片下载方案
-
-
-
-
function downloadFile() {
// 发起文件下载请求
fetch('/download', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
.then(response => response.json())
.then(data => {
const totalSize = data.totalSize;
const totalChunks = data.totalChunks;
let downloadedChunks = 0;
let chunks = [];
// 下载每个切片
for (let chunkNumber = 0; chunkNumber < totalChunks; chunkNumber++) {
fetch(`/download/${chunkNumber}`, {
method: 'GET',
})
.then(response => response.blob())
.then(chunk => {
downloadedChunks++;
chunks.push(chunk);
// 当所有切片都下载完成时
if (downloadedChunks === totalChunks) {
// 合并切片
const mergedBlob = new Blob(chunks);
// 创建对象 URL,生成下载链接
const downloadUrl = window.URL.createObjectURL(mergedBlob);
// 创建 <a> 元素并设置属性
const link = document.createElement('a');
link.href = downloadUrl;
link.setAttribute('download', 'file.txt');
// 模拟点击下载
link.click();
// 释放资源
window.URL.revokeObjectURL(downloadUrl);
}
});
}
})
.catch(error => {
console.error('文件下载失败:', error);
});
}
用 Blob 生成对象 URL,动态 <a> 设 href 和 download,再模拟点击。
进度和完成态
-
-
可接在切片上传示例后面:
// 处理文件下载事件
function handleFileDownload() {
axios.get('/download', {
responseType: 'blob',
onDownloadProgress: progressEvent => {
const progress = Math.round((progressEvent.loaded / progressEvent.total) * 100);
setProgress(progress);
}
})
.then(response => {
// 创建一个临时的URL对象用于下载
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'file.txt');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
})
.catch(error => {
console.error('文件下载失败:', error);
});
}
<button onClick={handleFileDownload}>下载文件</button>
<div>进度:{progress}%</div>
-
-
axios 设 responseType: ‘blob’ 拿二进制
-
-
完成后
createObjectURL + <a download> 触发保存
三、大文件上传:问题与方案
传统上传的问题
切片上传的优势
实现方法
-
File API 拿文件,Blob.prototype.slice() 切片
-
FormData + AJAX / Fetch 发到服务器
-
-
const [file, setFile] = useState(null); //用来存放我本地上传的文件
const chunkSize = 1024 * 1024; // 1MB 切片大小
const upload = () => {
if (!file) {
alert("请选择要上传的文件!");
return;
}
const chunkSize = 1024 * 1024; // 1MB
let start = 0;
let end = Math.min(chunkSize, file.size);
while (start < file.size) {
const chunk = file.slice(start, end);
// 创建FormData对象
const formData = new FormData();
formData.append('file', chunk);
// 发送切片到服务器
fetch('上传接口xxxx', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
console.log(data);
// 处理响应结果
})
.catch(error => {
console.error(error);
// 处理错误
});
start = end;
end = Math.min(start + chunkSize, file.size);
}
};
return (
<div>
<input type="file" onChange={handleFileChange} />
<button onClick={upload}>上传</button>
</div>
);
}
Upload 用 useState 管选中文件;onChange 里更新 file。点上传后按固定大小切片,FormData + fetch 逐片发送。
断点续传:记下并恢复状态
-
前端用
localStorage / sessionStorage 存已传切片索引、大小等
-
-
-
import React, { useState, useRef, useEffect } from 'react';
function Upload() {
const [file, setFile] = useState(null);
const [uploadedChunks, setUploadedChunks] = useState([]);
const [uploading, setUploading] = useState(false);
const uploadRequestRef = useRef();
const handleFileChange = (event) => {
const selectedFile = event.target.files[0];
setFile(selectedFile);
};
const uploadChunk = (chunk) => {
// 创建FormData对象
const formData = new FormData();
formData.append('file', chunk);
// 发送切片到服务器
return fetch('your-upload-url', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
console.log(data);
// 处理响应结果
return data;
});
};
const upload = async () => {
if (!file) {
alert("请选择要上传的文件!");
return;
}
const chunkSize = 1024 * 1024; // 1MB
const totalChunks = Math.ceil(file.size / chunkSize);
let start = 0;
let end = Math.min(chunkSize, file.size);
setUploading(true);
for (let i = 0; i < totalChunks; i++) {
const chunk = file.slice(start, end);
const uploadedChunkIndex = uploadedChunks.indexOf(i);
if (uploadedChunkIndex === -1) {
try {
const response = await uploadChunk(chunk);
setUploadedChunks((prevChunks) => [...prevChunks, i]);
// 保存已上传的切片信息到本地存储
localStorage.setItem('uploadedChunks', JSON.stringify(uploadedChunks));
} catch (error) {
console.error(error);
// 处理错误
}
}
start = end;
end = Math.min(start + chunkSize, file.size);
}
setUploading(false);
// 上传完毕,清除本地存储的切片信息
localStorage.removeItem('uploadedChunks');
};
useEffect(() => {
const storedUploadedChunks = localStorage.getItem('uploadedChunks');
if (storedUploadedChunks) {
setUploadedChunks(JSON.parse(storedUploadedChunks));
}
}, []);
return (
<div>
<input type="file" onChange={handleFileChange} />
<button onClick={upload} disabled={uploading}>
{uploading ? '上传中...' : '上传'}
</button>
</div>
);
}
uploadedChunks 存已传索引;useRef 的 uploadRequestRef 预留给当前请求。
handleFileChange 更新文件;uploadChunk 发一片并返回 Promise。
upload 里算总片数,uploading=true 禁用按钮;循环里若索引不在 uploadedChunks 才传。
传完把索引写入数组和 localStorage;全部结束后清掉本地记录,并把 uploading 设回 false。
落地时要考虑服务端处理能力和磁盘,以及安全。为避免并发传同一文件把续传弄乱,用文件唯一 ID 或会话 ID 区分。
四、应用场景
后台管理系统
-
下载报表、日志、库备份:切片更快更稳,可中断后续传
-
图片 / 视频
-
-
大视频切片上传更可靠;下发也可用切片,不必等整文件下完再播
云存储 / 云盘
| 环节 |
关键 API |
| 读文件 |
FileReader / ArrayBuffer / Blob |
| 切片 |
file.slice(start, end),常见 1MB |
| 上传 |
FormData + fetch/axios,记 localStorage 做续传 |
| 下载 |
按片 GET,Blob 合并后 createObjectURL 触发保存 |
一句话总结:大文件不要整包硬传:先用 FileReader/Blob 处理流,再按片并发上下行,并把已完成切片记下来做断点续传。