實驗- 分片上傳 VS 直接上傳

分片上傳和直接上傳是兩種常見的文件上傳方式。分片上傳將文件分成多個小塊,每次上傳一個小塊,可以并行處理多個分片,適用于大文件上傳,減少了單個請求的大小,能有效避免因網絡波動或上傳中斷導致的失敗,并支持斷點續傳。相比之下,直接上傳是將文件作為一個整體上傳,通常適用于較小的文件,簡單快捷,但對于大文件來說,容易受到網絡環境的影響,上傳中斷時需要重新上傳整個文件。因此,分片上傳在大文件上傳中具有更高的穩定性和可靠性。

FileUploadController

package per.mjn.controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import per.mjn.service.FileUploadService;import java.io.File;
import java.io.IOException;
import java.util.List;@RestController
@RequestMapping("/upload")
public class FileUploadController {private static final String UPLOAD_DIR = "F:/uploads/";@Autowiredprivate FileUploadService fileUploadService;// 啟動文件分片上傳@PostMapping("/start")public ResponseEntity<String> startUpload(@RequestParam("file") MultipartFile file,@RequestParam("totalParts") int totalParts,@RequestParam("partIndex") int partIndex) {try {String fileName = file.getOriginalFilename();fileUploadService.saveChunk(file, partIndex);  // 存儲單個分片return ResponseEntity.ok("Chunk " + partIndex + " uploaded successfully.");} catch (IOException e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error uploading chunk: " + e.getMessage());}}// 多線程并行上傳所有分片@PostMapping("/uploadAll")public ResponseEntity<String> uploadAllChunks(@RequestParam("file") List<MultipartFile> files,@RequestParam("fileName") String fileName) {try {fileUploadService.uploadChunksInParallel(files, fileName);  // 調用并行上傳方法return ResponseEntity.ok("All chunks uploaded successfully.");} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error uploading chunks: " + e.getMessage());}}// 合并文件分片@PostMapping("/merge")public ResponseEntity<String> mergeChunks(@RequestParam("fileName") String fileName,@RequestParam("totalParts") int totalParts) {try {System.out.println(fileName);System.out.println(totalParts);fileUploadService.mergeChunks(fileName, totalParts);return ResponseEntity.ok("File uploaded and merged successfully.");} catch (IOException e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error merging chunks: " + e.getMessage());}}// 直接上傳整個文件@PostMapping("/file")public ResponseEntity<String> handleFileUpload(@RequestParam("file") MultipartFile file) {try {String fileName = file.getOriginalFilename();File targetFile = new File(UPLOAD_DIR + fileName);File dir = new File(UPLOAD_DIR);if (!dir.exists()) {dir.mkdirs();}file.transferTo(targetFile);return ResponseEntity.ok("File uploaded successfully: " + fileName);} catch (IOException e) {return ResponseEntity.status(500).body("Error uploading file: " + e.getMessage());}}
}

FileUploadService

package per.mjn.service;import org.springframework.web.multipart.MultipartFile;import java.io.IOException;
import java.util.List;public interface FileUploadService {public void saveChunk(MultipartFile chunk, int partIndex) throws IOException;public void uploadChunksInParallel(List<MultipartFile> chunks, String fileName);public void mergeChunks(String fileName, int totalParts) throws IOException;
}

FileUploadServiceImpl

package per.mjn.service.impl;import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import per.mjn.service.FileUploadService;import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;@Service
public class FileUploadServiceImpl implements FileUploadService {private static final String UPLOAD_DIR = "F:/uploads/";private final ExecutorService executorService;public FileUploadServiceImpl() {// 使用一個線程池來并發處理上傳this.executorService = Executors.newFixedThreadPool(4); // 4個線程用于并行上傳}// 保存文件分片public void saveChunk(MultipartFile chunk, int partIndex) throws IOException {File dir = new File(UPLOAD_DIR);if (!dir.exists()) {dir.mkdirs();}File chunkFile = new File(UPLOAD_DIR + chunk.getOriginalFilename() + ".part" + partIndex);chunk.transferTo(chunkFile);}// 處理所有分片的上傳public void uploadChunksInParallel(List<MultipartFile> chunks, String fileName) {List<Callable<Void>> tasks = new ArrayList<>();for (int i = 0; i < chunks.size(); i++) {final int index = i;final MultipartFile chunk = chunks.get(i);tasks.add(() -> {try {saveChunk(chunk, index);System.out.println("Uploaded chunk " + index + " of " + fileName);} catch (IOException e) {e.printStackTrace();}return null;});}try {// 執行所有上傳任務executorService.invokeAll(tasks);} catch (InterruptedException e) {e.printStackTrace();}}// 合并文件分片public void mergeChunks(String fileName, int totalParts) throws IOException {File mergedFile = new File(UPLOAD_DIR + fileName);try (BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(mergedFile))) {for (int i = 0; i < totalParts; i++) {File chunkFile = new File(UPLOAD_DIR + fileName + ".part" + i);try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(chunkFile))) {byte[] buffer = new byte[8192];int bytesRead;while ((bytesRead = inputStream.read(buffer)) != -1) {outputStream.write(buffer, 0, bytesRead);}}chunkFile.delete(); // 刪除臨時分片文件}}}
}

前端測試界面

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>File Upload</title>
</head>
<body><h1>File Upload</h1><input type="file" id="fileInput"><button onclick="startUpload()">Upload File</button><button onclick="directUpload()">Upload File Directly</button><script>const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB per chunkconst fileInput = document.querySelector('#fileInput');let totalChunks;function startUpload() {const file = fileInput.files[0];totalChunks = Math.ceil(file.size / CHUNK_SIZE);let currentChunk = 0;let files = [];while (currentChunk < totalChunks) {const chunk = file.slice(currentChunk * CHUNK_SIZE, (currentChunk + 1) * CHUNK_SIZE);files.push(chunk);currentChunk++;}// 并行上傳所有分片uploadAllChunks(files, file.name);}function uploadAllChunks(chunks, fileName) {const promises = chunks.map((chunk, index) => {const formData = new FormData();formData.append('file', chunk, fileName);formData.append('partIndex', index);formData.append('totalParts', totalChunks);formData.append('fileName', fileName);return fetch('http://172.20.10.2:8080/upload/start', {method: 'POST',body: formData}).then(response => response.text()).then(data => console.log(`Chunk ${index} uploaded successfully.`)).catch(error => console.error(`Error uploading chunk ${index}`, error));});// 等待所有分片上傳完成Promise.all(promises).then(() => {console.log('All chunks uploaded, now merging.');mergeChunks(fileName);}).catch(error => console.error('Error during uploading chunks', error));}function mergeChunks(fileName) {fetch(`http://172.20.10.2:8080/upload/merge?fileName=${fileName}&totalParts=${totalChunks}`, {method: 'POST'}).then(response => response.text()).then(data => console.log('File uploaded and merged successfully.')).catch(error => console.error('Error merging chunks', error));}// 直接上傳整個文件function directUpload() {const file = fileInput.files[0];if (!file) {alert('Please select a file to upload.');return;}const formData = new FormData();formData.append('file', file); // 將整個文件添加到 FormDatafetch('http://172.20.10.2:8080/upload/file', {method: 'POST',body: formData}).then(response => response.text()).then(data => console.log('File uploaded directly: ', data)).catch(error => console.error('Error uploading file directly', error));}</script>
</body>
</html>

測試分片上傳與直接上傳耗時

我們上傳一個310MB的文件,分片上傳每個分片在前端設置為10MB,在后端開5個線程并發執行上傳操作。
直接上傳沒有分片大小也沒有開多線程,下面是兩種方式的測試結果。
在這里插入圖片描述
分片上傳,耗時2.419s
在這里插入圖片描述

直接上傳,耗時4.572s

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/898167.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/898167.shtml
英文地址,請注明出處:http://en.pswp.cn/news/898167.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Android視頻渲染SurfaceView強制全屏與原始比例切換

1.創建UI添加強制全屏與播放按鈕 2.SurfaceView控件設置全屏顯示 3.全屏點擊事件處理實現 4.播放點擊事件處理 5.使用接口更新強制全屏與原始比例文字 強制全屏/原始比例 點擊實現

數據結構——串、數組和廣義表

串、數組和廣義表 1. 串 1.1 串的定義 串(string)是由零個或多個字符組成的有限序列。一般記為 S a 1 a 2 . . . a n ( n ≥ 0 ) Sa_1a_2...a_n(n\geq0) Sa1?a2?...an?(n≥0) 其中&#xff0c;S是串名&#xff0c;單引號括起來的字符序列是串的值&#xff0c; a i a_i a…

無再暴露源站!群聯AI云防護IP隱匿方案+防繞過實戰

一、IP隱藏的核心原理 群聯AI云防護通過三層架構實現源站IP深度隱藏&#xff1a; 流量入口層&#xff1a;用戶訪問域名解析至高防CNAME節點&#xff08;如ai-protect.example.com&#xff09;智能調度層&#xff1a;基于AI模型動態分配清洗節點&#xff0c;實時更新節點IP池回…

1.5.3 掌握Scala內建控制結構 - for循環

Scala的for循環功能強大&#xff0c;支持單重和嵌套循環。單重for循環語法為for (變量 <- 集合或數組 (條件)) {語句組}&#xff0c;可選篩選條件&#xff0c;循環變量依次取集合值。支持多種任務&#xff0c;如輸出指定范圍整數&#xff08;使用Range、to、until&#xff0…

【MySQL基礎-9】深入理解MySQL中的聚合函數

在數據庫操作中&#xff0c;聚合函數是一類非常重要的函數&#xff0c;它們用于對一組值執行計算并返回單個值。MySQL提供了多種聚合函數&#xff0c;如COUNT、SUM、AVG、MIN和MAX等。這些函數在數據分析和報表生成中扮演著關鍵角色。本文將深入探討這些聚合函數的使用方法、注…

windows版本的時序數據庫TDengine安裝以及可視化工具

了解時序數據庫TDengine&#xff0c;可以點擊官方文檔進行詳細查閱 安裝步驟 首先找到自己需要下載的版本&#xff0c;這邊我暫時只寫windows版本的安裝 首先我們需要點開官網&#xff0c;找到發布歷史&#xff0c;目前TDengine的windows版本只更新到3.0.7.1&#xff0c;我們…

Web測試

7、Web安全測試概述 黑客技術的發展歷程 黑客基本涵義是指一個擁有熟練電腦技術的人&#xff0c;但大部分的媒體習慣將“黑客”指作電腦侵入者。 黑客技術的發展 在早期&#xff0c;黑客攻擊的目標以系統軟件居多。早期互聯網Web并非主流應用&#xff0c;而且防火墻技術還沒有…

華為OD機試 - 最長的完全交替連續方波信號(Java 2023 B卷 200分)

題目描述 給定一串方波信號,要求找出其中最長的完全連續交替方波信號并輸出。如果有多個相同長度的交替方波信號,輸出任意一個即可。方波信號的高位用1標識,低位用0標識。 說明: 一個完整的信號一定以0開始并以0結尾,即010是一個完整的信號,但101,1010,0101不是。輸入的…

游戲引擎學習第163天

我們可以在資源處理器中使用庫 因為我們的資源處理器并不是游戲的一部分&#xff0c;所以它可以使用庫。我說過我不介意讓它使用庫&#xff0c;而我提到這個的原因是&#xff0c;今天我們確實有一個選擇——可以使用庫。 生成字體位圖的兩種方式&#xff1a;求助于 Windows 或…

7、什么是死鎖,如何避免死鎖?【高頻】

&#xff08;1&#xff09;什么是死鎖&#xff1a; 死鎖 是指在兩個或多個進程的執行時&#xff0c;每個進程都持有資源 并 等待其他進程 釋放 它所需的資源&#xff0c;如果此時所有的進程一直占有資源而不釋放&#xff0c;就會陷入互相等待的一種僵局狀態。 死鎖只有同時滿足…

Compose 實踐與探索十四 —— 自定義布局

自定義布局在 Compose 中相對于原生的需求已經小了很多&#xff0c;先講二者在本質上的邏輯&#xff0c;再說它們的使用場景&#xff0c;兩相對比就知道為什么 Compose 中的自定義布局的需求較小了。 原生是在 xml 布局文件不太方便或者無法滿足需求時才會在代碼中通過自定義 …

【C++】:C++11詳解 —— 入門基礎

目錄 C11簡介 統一的列表初始化 1.初始化范圍擴展 2.禁止窄化轉換&#xff08;Narrowing Conversion&#xff09; 3.解決“最令人煩惱的解析”&#xff08;Most Vexing Parse&#xff09; 4.動態數組初始化 5. 直接初始化返回值 總結 聲明 1.auto 類型推導 2. declty…

oracle刪除表中重復數據

需求&#xff1a; 刪除wfd_procs_nodes_rwk表中&#xff0c;huser_id、dnode_id、rwk_name字段值相同的記錄&#xff0c;如果有多條&#xff0c;只保留一條。 SQL&#xff1a; DELETE FROM wfd_procs_nodes_rwk t WHERE t.rowid > (SELECT MIN(t1.rowid)FROM wfd_procs_n…

ESP32學習 -從STM32工程架構進階到ESP32架構

ESP32與STM32項目文件結構對比解析 以下是對你提供的ESP32項目文件結構的詳細解釋&#xff0c;并與STM32&#xff08;以STM32CubeIDE為例&#xff09;的常見結構進行對比&#xff0c;幫助你理解兩者的差異&#xff1a; 1. ESP32項目文件解析 文件/目錄作用STM32對應或差異set…

整形在內存中的存儲(例題逐個解析)

目錄 一.相關知識點 1.截斷&#xff1a; 2.整形提升&#xff1a; 3.如何 截斷&#xff0c;整型提升&#xff1f; &#xff08;1&#xff09;負數 &#xff08;2&#xff09;正數 &#xff08;3&#xff09;無符號整型&#xff0c;高位補0 注意&#xff1a;提升后得到的…

HTML中滾動加載的實現

設置div的overflow屬性&#xff0c;可以使得該div具有滾動效果&#xff0c;下面以div中包含的是table來舉例。 當table的元素較多&#xff0c;以至于超出div的顯示范圍的話&#xff0c;觀察下該div元素的以下3個屬性&#xff1a; clientHeight是div的顯示高度&#xff0c;scrol…

Netty基礎—7.Netty實現消息推送服務二

大綱 1.Netty實現HTTP服務器 2.Netty實現WebSocket 3.Netty實現的消息推送系統 (1)基于WebSocket的消息推送系統說明 (2)消息推送系統的PushServer (3)消息推送系統的連接管理封裝 (4)消息推送系統的ping-pong探測 (5)消息推送系統的全連接推送 (6)消息推送系統的HTTP…

人工智能助力家庭機器人:從清潔到陪伴的智能轉型

引言&#xff1a;家庭機器人進入智能時代 過去&#xff0c;家庭機器人只是簡單的“工具”&#xff0c;主要用于掃地、拖地、擦窗等單一任務。然而&#xff0c;隨著人工智能&#xff08;AI&#xff09;技術的迅猛發展&#xff0c;家庭機器人正經歷從“機械助手”向“智能管家”甚…

ssh轉發筆記

工作中又學到了&#xff0c;大腦轉不過來 現有主機A&#xff0c;主機B&#xff0c;主機C A能訪問B&#xff0c;B能訪問C&#xff0c;A不能訪問C C上80端口有個服務&#xff0c;現在A想訪問這個服務&#xff0c;領導讓用ssh轉發&#xff0c;研究半天沒找到理想的語句&#xf…

清晰易懂的Miniconda安裝教程

小白也能看懂的 Miniconda 安裝教程 Miniconda 是一個輕量級的 Python 環境管理工具&#xff0c;適合初學者快速搭建 Python 開發環境。本教程將手把手教你如何在 Windows 系統上安裝 Miniconda&#xff0c;并配置基礎環境&#xff0c;確保你能夠順利使用 Python 進行開發。即…