大文件-分片下载

0.需求背景

  1. 文件过大,单次文件流数据过多
  2. 需要有下载进度
  3. 需要提高下载速度

1.大文件下载的解决思路

  1. 获取文件大小,根据实际网络情况设置分片大小,确定份数
  2. 根据分片的大小索引,获取分片的流数据
  3. 所有的分片下载后,合并成最终文件

2.核心代码参考

前端 vue3
const downFile = async () => {  
  progress.value = 0;  
  finalFine.value = 0;  
  
  //获取文件的大小  
  let {fileName, contentLength} = await getFileSize();  
  
  //根据大小,去获取  
  let RANGE_SIZE = 1024 * 1024  
  let total = Math.ceil(contentLength / RANGE_SIZE);//向上取整  
  
  let fileArr = []  
  for (let i = 0; i < total; i++) {  
    let start = i * RANGE_SIZE  
    let end = start + RANGE_SIZE - 1  
    let range = end >= contentLength ? contentLength - start : RANGE_SIZE  
    fileArr.push({  
      index: i,  
      start: start,  
      end: end,  
      range: range,  
      chunk: undefined,  
      downloadTimes: 0  
    })  
  }  
  getFileChunkArray(fileArr, total, fileName);  
}  
  
async function getFileSize() {  
  //获取文件的大小  
  let response = await axios({  
    method: 'get',  
    url: 'bigfile/getFileSize',  
    params: {}  
  })  
  let result = response.data;  
  let contentLength = result.contentLength;  
  let fileName = result.fileName;  
  console.log("contentLength: ", contentLength);  
  return {fileName: fileName, contentLength: contentLength}  
}  
  
const getFileChunkArray = async (fileArr, total, fileName) => {  
  let pool = [] // 并发池  
  const max = 10 // 并发池异步操作的数量 ★ 设置太大或者太小,都不合适,会引起并发问题  
  let failList = [] // 下载失败的文件列表  
  let finish = 0  // 下载完成的数量  
  
  if (fileArr.length === 0) {  
    fineList.sort((a, b) => a.index - b.index);  
    let arrayBufferArray = fineList.map(item => item.chunk);  
  
    const blob = new Blob(arrayBufferArray, {type: 'application/octet-stream'});  
    const a = document.createElement('a');  
    a.href = URL.createObjectURL(blob);  
    a.download = fileName;  
    a.click();  
  }  
  
  for (const item of fileArr) {  
    let index = item.index  
    let start = item.start  
    let end = item.end  
    let range = item.range  
    let downloadTimes = item.downloadTimes  
    if (downloadTimes > 5) {  
      console.log("异常,已执行超过5次 ----- ")  
      break  
    }  
    console.log("range: ", index, `bytes=${start}-${end}`, range);  
  
    let res = axios({  
      method: 'get',  
      url: 'bigfile/download',  
      params: {  
        index: index,  
        start: start,  
        end: end,  
        range: range,  
      },  
      responseType: 'blob'  
    })  
  
    // 把下载文件的异步操作放入并发池里  
    pool.push(res)  
    if (pool.length === max) {  
      // 每当并发池跑完一个任务,就再塞入一个任务  
      await Promise.race(pool)  
    }  
  
    res.then((response) => {  
      let clip = response.data;  
      fineList.push({...item, chunk: clip})  
      finalFine.value++  
      progress.value = Math.round((finalFine.value) / total * 10000) / 100  
      // console.log('response: ', response)  
      console.log('下载进度 ----- ', finalFine.value, total, progress.value + '%')  
    }).catch((response) => {  
      console.log('response-error: ', response)  
      // 请求失败,从并发池里移除,添加到失败的文件列表  
      const index = pool.findIndex(it => it === res)  
      pool.splice(index, 1)  
      item.downloadTimes++;  
      failList.push(item)  
    }).finally(() => {  
      finish++  
      // 如果请求都完成了,递归调用自己,把下载失败的文件列表再下载一次  
      if (finish === fileArr.length) {  
        getFileChunkArray(failList, total, fileName);  
      }  
    })  
  }  
}  
后端java
/**  
 * 获取文件大小  
 *  
 * @param request 请求  
 * @return {@link JSONObject }  
 * @author Ysy  
 **/@RequestMapping(value = "/getFileSize", method = RequestMethod.GET)  
public JSONObject getFileSize(HttpServletRequest request) {  
    JSONObject result = new JSONObject();  
  
    String filePath = "D:\\" + downloadFile;  
    System.out.println(filePath);  
    File file = new File(filePath);  
  
    long contentLength = 0;  
    if (file.exists()) {  
       contentLength = file.length();  
    }  
    result.put("contentLength", contentLength);  
    result.put("fileName", downloadFile);  
    return result;  
}  
  
/**  
 * 下载  
 *  
 * @param request  请求  
 * @param response 响应  
 * @param index    指数  
 * @param start    开始  
 * @param end      结束  
 * @param range    范围  
 * @return {@link ResponseEntity }  
 * @author Ysy  
 **/@RequestMapping(value = "download", method = RequestMethod.GET)  
public ResponseEntity download(HttpServletRequest request, HttpServletResponse response,  
                         @RequestParam(value = "index", defaultValue = "") long index,  
                         @RequestParam(value = "start", defaultValue = "") long start,  
                         @RequestParam(value = "end", defaultValue = "") long end,  
                         @RequestParam(value = "range", defaultValue = "") int range  
) throws IOException {  
    String filePath = "D:\\" + downloadFile;  
    File file = new File(filePath);  
    if (!file.exists()) {  
    }  
    System.out.println(index);  
  
    // 返回 ResponseEntity    
    try (  
       FileInputStream fis = new FileInputStream(file);  
       FileChannel channel = fis.getChannel()) {  
       // 设置起始位置  
       channel.position(start);  
  
       ByteBuffer buffer = ByteBuffer.allocate(range);  
       int bytesRead = channel.read(buffer);  
  
       if (bytesRead > 0) {  
          buffer.flip();  
          byte[] data = new byte[bytesRead];  
          buffer.get(data);  
  
          HttpHeaders headers = new HttpHeaders();  
          headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);  
          headers.setContentLength(bytesRead);  
  
          return new ResponseEntity<>(data, headers, HttpStatus.OK);  
       } else {  
          return new ResponseEntity<>(HttpStatus.NO_CONTENT);  
       }  
    }  
}
效果

在这里插入图片描述

4.案例源码

百度网盘 链接:https://pan.baidu.com/s/15TZCM4dfgDRRBneyHJEkaQ

码微信小程序。获取 提取码

大文件 分片下载.png|600

分片下载和A标签下载对比

1. 基本概念

- 分片下载:

  • 分片下载是一种将文件分割成多个部分(片)进行下载的技术。它通过多线程或多任务的方式,同时从服务器获取文件的不同部分。例如,一个大文件被分成10个部分,程序可以同时开启多个连接,分别下载这10个部分,然后在本地将这些部分组合成完整的文件。这种方式可以充分利用网络带宽,提高下载速度。在一些专业的下载工具(如迅雷)中,就广泛应用了分片下载技术。

- A标签下载:

  • <a>标签是HTML中的超链接标签。当使用<a>标签进行下载时,用户点击链接后,浏览器会根据链接指向的资源类型和服务器的配置,尝试以默认的方式下载文件。例如,<a href="example.pdf" download>下载文件</a>,当用户点击这个链接时,浏览器会开始下载example.pdf文件。它的下载过程相对比较简单直接,通常是单线程的,由浏览器直接处理下载请求。

1. 下载速度

- 分片下载:

  • 由于采用多线程或多任务方式,多个分片可以同时下载,能够更好地利用网络带宽。特别是在下载大型文件和网络环境较好的情况下,分片下载可以显著提高下载速度。例如,对于一个1GB的文件,如果服务器和网络允许,分片下载可以同时从服务器获取多个部分,每个部分都能占用一定的带宽进行传输,从而加快整体下载进度。

- A标签下载:

  • 通常是单线程下载,速度主要取决于网络带宽以及服务器对该文件下载的限制。对于小文件,单线程下载可能足够快,但对于大型文件,其下载速度可能会受到限制,因为它无法像分片下载那样同时获取文件的多个部分来充分利用带宽。

2. 资源占用情况

- 分片下载:

  • 因为涉及多个线程或任务同时运行,会占用较多的系统资源,如CPU和内存。多个线程的调度和文件分片的管理都需要一定的系统资源支持。例如,在进行大量分片下载时,可能会导致计算机的CPU使用率升高,内存占用量增大,从而可能会影响计算机的其他操作性能。

- A标签下载:

  • 资源占用相对较少,主要是浏览器本身的资源占用。它只是通过浏览器的单线程下载机制进行文件下载,不需要额外的复杂线程管理和文件分片组合操作,所以对系统资源的需求比较低。

3. 稳定性和可靠性

- 分片下载:

  • 相对复杂的下载过程可能会导致一些稳定性问题。例如,如果其中一个分片的下载出现错误(如网络中断、服务器故障等),可能需要重新下载该分片或者采取一些错误恢复措施。不过,好的分片下载工具会有相应的机制来处理这些情况,比如重试下载出错的分片一定次数后,如果还不行,可能会从其他服务器或节点获取该分片。

- A标签下载:

  • 比较简单直接,相对更稳定。如果下载过程中出现问题(如网络中断),浏览器通常会提供简单的恢复机制,如重新开始下载或者提示用户是否继续等。因为它没有复杂的分片管理,所以出现错误后的恢复相对比较容易理解和操作。

4. 适用场景

- 分片下载:

  • 适用于大型文件的下载,尤其是在需要快速下载并且网络条件允许的情况下。例如,下载大型的软件安装包、高清视频文件等。同时,在一些需要对下载过程进行更精细控制的场景下也很有用,比如暂停、恢复各个分片的下载,或者设置不同分片的优先级等。

- A标签下载:

  • 适合于小文件的下载,如文档、图片等。对于一些简单的文件下载需求,不需要复杂的下载工具,使用<a>标签就可以方便地实现文件下载。而且,在网页开发中,对于大多数普通的文件下载链接,<a>标签是一种简单直接的实现方式。
Vue是一个非常流行的JavaScript框架,由于其方便和易于使用性,Vue应用程序越来越受欢迎。 不同的应用程序需要处理不同类型和大小的文件上传和下载操作。当涉及到较大的文件时,大文件分片上传和下载成为Vue应用程序中的最佳选择,它可以大大提高效率和优化网络。 大文件分片上传是指上传大型文件时将该文件拆分为多个小块,并将这些小块单独上传,从而将上传速度加快,并且支持断点续传。 常见的实现方式通常是在Vue前端应用程序和后端服务之间建立RESTful API,通过该API将数据传输到服务器,使用Axios(一种Promise based HTTP client for the browser and node.js)进行数据传输。 大文件分片下载是指将大文件拆分为多个小块,按顺序下载下载成功后,将这些分块合并成完整的文件。这种方法有助于防止在下载过程中出现任何错误或网络问题时丢失整个文件,从而减少了浪费的时间和资源。 在Vue中,可以使用Axios或jQuery等类库来完成大文件分片上传和下载。 通过配置Axios或jQuery插件的文件上传和下载设置,并按需处理请求的成功和失败,就可以让Vue应用程序支持大文件分片上传和下载。 总之,大文件分片上传和下载在Vue应用程序中扮演着重要角色,可以使文件传输更快,更高效,减轻服务器的压力,为用户带来更好的体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DO_IT_JACK

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值