Java实现文件分片上传

Java实现文件分片上传

为什么要使用分片上传

在需要上传文件时,不可避免地会遇到上传文件内容过大,上传时间太长地问题,采用文件分片上传就可以解决这个问题。

什么是分片上传?

简单的说就是本来是需要一次搬一个很大的东西,比如是一大桶水,一次搬起来比较费事费力。我们可以把这一大桶水分装在几个或几十个或者更多的小瓶里,这样搬运起来就比较省力,也比较方便,等到目的地后,我们在将这些小瓶子里的水都倒回大桶里,这样就完成了一大桶水的搬运工作。这个将一大桶水分成许多小瓶子的过程就是分片的过程,最后将水倒回大桶的过程就是合并的过程。分片与合并也是文件分片上传的重要过程。

前后端代码

这个分片的过程是在前端实现的,上传完成后的合并工作是后端完成的。

前端代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>JS分片上传-极速上传</title>
</head>
<body>
<input type="file" name="slice" id="slice" >
<br/>
</body>
<script src="http://libs.baidu.com/jquery/1.8.3/jquery.min.js"></script>
<script type="text/javascript">
    $("#slice").change(function(event) {
        var file = $("#slice")[0].files[0];
        PostFile(file,0);
    });
    //执行分片上传
    function PostFile(file,i, uuid){
        var name = file.name,                           //文件名
            size = file.size,                           //总大小
            shardSize = 10 * 1024 * 1024,                //以10MB为一个分片,每个分片的大小
            shardCount = Math.ceil(size / shardSize);   //总片数
        if(i >= shardCount){
            return;
        }
        //判断uuid是否存在
        if (uuid === null || uuid === undefined) {
            uuid = guid();
        }
        //console.log(size,i+1,shardSize);  //文件总大小,第一次,分片大小//
        var start = i * shardSize;
        var end = start + shardSize;
        var packet = file.slice(start, end);  //将文件进行切片
        /*  构建form表单进行提交  */
        var form = new FormData();
        form.append("uuid", uuid);// 前端生成uuid作为标识符传个后台每个文件都是一个uuid防止文件串了
        form.append("data", packet); //slice方法用于切出文件的一部分
        form.append("name", name);
        form.append("totalSize", size);
        form.append("total", shardCount); //总片数
        form.append("index", i + 1); //当前是第几片
        $.ajax({
            url: "http://127.0.0.1:8080/index/doPost",
            type: "POST",
            data: form,
            //timeout:"10000",  //超时10秒
            async: true, //异步
            dataType:"json",
            processData: false, //很重要,告诉jquery不要对form进行处理
            contentType: false, //很重要,指定为false才能形成正确的Content-Type
            success: function (msg) {
                console.log(msg);
                /*  表示上一块文件上传成功,继续下一次  */
                if (msg.status === 201) {
                    form = '';
                    i++;
                    PostFile(file, i, uuid);
                } else if (msg.status === 502) {
                    form = '';
                    /*  失败后,每2秒继续传一次分片文件  */
                    setInterval(function () { PostFile(file, i, uuid) }, 2000);
                } else if (msg.status === 200) {
                    merge(uuid, name)
                    console.log("上传成功");
                } else if (msg.status === 500) {
                    console.log('第'+msg.i+'次,上传文件有误!');
                } else {
                    console.log('未知错误');
                }
            }
        })
    }

    function merge(uuid, fileName) {
        $.ajax({
            url: "http://127.0.0.1:8080/index/merge",
            type: "GET",
            data: {uuid: uuid, newFileName: fileName},
            //timeout:"10000",  //超时10秒
            async: true, //异步
            dataType:"json",
            success: function (msg) {
                console.log(msg);
            }
        })
    }

    function guid() {
        return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            var r = Math.random() * 16 | 0,
                v = c === 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    }
</script>
</html>

后端代码:

import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@Controller
@RequestMapping("/index")
public class test_controller {
    private static final String fileUploadTempDir = "D:/portalupload/fileuploaddir";
    private static final String fileUploadDir = "D:/portalupload/file";
    @RequestMapping("/doPost")
    @ResponseBody
    public Map fragmentation(HttpServletRequest req, HttpServletResponse resp) {
        resp.addHeader("Access-Control-Allow-Origin", "*");
        Map<String, Object> map = new HashMap<>();

        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) req;

        // 获得文件分片数据
        MultipartFile file = multipartRequest.getFile("data");
//        分片第几片
        int index = Integer.parseInt(multipartRequest.getParameter("index"));
//        总片数
        int total = Integer.parseInt(multipartRequest.getParameter("total"));
//        获取文件名
        String fileName = multipartRequest.getParameter("name");
        String name = fileName.substring(0, fileName.lastIndexOf("."));
        String fileEnd = fileName.substring(fileName.lastIndexOf("."));
//        前端uuid,作为标识
        String uuid = multipartRequest.getParameter("uuid");

        File uploadFile = new File(fileUploadTempDir + "/" + uuid, uuid + name + index + ".tem");

        if (!uploadFile.getParentFile().exists()) {
            uploadFile.getParentFile().mkdirs();
        }

        if (index < total) {
            try {
                file.transferTo(uploadFile);
                // 上传的文件分片名称
                map.put("status", 201);
                return map;
            } catch (IOException e) {
                e.printStackTrace();
                map.put("status", 502);
                return map;
            }
        } else {
            try {
                file.transferTo(uploadFile);
                // 上传的文件分片名称
                map.put("status", 200);
                return map;
            } catch (IOException e) {
                e.printStackTrace();
                map.put("status", 502);
                return map;
            }
        }
    }
    @RequestMapping(value = "/merge", method = RequestMethod.GET)
    @ResponseBody
    public Map merge(String uuid, String newFileName) {
        System.out.println(newFileName);
        Map retMap = new HashMap();
        try {
            File dirFile = new File(fileUploadTempDir + "/" + uuid);
            if (!dirFile.exists()) {
                throw new RuntimeException("文件不存在!");
            }
            //分片上传的文件已经位于同一个文件夹下,方便寻找和遍历(当文件数大于十的时候记得排序用冒泡排序确保顺序是正确的)
            String[] fileNames = dirFile.list();
            String name = newFileName.substring(0, newFileName.lastIndexOf("."));
            Arrays.sort(fileNames, (o1,o2)->{
                int i1 = Integer.parseInt(o1.substring(o1.indexOf(name)+name.length()).split("\\.tem")[0]);
                int i2 = Integer.parseInt(o2.substring(o2.indexOf(name)+name.length()).split("\\.tem")[0]);
                return i1 - i2;
            });

//       创建空的合并文件
            File targetFile = new File(fileUploadDir, newFileName);
            if (!targetFile.getParentFile().exists()) {
                targetFile.getParentFile().mkdirs();
            }
            RandomAccessFile writeFile = new RandomAccessFile(targetFile, "rw");

            long position = 0;
            for (String fileName : fileNames) {
                System.out.println(fileName);
                File sourceFile = new File(fileUploadTempDir + "/" + uuid, fileName);
                RandomAccessFile readFile = new RandomAccessFile(sourceFile, "rw");
                int chunksize = 1024 * 3;
                byte[] buf = new byte[chunksize];
                writeFile.seek(position);
                int byteCount;
                while ((byteCount = readFile.read(buf)) != -1) {
                    if (byteCount != chunksize) {
                        byte[] tempBytes = new byte[byteCount];
                        System.arraycopy(buf, 0, tempBytes, 0, byteCount);
                        buf = tempBytes;
                    }
                    writeFile.write(buf);
                    position = position + byteCount;
                }
                readFile.close();
                FileUtils.deleteQuietly(sourceFile);//删除缓存的临时文件
            }
            writeFile.close();
            retMap.put("code", "200");
        }catch (IOException e){
            e.printStackTrace();
            retMap.put("code", "500");
        }
        return retMap;
    }
}

测试:

在这里插入图片描述

可以看到文件进行了分片上传,我是上传了一个系统镜像文件,4GB多,上传的也是非常的快。

  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
实现文件分片上传并且断点续传的一种常见方案是使用HTTP协议,将大文件进行分片上传,每个分片的大小可以根据具体情况设置,通常是几十KB到几百KB不等。上传过程中,服务器接收到每个分片后,将其存储到磁盘上。 同时,为了实现断点续传,客户端需要在上传前检查服务器上是否已经存在相同的文件。如果已存在,则客户端需要向服务器发送一个请求,以获取已上传分片的信息,然后继续上传上传分片。 下面是使用Java语言实现文件分片上传并且断点续传的示例代码: ```java import java.io.*; import java.net.HttpURLConnection; import java.net.URL; public class FileUploader { private static final String BOUNDARY = "----WebKitFormBoundary7MA4YWxkTrZu0gW"; public static void main(String[] args) throws Exception { String filePath = "C:\\test\\largeFile.zip"; String url = "http://localhost:8080/upload"; File file = new File(filePath); long fileSize = file.length(); int chunkSize = 1024 * 1024; // 1MB int chunkCount = (int) Math.ceil((double) fileSize / chunkSize); for (int i = 0; i < chunkCount; i++) { int start = i * chunkSize; int end = (i + 1) * chunkSize; if (end > fileSize) { end = (int) fileSize; } uploadChunk(url, file.getName(), i, chunkCount, start, end, file); } } private static void uploadChunk(String url, String fileName, int chunkIndex, int chunkCount, int start, int end, File file) throws Exception { URL uploadUrl = new URL(url); HttpURLConnection connection = (HttpURLConnection) uploadUrl.openConnection(); connection.setRequestMethod("POST"); connection.setDoOutput(true); connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY); DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); outputStream.writeBytes("--" + BOUNDARY + "\r\n"); outputStream.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\"" + fileName + "\"\r\n"); outputStream.writeBytes("Content-Type: application/octet-stream\r\n\r\n"); FileInputStream inputStream = new FileInputStream(file); inputStream.skip(start); byte[] buffer = new byte[1024]; int len; int uploadedBytes = start; while (uploadedBytes < end && (len = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, len); uploadedBytes += len; } inputStream.close(); outputStream.writeBytes("\r\n--" + BOUNDARY + "\r\n"); outputStream.writeBytes("Content-Disposition: form-data; name=\"chunkIndex\"\r\n\r\n"); outputStream.writeBytes(String.valueOf(chunkIndex) + "\r\n"); outputStream.writeBytes("--" + BOUNDARY + "\r\n"); outputStream.writeBytes("Content-Disposition: form-data; name=\"chunkCount\"\r\n\r\n"); outputStream.writeBytes(String.valueOf(chunkCount) + "\r\n"); outputStream.writeBytes("--" + BOUNDARY + "--\r\n"); outputStream.flush(); outputStream.close(); int responseCode = connection.getResponseCode(); if (responseCode != HttpURLConnection.HTTP_OK) { throw new RuntimeException("Failed to upload chunk: " + chunkIndex); } } } ``` 上述代码将文件分成若干个分片,每个分片大小为1MB,然后逐个上传到服务器。其中,`uploadChunk()`方法用于上传单个分片,它将分片数据和分片信息一起发送到服务器。服务器需要根据分片信息将所有分片组合成完整的文件。 此外,为了实现断点续传,还需要在服务器端实现一个接口,用于获取已上传分片的信息,并返回给客户端。在上传前,客户端需要向服务器发送一个请求,以获取已上传分片的信息,然后继续上传上传分片。在上传过程中,客户端需要记录已上传分片信息,以便在上传失败后能够恢复上传进度。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值