JVM記憶體分配 先瞭解下JVM中的記憶體分配,此處以hotspot vm為例(官方jdk採用的vm) 程式計數器 棧 1. 虛擬機棧 2. 本地方法棧 Java堆 堆記憶體是各個線程共用的區域 方法區 它用於存儲已經被虛擬機載入的類信息、常量、靜態變數、即編譯器編譯後的代碼等數據。靜態變數、常量在方法區 ...
一、前言和開發環境及配置
可以轉載,但請註明出處。
之前自己寫的SpringBoot整合MongoDB的聚合查詢操作,感興趣的可以點擊查閱。
https://www.cnblogs.com/zaoyu/p/springboot-mongodb.html
使用mongodb存儲文件並實現讀取,通過springboot集成mongodb操作。
可以有兩種實現方式:
1. 單個文件小於16MB的,可以直接把文件轉成二進位或者使用如Base64編碼對文件做編碼轉換,以二進位或者string格式存入mongodb。
讀取時,把二進位數據或者string數據轉成對應的IO流或做解碼,再返回即可。
2. 對於單個文件大於16MB的,可以使用mongodb自帶的GridFS
開發環境、工具:JDK1.8,IDEA 2021
Springboot版本:2.7.5
Mongodb依賴版本:4.6.1
SpringBoot的配置 application.properties 如下
# 應用名稱 spring.application.name=demo #server.port=10086 不配置的話,預設8080 # springboot下mongoDB的配置參數。 spring.data.mongodb.host=localhost spring.data.mongodb.port=27017 # 指定資料庫庫名 spring.data.mongodb.database=temp
二、實現步驟和代碼
1. 小文件存儲
1.1 說明和限制
由於MongoDB限制單個文檔大小不能超過16MB,所以這種方式僅適用於單個文件小於16MB的。
如果傳入大於16MB的文件,存儲是會失敗的,報錯如下。
1.2 實現代碼
package com.onepiece.mongo; import org.bson.Document; import org.bson.types.Binary; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import java.io.*; import java.util.Base64; import java.util.List; /** * @author zaoyu * @description 用於演示Mongodb的文件存儲(單個文件不大於16MB) */ @SpringBootTest public class MongoSaveFiles { @Autowired private MongoTemplate mongoTemplate; // collection名 private String IMAGE_COLLECTION = "image"; // 源文件完整路徑 private String FILE_PATH = "D:\\temp\\onepiece.jpg"; // 輸出文件路徑 private String FILE_OUTPUT_PATH = "C:\\Users\\onepiece\\Desktop\\"; // 限制16MB private Long FILE_SIZE_LIMIT = 16L * 1024L * 1024L; @Test public void saveFiles(){ byte[] fileContent = null; FileInputStream fis = null; try { File file = new File(FILE_PATH); long length = file.length(); // 校驗文件大小,大於16MB返回。 這裡的操作邏輯依據你自己業務需求調整即可。 if (length >= FILE_SIZE_LIMIT) { System.out.println("文件: " + file.getAbsolutePath() + " 超出單個文件16MB的限制。" ); return; } fileContent = new byte[(int) file.length()]; fis = new FileInputStream(file); // 讀取整個文件 fis.read(fileContent); // 把文件內容以二進位格式寫入到mongodb Document document = new Document(); // fileName欄位、content欄位自定義。 document.append("fileName", file.getName()); document.append("content", new Binary(fileContent)); Document insert = mongoTemplate.insert(document, IMAGE_COLLECTION); System.out.println("文件 " + file.getName() + " 已存入mongodb,對應ID是: " + insert.get("_id").toString()); } catch (IOException e) { e.printStackTrace(); } finally { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } /** * 測試讀取並寫入到指定路徑。 */ @Test public void readAndWriteFiles(){ // 這裡也是,預設查所有,需要條件自行增加。 簡單取1條驗證。 List<Document> result = mongoTemplate.find(new Query(), Document.class, IMAGE_COLLECTION); Document document = result.get(0); // 取出存儲的二進位數據,這裡用binary.class處理。 Binary content = document.get("content", Binary.class); String fileName = document.get("fileName", String.class); try { String newFilePath = FILE_OUTPUT_PATH + fileName; // 寫入到指定路徑 FileOutputStream fos = new FileOutputStream(newFilePath); fos.write(content.getData()); } catch (IOException e) { e.printStackTrace(); } }
除了二進位的格式,也可以直接把文件用如Base64之類的編碼工具來轉碼存儲String。
@Test public void testBase64(){ saveFileWithBase64(FILE_PATH); getFileWithBase64(); } public void saveFileWithBase64(String filePath){ // 讀取文件並編碼為 Base64 格式 File file = new File(filePath); byte[] fileContent = new byte[(int) file.length()]; try (FileInputStream inputStream = new FileInputStream(file)) { inputStream.read(fileContent); } catch (IOException e) { e.printStackTrace(); } // 把讀取到的流轉成base64 String encodedString = Base64.getEncoder().encodeToString(fileContent); // 將 Base64 編碼的文件內容存儲到 MongoDB 文檔中 Document document = new Document(); document.put("fileName", file.getName()); document.put("base64Content", encodedString); Document insert = mongoTemplate.insert(document, IMAGE_COLLECTION); System.out.println("文件 " + file.getName() + " 已存入mongodb,對應ID是: " + insert.get("_id").toString()); } public void getFileWithBase64(){ Criteria criteria = Criteria.where("base64Content").exists(true); List<Document> result = mongoTemplate.find(new Query(criteria), Document.class, IMAGE_COLLECTION); Document document = result.get(0); String base64Content = document.get("base64Content", String.class); String fileName = document.get("fileName", String.class); byte[] decode = Base64.getDecoder().decode(base64Content); try { String newFilePath = FILE_OUTPUT_PATH + fileName; FileOutputStream fos = new FileOutputStream(newFilePath); fos.write(decode); System.out.println("文件已讀取並複製到指定路徑,詳情為:" + newFilePath); } catch (IOException e) { e.printStackTrace(); } }
1.3 落庫效果
直接存儲二進位數據,可以看到,使用BinData存儲,還會顯示位元組數(文件大小)。
2. 大於16MB的文件存儲,使用GridFS
2.1 gridFS簡介
GridFS is a specification for storing and retrieving files that exceed the BSON-document size limit of 16 MB.
字面直譯就是說GridFS是用來存儲大於BSON文檔限制的16MB的文件。
官方文檔 https://www.mongodb.com/docs/manual/core/gridfs/
存儲原理:GridFS 會將大文件對象分割成多個小的chunk(文件片段), 一般為256k/個,每個chunk將作為MongoDB的一個文檔(document)被存儲在chunks集合中。
每一個資料庫有一個GridFS區域,用來存儲。
需要通過先創建bucket(和OSS中一樣的概念)來存儲,一個bucket創建後,一旦有文件存入,在collections中就會自動生成2個集合來存儲文件的數據和信息,一般是bucket名字+files和bucket名字+chunks。
每個文件的實際內容被存在chunks(二進位數據)中,和文件有關的meta數據(filename,content_type,還有用戶自定義的屬性)將會被存在files集合中。
如下圖結構
2.2 實現代碼
package com.onepiece.mongo; import com.mongodb.client.MongoDatabase; import com.mongodb.client.gridfs.GridFSBucket; import com.mongodb.client.gridfs.GridFSBuckets; import com.mongodb.client.gridfs.model.GridFSUploadOptions; import org.bson.Document; import org.bson.types.ObjectId; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Query; import org.springframework.util.FileCopyUtils; import java.io.*; import java.util.List; /** * @author zaoyu * @description:使用GridFS存儲文件並做讀取。 */ @SpringBootTest public class MongoGridFS { @Autowired private MongoTemplate mongoTemplate; // GridFS下的bucket,自行指定要把文件存儲到哪個bucket。 private String BUCKET_NAME = "images"; // 源文件,即要被存儲的文件的絕對路徑 private String FILE_PATH = "D:\\temp\\onepiece.jpg"; // 存儲文件後自動生成的存儲文件信息的collection,一般是xx.files。 private String COLLECTION_NAME = "images.files"; // 用於演示接收輸出文件的路徑 private String FILE_OUTPUT_PATH = "C:\\Users\\onepiece\\Desktop\\"; @Test public void testGridFSSaveFiles() { saveToGridFS(); System.out.println("------------"); readFromGridFS(); } /** * 傳入bucketName得到指定bucket操作對象。 * * @param bucketName * @return */ public GridFSBucket createGridFSBucket(String bucketName) { MongoDatabase db = mongoTemplate.getDb(); return GridFSBuckets.create(db, bucketName); } /** * 儲存文件到GridFS */ public void saveToGridFS() { // 先調用上面方法得到一個GridFSBucket的操作對象 GridFSBucket gridFSBucket = createGridFSBucket(BUCKET_NAME); File file = new File(FILE_PATH); FileInputStream inputStream = null; try { inputStream = new FileInputStream(file); } catch (FileNotFoundException e) { e.printStackTrace(); } // 設置GridFS存儲配置,這裡是設置了每個chunk(塊)的大小為1024個位元組,也可以設置大一點。 MetaData是對文件的說明,如果不需要可以不寫。 也是以鍵值對存在,BSON格式。 GridFSUploadOptions options = new GridFSUploadOptions().chunkSizeBytes(1024).metadata(new Document("user", "onepiece")); // 調用GridFSBucket中的uploadFromStream方法,把對應的文件流傳遞進去,然後就會以binary(二進位格式)存儲到GridFS中,並得到一個文件在xx.files中的主鍵ID,後面可以用這個ID來查找關聯的二進位文件數據。 ObjectId objectId = gridFSBucket.uploadFromStream(file.getName(), inputStream, options); System.out.println(file.getName() + "已存入mongodb gridFS, 對應id是:" + objectId); } /** * 從GridFS中讀取文件 */ public void readFromGridFS() { // 這裡查找條件我先不寫,預設查所有,取第一條做驗證演示。 用Document類接收。 List<Document> files = mongoTemplate.find(new Query(), Document.class, COLLECTION_NAME); Document file = files.get(0); // 得到主鍵ID,作為等下要查詢的文件ID值。 ObjectId fileId = file.getObjectId("_id"); String filename = file.getString("filename"); // 先調用上面方法得到一個GridFSBucket的操作對象 GridFSBucket gridFSBucket = createGridFSBucket(BUCKET_NAME); // 調用openDownloadStream方法得到文件IO流。 InputStream downloadStream = gridFSBucket.openDownloadStream(fileId); FileOutputStream fileOutputStream = null; try { fileOutputStream = new FileOutputStream(FILE_OUTPUT_PATH + filename); // 把IO流直接到指定路徑的輸出流對象實現輸出。 FileCopyUtils.copy(downloadStream, fileOutputStream); } catch (IOException e) { e.printStackTrace(); } finally { try { fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }
2.3 落庫效果
bucket:
註意這裡的ID,就是files中的主鍵ID。
files collection (image.files):
chunks collection (image.chunks)
可以看到這裡的files_id就是對應image.files中的主鍵ID。文件被拆成多個chunk塊。
三、小結
對於小文件的,可以直接轉二進位存儲,對於大於等於16MB的,使用GridFS存儲。
希望這篇文章能幫到大家,有錯漏之處,歡迎指正。
請多點贊、評論~
完。