高效應對內存溢出!Spring Boot 3.3 大文件處理全攻略
在現代應用程序中,處理大文件已成為一項常見需求,例如視頻上傳、數據遷移和日志分析等。然而,當處理超過內存容量的大文件時,可能會導致內存溢出(OutOfMemoryError)的問題,影響應用程序的穩定性和性能。傳統的文件處理方式通常將整個文件加載到內存中,這在處理大文件時顯然不可行。
為了解決這一挑戰,我們需要采用高效的文件處理策略,例如分塊處理和流式讀取。這些方法可以有效降低內存占用,提高文件處理的效率。本文將深入探討如何在 Spring Boot 3.3 中實現大文件的高效處理,包括分塊上傳、服務端分塊保存,以及前端使用 jQuery 和 Bootstrap 實現分塊上傳的流程。
分塊處理的流程介紹
分塊處理是一種將大文件拆分為多個小塊,逐個傳輸和處理的技術。其主要流程如下:
- 前端文件分塊: 在客戶端(瀏覽器)將大文件按照一定大小(如 1MB)進行拆分,生成多個文件塊。
- 分塊上傳: 前端逐個將文件塊通過 HTTP 請求上傳到服務器,可以使用 AJAX 異步提交。
- 服務端接收并保存: 服務器接收到文件塊后,按順序將其保存到臨時文件或直接寫入目標文件,使用流式寫入方式,避免占用過多內存。
- 合并文件塊: 如果在臨時目錄保存,待所有文件塊上傳完成后,服務器將所有文件塊按序合并為最終的完整文件。
- 返回上傳結果: 服務器將上傳結果以 JSON 格式返回給前端,前端根據結果進行相應的提示和處理。
運行效果:
圖片
若想獲取項目完整代碼以及其他文章的項目源碼,且在代碼編寫時遇到問題需要咨詢交流,歡迎加入下方的知識星球。
項目結構
src
├── main
│ ├── java
│ │ └── com.icoderoad.largefile
│ │ ├── config
│ │ │ └── FileUploadProperties.java // 讀取配置文件的類
│ │ ├── controller
│ │ │ └── FileUploadController.java // 文件上傳控制器
│ │ ├── service
│ │ │ └── FileProcessingService.java // 文件處理服務(含分塊處理邏輯)
│ ├── resources
│ │ ├── application.yml // 配置文件
│ │ ├── templates
│ │ │ └── index.html // 文件上傳頁面
└─── └───pom.xml // 項目依賴配置
POM 文件配置
在 pom.xml 中引入必要的依賴,包括 Spring Boot、Thymeleaf、文件上傳和 JSON 處理等。
<?xml versinotallow="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.icoderoad</groupId>
<artifactId>largefile-upload</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>largefile-upload</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Thymeleaf 模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- 文件上傳相關依賴 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
YAML 配置文件 application.yml
在 application.yml 中配置文件上傳相關屬性。
server:
port: 8080
spring:
servlet:
multipart:
max-file-size: 200MB
max-request-size: 210MB
file-upload:
upload-dir: /tmp/uploads
chunk-size: 1MB # 分塊大小
讀取配置類 FileUploadProperties.java
package com.icoderoad.largefile.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "file-upload")
public class FileUploadProperties {
private String uploadDir;
private String chunkSize; // 分塊大小
}
文件處理服務 FileProcessingService.java
在服務層中實現分塊處理的邏輯,包括接收文件塊并按順序保存。
package com.example.largefile.service;
import com.example.largefile.config.FileUploadProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
@Service
@RequiredArgsConstructor
public class FileProcessingService {
private final FileUploadProperties fileUploadProperties;
// 保存單個文件塊
public void saveFileChunk(MultipartFile file, String filename, int chunkIndex) throws Exception {
// 獲取上傳目錄
String uploadDir = fileUploadProperties.getUploadDir();
Path uploadPath = Paths.get(uploadDir);
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
// 保存文件塊到臨時文件
File outputFile = new File(uploadDir + File.separator + filename + ".part" + chunkIndex);
try (InputStream inputStream = file.getInputStream();
FileOutputStream outputStream = new FileOutputStream(outputFile)) {
byte[] buffer = new byte[1024 * 1024]; // 1MB 緩沖區
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
}
// 合并文件塊
public void mergeFileChunks(String filename, int totalChunks) throws Exception {
String uploadDir = fileUploadProperties.getUploadDir();
File outputFile = new File(uploadDir + File.separator + filename);
try (FileOutputStream outputStream = new FileOutputStream(outputFile, true)) {
for (int i = 0; i < totalChunks; i++) {
File chunkFile = new File(uploadDir + File.separator + filename + ".part" + i);
try (FileInputStream inputStream = new FileInputStream(chunkFile)) {
byte[] buffer = new byte[1024 * 1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
// 刪除塊文件
chunkFile.delete();
}
}
}
}
文件上傳控制器 FileUploadController.java
控制器接收分塊文件,并在所有塊接收完成后調用服務合并文件。返回 JSON 格式的數據,提示前端上傳進度和狀態。
package com.icoderoad.largefile.controller;
import java.util.HashMap;
import java.util.Map;
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 com.icoderoad.largefile.service.FileProcessingService;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
@RequestMapping("/upload")
public class FileUploadController {
private final FileProcessingService fileProcessingService;
@PostMapping("/chunk")
public Map<String, Object> handleFileChunkUpload(
@RequestParam("file") MultipartFile file,
@RequestParam("filename") String filename,
@RequestParam("chunkIndex") int chunkIndex,
@RequestParam("totalChunks") int totalChunks) {
Map<String, Object> response = new HashMap<>();
try {
fileProcessingService.saveFileChunk(file, filename, chunkIndex);
response.put("status", "chunk_uploaded");
response.put("chunkIndex", chunkIndex);
// 如果是最后一個塊,進行文件合并
if (chunkIndex == totalChunks - 1) {
fileProcessingService.mergeFileChunks(filename, totalChunks);
response.put("status", "file_uploaded");
}
} catch (Exception e) {
response.put("status", "error");
response.put("message", e.getMessage());
}
return response;
}
}
前端頁面 index.html
前端頁面使用 jQuery 分塊上傳文件,并使用 Bootstrap 的提示組件顯示上傳進度和結果。
在 src/main/resources/templates 目錄下創建 index.html 文件:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>大文件上傳</title>
<link rel="stylesheet" >
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
</head>
<body>
<div class="container">
<h2 class="mt-5">大文件上傳</h2>
<form id="uploadForm">
<div class="form-group">
<label for="file">選擇文件</label>
<input type="file" class="form-control-file" id="file" name="file">
</div>
<button type="button" id="uploadBtn" class="btn btn-primary">上傳</button>
</form>
<div id="uploadProgress" class="alert alert-info mt-3" style="display:none;"></div>
</div>
<script>
$(function() {
$('#uploadBtn').on('click', function() {
var file = $('#file')[0].files[0];
if (!file) {
alert('請選擇文件進行上傳');
return;
}
var chunkSize = 1024 * 1024; // 1MB
var totalChunks = Math.ceil(file.size / chunkSize);
for (var i = 0; i < totalChunks; i++) {
var chunk = file.slice(i * chunkSize, (i + 1) * chunkSize);
var formData = new FormData();
formData.append('file', chunk);
formData.append('filename', file.name);
formData.append('chunkIndex', i);
formData.append('totalChunks', totalChunks);
$.ajax({
url: '/upload/chunk',
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function (response) {
if (response.status === 'chunk_uploaded') {
$('#uploadProgress').show().text('已上傳塊: ' + (response.chunkIndex + 1) + '/' + totalChunks);
} else if (response.status === 'file_uploaded') {
$('#uploadProgress').removeClass('alert-info').addClass('alert-success').text('文件上傳完成');
}
}
});
}
});
});
</script>
</body>
</html>
總結
本文深入探討了如何在 Spring Boot3.3 中實現大文件分塊處理與上傳,通過分塊的方式降低內存壓力,并且結合 jQuery 前端實現了分塊提交,利用 Bootstrap 進行用戶上傳狀態的提示。通過這種方式,既提高了系統的穩定性,也增強了用戶體驗。在實際應用中,這種技術可以廣泛應用于音視頻文件上傳、日志文件處理等場景,確保系統在處理大數據時依然能高效、穩定地運行。