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

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

相关推荐
佚名涙1 分钟前
go中锁的入门到进阶使用
开发语言·后端·golang
猫猫的小茶馆3 分钟前
【PCB工艺】软件是如何控制硬件的发展过程
开发语言·stm32·单片机·嵌入式硬件·mcu·51单片机·pcb工艺
勘察加熊人1 小时前
wpf+c#路径迷宫鼠标绘制
开发语言·c#·wpf
小黄人软件2 小时前
C# ini文件全自动界面配置:打开界面时读ini配置到界面各控件,界面上的控件根据ini文件内容自动生成,点保存时把界面各控件的值写到ini里。
开发语言·c#
☞无能盖世♛逞何英雄☜4 小时前
Upload-labs 靶场搭建 及一句话木马的原理与运用
php
Android洋芋5 小时前
C语言深度解析:从零到系统级开发的完整指南
c语言·开发语言·stm32·条件语句·循环语句·结构体与联合体·指针基础
bjxiaxueliang5 小时前
一文详解QT环境搭建:Windows使用CLion配置QT开发环境
开发语言·windows·qt
Run_Teenage5 小时前
C语言 【初始指针】【指针一】
c语言·开发语言
苹果.Python.八宝粥5 小时前
Python第七章02:文件读取的练习
开发语言·python
J不A秃V头A6 小时前
Redis批量操作详解
开发语言·redis