在文件傳輸中趴荸,有時候文件過大,對于大文件傳輸問題一般有以下幾個方案:
1.如果是前端傳輸?shù)胶笈_寂玲,可以參考我之前寫的一篇:java利用websocket實(shí)現(xiàn)分段上傳大文件并顯示進(jìn)度信息
2.如果是后臺通過接口傳輸?shù)胶笈_爹橱,可以通過httpclient用application/octet-stream的形式通過流傳輸大文件,
可以參考我之前寫的一篇: httpClient請求https-ssl驗(yàn)證的幾種方法 决乎,在這篇里搜索application/octet-stream即可找到httpClient通過application/octet-stream來傳輸文件的方法。
但這種通常會受到nginx上最大文件傳輸?shù)南拗婆勺绻貏e大的文件构诚,比如5g左右的,nginx上就需要配置http塊下的client_max_body_size來避免nginx報錯铆惑。
3.可以把大文件切分為多個小文件范嘱,多個小文件傳輸過去之后,再把多個小文件合并還原成大文件员魏,這種方法不管是前端傳給后臺丑蛤,還是后臺通過接口傳給其他后臺,都可以使用撕阎。
思路如下:
把大文件切分為多個小文件后受裹,可以通過多次請求的方式依次把小文件傳輸給后臺,后臺在多次接收到小文件后利用java的追加文件方法把文件合并還原虏束,為了保證傳輸順序棉饶,可以在header里增加Content-Range : bytes 0-26214400/1657487360
的格式,如:
headerMap.put(headers.CONTENT_RANGE, "bytes " + beginNum + "-" + endNum + "/" + file.length());
其中beginNum從0開始,endNum是每塊文件的length() -1的位置镇匀,file.length()是文件的總大小砰盐,同個文件多次請求時,每次請求beginNum和endNum根據(jù)文件塊大小計(jì)算出位置坑律,
多次請求都可以攜帶參數(shù),比如定一個uuid囊骤,同一個文件多次請求下此uuid不變晃择,后臺通過此參數(shù)來識別是否是同個文件,不同文件傳輸時uuid是不一樣的也物,通過multipart/form-data就可以傳輸過去宫屠。
業(yè)務(wù)代碼不多寫了,只把核心的文件切分和文件合并的java實(shí)現(xiàn)記錄一下:
package com.zhaohy.app.utils;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
public class FileUtil {
public static final String LOCAL_TEMP_PATH = System.getProperty("user.dir") + "/tmp/";
/**
* txt格式轉(zhuǎn)String
*
* @param txtPath
* @return
* @throws IOException
*/
public static String txtToStr(String txtPath) throws IOException {
StringBuilder buffer = new StringBuilder();
BufferedReader bf = null;
try {
bf = new BufferedReader(new InputStreamReader(new FileInputStream(txtPath), "UTF-8"));
String str = null;
while ((str = bf.readLine()) != null) {// 使用readLine方法滑蚯,一次讀一行
buffer.append(new String(str.getBytes(), "UTF-8"));
}
} finally {
if(null != bf) bf.close();
}
String xml = buffer.toString();
return xml;
}
public static Boolean downloadNet(String urlPath, String filePath) throws Exception {
Boolean flag = true;
int byteread = 0;
URL url;
try {
url = new URL(urlPath);
} catch (MalformedURLException e1) {
flag = false;
throw e1;
}
InputStream inStream = null;
FileOutputStream fs = null;
try {
URLConnection conn = url.openConnection();
inStream = conn.getInputStream();
fs = new FileOutputStream(filePath);
byte[] buffer = new byte[1024];
while ((byteread = inStream.read(buffer)) != -1) {
fs.write(buffer, 0, byteread);
}
} catch (Exception e) {
flag = false;
throw e;
} finally {
fs.close();
inStream.close();
}
return flag;
}
public static void downloadBytes(byte[] bytes, String filePath) throws Exception {
File file = new File(filePath);
if(!file.exists()) {
file.createNewFile();
}
FileOutputStream fs = null;
try {
fs = new FileOutputStream(filePath);
fs.write(bytes);
} finally {
fs.close();
}
}
public static void appendFile(byte[] bytes, String filePath) throws IOException {
// 打開一個隨機(jī)訪問文件流浪蹂,按讀寫方式
RandomAccessFile randomFile = new RandomAccessFile(filePath, "rw");
// 文件長度抵栈,字節(jié)數(shù)
long fileLength = randomFile.length();
// 將寫文件指針移到文件尾。
randomFile.seek(fileLength);
randomFile.write(bytes);
randomFile.close();
}
public static byte[] fileToBytes(String outFilePath) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
try (DataInputStream dis = new DataInputStream(new FileInputStream(outFilePath))) {
byte[] buffer = new byte[1024];
int len;
while ((len = dis.read(buffer)) != -1) {
dos.write(buffer, 0, len);
}
return bos.toByteArray();
}
}
/**
* 獲取當(dāng)前項(xiàng)目下的
*
* @return
*/
public static String getLocalRandomPath() {
String uid = UUID.randomUUID().toString().replaceAll("-", "");
return pathSeparateTransfer(LOCAL_TEMP_PATH + uid + "/");
}
/**
* 替換文件間隔路徑符
*
* @param filePath
* @return
*/
public static String pathSeparateTransfer(String filePath) {
if (File.separator.equals("\\")) {
filePath = filePath.replaceAll("/", "\\\\");
} else {
filePath = filePath.replaceAll("\\\\", "/");
}
return filePath;
}
/**
* 刪除文件下所有文件夾和文件
* file:文件對象
*/
public static void deleteFileAll(File file) {
if (file.exists()) {
File files[] = file.listFiles();
int len = files.length;
for (int i = 0; i < len; i++) {
if (files[i].isDirectory()) {
deleteFileAll(files[i]);
} else {
files[i].delete();
}
}
file.delete();
}
}
/**
* 刪除文件下所有文件夾和文件
* path:文件名
*/
public static void deleteFileAll(String path) {
File file = new File(path);
deleteFileAll(file);
}
/**
* 創(chuàng)建多層級文件夾
*
* @param path
* @return
*/
public static boolean createDirs(String path) {
File fileDir = new File(path);
if (!fileDir.exists()) {
return fileDir.mkdirs();
}
return true;
}
/**
* 將字符串輸出到文件
*
* @param filePath 輸出的文件夾路徑
* @param fileName 輸出的文件名稱
* @param content 輸出的內(nèi)容
* @param charset 字符編碼
*/
public static void writeTextFile(String filePath, String fileName, String content, String charset) throws IOException {
charset = StringUtils.isBlank(charset) ? "UTF-8" : charset;
createDirs(filePath);
try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath + fileName), charset))) {
bw.write(content);
} catch (IOException e) {
throw e;
}
}
public static String getTmpDir() {
String tmpDir = "/tmp/";
String os = System.getProperty("os.name");
if (os.toLowerCase().contains("windows")) {
tmpDir = "D://tmp/";
File file = new File(tmpDir);
if (!file.exists())
file.mkdir();
}
return tmpDir;
}
public static void mkdir(String path) {
File file = new File(path);
if (!file.exists()) {
file.mkdir();
} else if (!file.isDirectory()) {
file.mkdir();
}
}
public static void deleteDir(String dirPath) {
File file = new File(dirPath);
File[] fileList = file.listFiles();
for (File f : fileList) {
if (f.exists()) {
if (f.isDirectory()) {
deleteDir(f.getAbsolutePath());
} else {
f.delete();
}
}
}
if (file.exists())
file.delete();
}
/**
*
* @param srcFile 源filePath
* @param endDir 目標(biāo)文件目錄
* @param num 分割大小(字節(jié))
*/
public static void cutFile(String srcFile, String endDir, int byteNum) {
FileInputStream fis = null;
File file = null;
try {
fis = new FileInputStream(srcFile);
file = new File(srcFile);
// 創(chuàng)建規(guī)定大小的byte數(shù)組
byte[] b = new byte[byteNum];
int len = 0;
// name為以后的小文件命名做準(zhǔn)備
int nameNum = 1;
// 遍歷將大文件讀入byte數(shù)組中坤次,當(dāng)byte數(shù)組讀滿后寫入對應(yīng)的小文件中
while ((len = fis.read(b)) != -1) {
File nameDir = new File(endDir + nameNum + "/");
if(!nameDir.exists()) {
nameDir.mkdirs();
}
// 分別找到原大文件的文件名和文件類型古劲,為下面的小文件命名做準(zhǔn)備
String fileName = file.getName();
FileOutputStream fos = new FileOutputStream(endDir + nameNum + "/" + fileName);
// 將byte數(shù)組寫入對應(yīng)的小文件中
fos.write(b, 0, len);
// 結(jié)束資源
fos.close();
nameNum++;
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fis != null) {
// 結(jié)束資源
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
//切分文件
String filePath = "D:/upload/RDO.SOFTWARE_00000B7H0F.iso";
File file = new File(filePath);
System.out.println("源文件大小: " + file.length() + " 源文件哈希值:" + HashUtils.getHashValue(filePath, HashAlgorithm.SHA256));
String targetDir = getLocalRandomPath();
createDirs(targetDir);
cutFile(filePath, targetDir, 1024*1024*25);//切分25M一個
//合并文件
File targetDirFile = new File(targetDir);
if(targetDirFile.exists() && targetDirFile.isDirectory()) {
int nameNum = 1;
String nameNumDirPath = targetDir + nameNum + "/";
String targetFilePath = nameNumDirPath + file.getName();
File nameNumDir = new File(nameNumDirPath);
String newFilePath = targetDir + file.getName();
File newFile = new File(newFilePath);
while(null != nameNumDir && nameNumDir.exists()) {
byte[] fileBytes = fileToBytes(targetFilePath);
appendFile(fileBytes, newFilePath);
nameNum++;
nameNumDirPath = targetDir + nameNum + "/";
targetFilePath = nameNumDirPath + file.getName();
nameNumDir = new File(nameNumDirPath);
}
System.out.println("合并后文件大戌趾铩: " + newFile.length() + " 源文件哈希值:" + HashUtils.getHashValue(newFilePath, HashAlgorithm.SHA256));
}
}
}
運(yùn)行如上的main方法产艾,其中源文件是一個1.54G的大文件,切分文件按每個25M滑绒,切分后再依次合并闷堡,對比切分前和合并后的文件大小和文件哈希值,打印結(jié)果如下:
源文件大幸晒省: 1657487360 源文件哈希值:49e82c5804841f6b0d749c5d40d857d0cbef904e06fb60c7c9d1750ce6b0b486
合并后文件大懈芾馈: 1657487360 源文件哈希值:49e82c5804841f6b0d749c5d40d857d0cbef904e06fb60c7c9d1750ce6b0b486
可以看到,切分合并文件成功纵势,文件哈希值沒變踱阿。