java大文件傳輸方案總結(jié)以及切分與合并代碼記錄

在文件傳輸中趴荸,有時候文件過大,對于大文件傳輸問題一般有以下幾個方案:
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

可以看到,切分合并文件成功纵势,文件哈希值沒變踱阿。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市吨悍,隨后出現(xiàn)的幾起案子扫茅,更是在濱河造成了極大的恐慌,老刑警劉巖育瓜,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件葫隙,死亡現(xiàn)場離奇詭異,居然都是意外死亡躏仇,警方通過查閱死者的電腦和手機(jī)恋脚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來焰手,“玉大人糟描,你說我怎么就攤上這事∈槠蓿” “怎么了船响?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長躲履。 經(jīng)常有香客問我见间,道長,這世上最難降的妖魔是什么工猜? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任米诉,我火速辦了婚禮,結(jié)果婚禮上篷帅,老公的妹妹穿的比我還像新娘史侣。我一直安慰自己拴泌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布惊橱。 她就那樣靜靜地躺著蚪腐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪李皇。 梳的紋絲不亂的頭發(fā)上削茁,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天,我揣著相機(jī)與錄音掉房,去河邊找鬼茧跋。 笑死,一個胖子當(dāng)著我的面吹牛卓囚,可吹牛的內(nèi)容都是我干的瘾杭。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼哪亿,長吁一口氣:“原來是場噩夢啊……” “哼粥烁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蝇棉,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤讨阻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后篡殷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钝吮,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年板辽,在試婚紗的時候發(fā)現(xiàn)自己被綠了奇瘦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡劲弦,死狀恐怖耳标,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情邑跪,我是刑警寧澤次坡,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站画畅,受9級特大地震影響砸琅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜夜赵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望乡革。 院中可真熱鬧寇僧,春花似錦摊腋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至细办,卻和暖如春橙凳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背笑撞。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工岛啸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人茴肥。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓坚踩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瓤狐。 傳聞我的和親對象是個殘疾皇子瞬铸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容