java大文件传输方案总结以及切分与合并代码记录

在文件传输中,有时候文件过大,对于大文件传输问题一般有以下几个方案:
1.如果是前端传输到后台,可以参考我之前写的一篇:java利用websocket实现分段上传大文件并显示进度信息
2.如果是后台通过接口传输到后台,可以通过httpclient用application/octet-stream的形式通过流传输大文件,
可以参考我之前写的一篇: httpClient请求https-ssl验证的几种方法 ,在这篇里搜索application/octet-stream即可找到httpClient通过application/octet-stream来传输文件的方法。
但这种通常会受到nginx上最大文件传输的限制,如果特别大的文件,比如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根据文件块大小计算出位置,
多次请求都可以携带参数,比如定一个uuid,同一个文件多次请求下此uuid不变,后台通过此参数来识别是否是同个文件,不同文件传输时uuid是不一样的,通过multipart/form-data就可以传输过去。

业务代码不多写了,只把核心的文件切分和文件合并的java实现记录一下:

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格式转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 {
        // 打开一个随机访问文件流,按读写方式
        RandomAccessFile randomFile = new RandomAccessFile(filePath, "rw");
        // 文件长度,字节数
        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();
          }
    }
    
    /**
     * 获取当前项目下的
     *
     * @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);
    }

    /**
     * 创建多层级文件夹
     *
     * @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  输出的内容
     * @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 目标文件目录
     * @param num 分割大小(字节)
     */
    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);
            // 创建规定大小的byte数组
            byte[] b = new byte[byteNum];
            int len = 0;
            // name为以后的小文件命名做准备
            int nameNum = 1;
            // 遍历将大文件读入byte数组中,当byte数组读满后写入对应的小文件中
            while ((len = fis.read(b)) != -1) {
                File nameDir = new File(endDir + nameNum + "/");
                if(!nameDir.exists()) {
                    nameDir.mkdirs();
                }
                // 分别找到原大文件的文件名和文件类型,为下面的小文件命名做准备
                String fileName = file.getName();
                FileOutputStream fos = new FileOutputStream(endDir + nameNum + "/" + fileName);
                // 将byte数组写入对应的小文件中
                fos.write(b, 0, len);
                // 结束资源
                fos.close();
                nameNum++;
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fis != null) {
                    // 结束资源
                    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));
        }
    }
}

运行如上的main方法,其中源文件是一个1.54G的大文件,切分文件按每个25M,切分后再依次合并,对比切分前和合并后的文件大小和文件哈希值,打印结果如下:

源文件大小: 1657487360 源文件哈希值:49e82c5804841f6b0d749c5d40d857d0cbef904e06fb60c7c9d1750ce6b0b486
合并后文件大小: 1657487360 源文件哈希值:49e82c5804841f6b0d749c5d40d857d0cbef904e06fb60c7c9d1750ce6b0b486

可以看到,切分合并文件成功,文件哈希值没变。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,706评论 4 366
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,002评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,462评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,375评论 0 216
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,763评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,849评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,033评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,768评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,490评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,734评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,204评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,566评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,227评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,137评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,934评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,926评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,774评论 2 274

推荐阅读更多精彩内容