流式上传与分片上传的原理与实现

🚀 博主介绍:大家好,我是无休居士!一枚任职于一线Top3互联网大厂的Java开发工程师! 🚀

🌟 在这里,你将找到通往Java技术大门的钥匙。作为一个爱敲代码技术人,我不仅热衷于探索一些框架源码和算法技巧奥秘,还乐于分享这些宝贵的知识和经验。

💡 无论你是刚刚踏入编程世界的新人,还是希望进一步提升自己的资深开发者,在这里都能找到适合你的内容。我们共同探讨技术难题,一起进步,携手度过互联网行业的每一个挑战

📣 如果你觉得我的文章对你有帮助,请不要吝啬你的点赞👍分享💕和评论哦! 让我们一起打造一个充满正能量的技术社区吧!


目录标题

  • [1. 背景](#1. 背景)
  • [2. 引言](#2. 引言)
  • [3. 流式上传](#3. 流式上传)
    • [3.1 原理解析](#3.1 原理解析)
      • [3.1.1 实现步骤](#3.1.1 实现步骤)
      • [3.1.2 示例代码](#3.1.2 示例代码)
  • [4. 分片上传](#4. 分片上传)
    • [4.1 原理解析](#4.1 原理解析)
      • [4.1.1 实现步骤](#4.1.1 实现步骤)
      • [4.1.2 示例代码](#4.1.2 示例代码)
  • [5. 断点续传💡](#5. 断点续传💡)
    • [5.1 原理解析](#5.1 原理解析)
      • [5.1.1 实现步骤](#5.1.1 实现步骤)
      • [5.1.2 示例代码](#5.1.2 示例代码)
  • [6. 总结](#6. 总结)
  • [7. 附录](#7. 附录)
    • [7.1 流程图](#7.1 流程图)
      • [7.1.1 流式上传流程图](#7.1.1 流式上传流程图)
      • [7.1.2 分片上传流程图](#7.1.2 分片上传流程图)
      • [7.1.3 断点续传流程图](#7.1.3 断点续传流程图)
  • [8. 参考资料](#8. 参考资料)

1. 背景

在现代互联网应用中,文件上传是一个常见的需求,尤其是在处理大文件时。流式上传和分片上传是两种重要的文件上传技术。流式上传通过一次请求直接将文件上传到服务器,适用于小文件或网络环境稳定的场景。分片上传则是将大文件切成多个小的分片,每个分片单独上传,可以提高上传的可靠性和效率,特别适合网络环境不稳定或文件较大的情况。理解这两种技术的原理和实现,不仅能帮助开发者优化文件上传的性能,还能在面试中展示深厚的技术功底。

2. 引言

在互联网大厂的面试中,文件上传是一个常见的技术考点。特别是在处理大文件上传时,流式上传和分片上传是两个非常重要的技术手段。本文将详细介绍这两种上传方式的原理、实现细节以及如何实现断点续传,帮助你在面试中脱颖而出。🚀


3. 流式上传

3.1 原理解析

流式上传(Stream Upload)是指通过一次请求将文件直接上传到服务器。这种方式适用于小文件上传,因为小文件的传输时间较短,网络波动对其影响较小。然而,对于大文件,直接上传可能会因为网络中断、超时等原因导致上传失败。因此,流式上传更适合小文件或网络环境稳定的场景。🌟

3.1.1 实现步骤

  1. 客户端准备

    • 读取文件内容,将其转换为二进制数据。
    • 使用HTTP POST请求将文件数据发送到服务器。
  2. 服务器端处理

    • 接收客户端发送的文件数据。
    • 将接收到的数据保存到指定的文件路径。

3.1.2 示例代码

客户端(JavaScript)
javascript 复制代码
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
const formData = new FormData();
formData.append('file', file);

fetch('/upload', {
  method: 'POST',
  body: formData
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
服务器端(Node.js + Express)
javascript 复制代码
const express = require('express');
const app = express();
const multer = require('multer');

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'uploads/');
  },
  filename: function (req, file, cb) {
    cb(null, Date.now() + '-' + file.originalname);
  }
});

const upload = multer({ storage: storage });

app.post('/upload', upload.single('file'), (req, res) => {
  res.send('File uploaded successfully');
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

4. 分片上传

4.1 原理解析

分片上传(Chunked Upload)是将大文件切成多个小的分片,每个分片单独上传到服务器。这种方式可以提高上传的可靠性和效率,特别是在网络环境不稳定的情况下。分片上传的关键在于如何处理分片的合并和断点续传。🌐

4.1.1 实现步骤

  1. 客户端准备

    • 读取文件内容,将其切成多个分片。
    • 为每个分片生成唯一的标识(如索引)。
    • 使用HTTP POST请求将每个分片单独上传到服务器。
  2. 服务器端处理

    • 接收客户端发送的分片数据。
    • 将接收到的分片数据保存到临时文件夹。
    • 当所有分片上传完成后,将分片合并成完整的文件。

4.1.2 示例代码

客户端(JavaScript)
javascript 复制代码
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
const chunkSize = 1024 * 1024; // 1MB
const chunks = Math.ceil(file.size / chunkSize);

for (let i = 0; i < chunks; i++) {
  const start = i * chunkSize;
  const end = Math.min(start + chunkSize, file.size);
  const chunk = file.slice(start, end);
  const formData = new FormData();
  formData.append('file', chunk);
  formData.append('index', i);
  formData.append('totalChunks', chunks);

  fetch('/upload-chunk', {
    method: 'POST',
    body: formData
  })
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));
}
服务器端(Node.js + Express)
javascript 复制代码
const express = require('express');
const app = express();
const multer = require('multer');
const fs = require('fs');
const path = require('path');

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'uploads/temp/');
  },
  filename: function (req, file, cb) {
    cb(null, `${req.body.index}-${Date.now()}-${file.originalname}`);
  }
});

const upload = multer({ storage: storage });

app.post('/upload-chunk', upload.single('file'), (req, res) => {
  const { index, totalChunks } = req.body;
  const filePath = path.join(__dirname, 'uploads/temp', `${index}-${Date.now()}-${req.file.originalname}`);

  if (parseInt(index) === parseInt(totalChunks) - 1) {
    mergeChunks(totalChunks, req.file.originalname);
  }

  res.send('Chunk uploaded successfully');
});

function mergeChunks(totalChunks, fileName) {
  const tempDir = path.join(__dirname, 'uploads/temp');
  const targetPath = path.join(__dirname, 'uploads', fileName);

  const writeStream = fs.createWriteStream(targetPath);

  for (let i = 0; i < totalChunks; i++) {
    const chunkPath = path.join(tempDir, `${i}-${Date.now()}-${fileName}`);
    const readStream = fs.createReadStream(chunkPath);
    readStream.pipe(writeStream, { end: false });

    readStream.on('end', () => {
      fs.unlinkSync(chunkPath);
    });
  }

  writeStream.end();
}

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

5. 断点续传💡

5.1 原理解析

断点续传是指在文件上传过程中,如果发生中断,可以从断点继续上传,而不是从头开始。这对于大文件上传尤为重要,因为它可以节省时间和带宽,提高上传的可靠性。

5.1.1 实现步骤

  1. 客户端准备

    • 读取文件内容,将其切成多个分片
    • 为每个分片生成唯一的标识(如索引)
    • 记录已上传的分片信息(如已上传的分片索引)
    • 使用HTTP POST请求将每个分片单独上传到服务器
  2. 服务器端处理

    • 接收客户端发送的分片数据。
    • 将接收到的分片数据保存到临时文件夹
    • 当所有分片上传完成后,将分片合并成完整的文件
    • 如果上传中断,客户端可以查询已上传的分片信息,从断点继续上传。

5.1.2 示例代码

客户端(JavaScript)
javascript 复制代码
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
const chunkSize = 1024 * 1024; // 1MB
const chunks = Math.ceil(file.size / chunkSize);

let uploadedChunks = [];

// 查询已上传的分片
fetch('/uploaded-chunks', {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json'
  }
})
.then(response => response.json())
.then(data => {
  uploadedChunks = data.uploadedChunks;
  uploadChunks();
})
.catch(error => console.error('Error:', error));

function uploadChunks() {
  for (let i = 0; i < chunks; i++) {
    if (!uploadedChunks.includes(i)) {
      const start = i * chunkSize;
      const end = Math.min(start + chunkSize, file.size);
      const chunk = file.slice(start, end);
      const formData = new FormData();
      formData.append('file', chunk);
      formData.append('index', i);
      formData.append('totalChunks', chunks);

      fetch('/upload-chunk', {
        method: 'POST',
        body: formData
      })
      .then(response => response.json())
      .then(data => {
        console.log(data);
        uploadedChunks.push(i);
        if (uploadedChunks.length === chunks) {
          console.log('All chunks uploaded successfully');
        } else {
          uploadChunks();
        }
      })
      .catch(error => console.error('Error:', error));
    }
  }
}
服务器端(Node.js + Express)
javascript 复制代码
const express = require('express');
const app = express();
const multer = require('multer');
const fs = require('fs');
const path = require('path');

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'uploads/temp/');
  },
  filename: function (req, file, cb) {
    cb(null, `${req.body.index}-${Date.now()}-${file.originalname}`);
  }
});

const upload = multer({ storage: storage });

app.post('/upload-chunk', upload.single('file'), (req, res) => {
  const { index, totalChunks } = req.body;
  const filePath = path.join(__dirname, 'uploads/temp', `${index}-${Date.now()}-${req.file.originalname}`);

  if (parseInt(index) === parseInt(totalChunks) - 1) {
    mergeChunks(totalChunks, req.file.originalname);
  }

  res.send('Chunk uploaded successfully');
});

app.get('/uploaded-chunks', (req, res) => {
  const tempDir = path.join(__dirname, 'uploads/temp');
  const files = fs.readdirSync(tempDir);
  const uploadedChunks = files.map(file => parseInt(file.split('-')[0]));
  res.json({ uploadedChunks });
});

function mergeChunks(totalChunks, fileName) {
  const tempDir = path.join(__dirname, 'uploads/temp');
  const targetPath = path.join(__dirname, 'uploads', fileName);

  const writeStream = fs.createWriteStream(targetPath);

  for (let i = 0; i < totalChunks; i++) {
    const chunkPath = path.join(tempDir, `${i}-${Date.now()}-${fileName}`);
    const readStream = fs.createReadStream(chunkPath);
    readStream.pipe(writeStream, { end: false });

    readStream.on('end', () => {
      fs.unlinkSync(chunkPath);
    });
  }

  writeStream.end();
}

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

6. 总结

通过本文的详细讲解,相信你已经对流式上传和分片上传有了全面的理解。流式上传适用于小文件或网络环境稳定的场景,而分片上传则更适合大文件上传,特别是需要处理网络中断的情况。断点续传是分片上传的一个重要特性,可以大大提高上传的可靠性和效率。希望本文能帮助你在面试中顺利过关,祝你好运!🎉


7. 附录

7.1 流程图

7.1.1 流式上传流程图

客户端读取文件 生成二进制数据 发送HTTP POST请求 服务器接收文件数据 保存文件到指定路径

7.1.2 分片上传流程图

客户端读取文件 切分成多个分片 生成分片标识 发送每个分片 服务器接收分片 保存分片到临时文件夹 合并分片成完整文件

7.1.3 断点续传流程图

客户端查询已上传分片 读取文件并切分成多个分片 生成分片标识 发送每个分片 服务器接收分片 保存分片到临时文件夹 合并分片成完整文件 客户端记录已上传分片


8. 参考资料


乐于分享和输出干货的WXGZG:JavaPersons

相关推荐
枫の准大一15 分钟前
C++从零到满绩——类和对象(中)
开发语言·c++
HEX9CF21 分钟前
【数字图像处理+MATLAB】通过 Roberts, Prewitt, Sobel, LoG 等算子实现图像边缘检测:使用 edge 函数
开发语言·matlab·edge
凡人的AI工具箱32 分钟前
40分钟学 Go 语言高并发实战:高性能缓存组件开发
开发语言·后端·缓存·架构·golang
大白的编程日记.36 分钟前
【C++笔记】数据结构进阶之二叉搜索树(BSTree)
开发语言·数据结构·c++·笔记
每天一个秃顶小技巧38 分钟前
01.Golang 源码目录结构
开发语言·后端·golang
小柯J桑_38 分钟前
C++:用红黑树封装map与set-1
开发语言·c++·set·map·红黑树
2401_8532757344 分钟前
Java IO 基础知识总结下
java·开发语言
时光の尘44 分钟前
C语言菜鸟入门·关键字·union的用法
运维·服务器·c语言·开发语言·c·printf
诸神黄昏EX1 小时前
Android 常用命令和工具解析之内存相关
android·java·开发语言
AitTech1 小时前
C#动态类型详解:应用场景与注意事项
开发语言·c#