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

🚀 博主介绍:大家好,我是无休居士!一枚任职于一线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

相关推荐
F-2H几秒前
C语言:指针4(常量指针和指针常量及动态内存分配)
java·linux·c语言·开发语言·前端·c++
bryant_meng1 小时前
【python】OpenCV—Image Moments
开发语言·python·opencv·moments·图片矩
若亦_Royi2 小时前
C++ 的大括号的用法合集
开发语言·c++
资源补给站2 小时前
大恒相机开发(2)—Python软触发调用采集图像
开发语言·python·数码相机
m0_748247553 小时前
Web 应用项目开发全流程解析与实战经验分享
开发语言·前端·php
6.943 小时前
Scala学习记录 递归调用 练习
开发语言·学习·scala
FF在路上3 小时前
Knife4j调试实体类传参扁平化模式修改:default-flat-param-object: true
java·开发语言
众拾达人4 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
皓木.4 小时前
Mybatis-Plus
java·开发语言
不良人天码星4 小时前
lombok插件不生效
java·开发语言·intellij-idea