java实现大文件下载(http方式)

Wesley13
• 阅读 432

java实现大文件下载,基于http方式,控件神马的就不说了。

思路:下载文件无非要读取文件然后写文件,主要这两个步骤,主要难点:

    1.读文件,就是硬盘到内存的过程,由于jdk内存限制,不能读的太大。

    2.写文件,就是响应到浏览器端的过程,http协议是短链接,如果写文件太慢,时间过久,会造成浏览器死掉。

知识点:

    1.org.apache.http.impl.client.CloseableHttpClient  模拟httpClient客户端发送http请求,可以控制到请求文件的字节位置。

    2.BufferedInputStream都熟悉,用它接受请求来的流信息缓存。

    3.RandomAccessFile文件随机类,可以向文件写入指定位置的流信息。

基于以上信息,我的实现思路就是首先判断下载文件大小,配合多线程分割定制http请求数量和请求内容,响应到写入到RandomAccessFile指定位置中。在俗点就是大的http分割成一个个小的http请求,相当于每次请求一个网页。

废话不说,上代码。


DownLoadManagerTest类:

package xxxx;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.CountDownLatch;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * 
 * 文件下载管理类
 */

@RunWith(SpringJUnit4ClassRunner.class)
@ActiveProfiles("test")
@ContextConfiguration(locations={"classpath:test/applicationContext.xml"})
public class DownLoadManagerTest extends AbstractTransactionalJUnit4SpringContextTests{
    
    private static final Logger LOGGER = LoggerFactory.getLogger(DownLoadManagerTest.class);

    /**
     * 
     * 每个线程下载的字节数
     */

    private long unitSize = 1000 * 1024;
    
    @Autowired
    private TaskExecutor taskExecutor;
    
    private CloseableHttpClient httpClient;
    
    private Long starttimes;
    
    private Long endtimes;
    
    @Before
    public void setUp() throws Exception
    {
        starttimes = System.currentTimeMillis();
        System.out.println("测试开始....");
    }
    
    @After
    public void tearDown() throws Exception
    {
        endtimes = System.currentTimeMillis();
        System.out.println("测试结束!!");
        System.out.println("********************");
        System.out.println("下载总耗时:"+(endtimes-starttimes)/1000+"s");
        System.out.println("********************");
    }

    public DownLoadManagerTest() {
        
        System.out.println("初始化测试类....");
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        cm.setMaxTotal(100);
        httpClient = HttpClients.custom().setConnectionManager(cm).build();
        
    }

    /**
     * 
     * 启动多个线程下载文件
     */
    @Test
    public void  doDownload() throws IOException {

        String remoteFileUrl="http://{host}:{port}/{project}/xx.xml";
        String localPath="E://test//";
        
        String fileName = new URL(remoteFileUrl).getFile();

        System.out.println("远程文件名称:"+fileName);
        fileName = fileName.substring(fileName.lastIndexOf("/") + 1,
                fileName.length()).replace("%20", " ");
        System.out.println("本地文件名称:"+fileName);
        long fileSize = this.getRemoteFileSize(remoteFileUrl);

        this.createFile(localPath+System.currentTimeMillis()+fileName, fileSize);

        Long threadCount = (fileSize/unitSize)+(fileSize % unitSize!=0?1:0);
        long offset = 0;
        
        CountDownLatch end = new CountDownLatch(threadCount.intValue());
        
        if (fileSize <= unitSize) {// 如果远程文件尺寸小于等于unitSize

            DownloadThreadTest downloadThread = new DownloadThreadTest(remoteFileUrl,

                    localPath+fileName, offset, fileSize,end,httpClient);

            taskExecutor.execute(downloadThread);

        } else {// 如果远程文件尺寸大于unitSize

            for (int i = 1; i < threadCount; i++) {

                DownloadThreadTest downloadThread = new DownloadThreadTest(

                remoteFileUrl, localPath+fileName, offset, unitSize,end,httpClient);

                taskExecutor.execute(downloadThread);

                offset = offset + unitSize;

            }

            if (fileSize % unitSize != 0) {// 如果不能整除,则需要再创建一个线程下载剩余字节

                DownloadThreadTest downloadThread = new DownloadThreadTest(remoteFileUrl, localPath+fileName, offset, fileSize - unitSize * (threadCount-1),end,httpClient);
                taskExecutor.execute(downloadThread);
            }

        }
        try {
            end.await();
        } catch (InterruptedException e) {
            LOGGER.error("DownLoadManager exception msg:{}",ExceptionUtils.getFullStackTrace(e));
            e.printStackTrace();
        }
//        System.out.println("111111");
        LOGGER.debug("下载完成!{} ",localPath+fileName);
        //return localPath+fileName;
    }

    /**
     * 
     * 获取远程文件尺寸
     */

    private long getRemoteFileSize(String remoteFileUrl) throws IOException {

        long fileSize = 0;

        HttpURLConnection httpConnection = (HttpURLConnection) new URL(

        remoteFileUrl).openConnection();

        httpConnection.setRequestMethod("HEAD");

        int responseCode = httpConnection.getResponseCode();

        if (responseCode >= 400) {

            LOGGER.debug("Web服务器响应错误!");

            return 0;

        }

        String sHeader;

        for (int i = 1;; i++) {

            sHeader = httpConnection.getHeaderFieldKey(i);

            if (sHeader != null && sHeader.equals("Content-Length")) {

                System.out.println("文件大小ContentLength:"
                        + httpConnection.getContentLength());

                fileSize = Long.parseLong(httpConnection
                        .getHeaderField(sHeader));

                break;

            }

        }

        return fileSize;

    }

    /**
     * 
     * 创建指定大小的文件
     */

    private void createFile(String fileName, long fileSize) throws IOException {

        File newFile = new File(fileName);

        RandomAccessFile raf = new RandomAccessFile(newFile, "rw");

        raf.setLength(fileSize);

        raf.close();

    }


    public TaskExecutor getTaskExecutor() {
        return taskExecutor;
    }

    public void setTaskExecutor(TaskExecutor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }
    
}

DownloadThreadTest类:

package xxxx;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.concurrent.CountDownLatch;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 
 * 负责文件下载的类
 */

public class DownloadThreadTest extends Thread {

    private static final Logger LOGGER = LoggerFactory
            .getLogger(DownloadThreadTest.class);

    /**
     * 
     * 待下载的文件
     */

    private String url = null;

    /**
     * 
     * 本地文件名
     */

    private String fileName = null;

    /**
     * 
     * 偏移量
     */

    private long offset = 0;

    /**
     * 
     * 分配给本线程的下载字节数
     */

    private long length = 0;

    private CountDownLatch end;

    private CloseableHttpClient httpClient;

    private HttpContext context;

    /**
     * 
     * @param url
     *            下载文件地址
     * 
     * @param fileName
     *            另存文件名
     * 
     * @param offset
     *            本线程下载偏移量
     * 
     * @param length
     *            本线程下载长度
     * 
     * 
     * 
     * @author Angus.wang
     * 
     * */

    public DownloadThreadTest(String url, String file, long offset, long length,
            CountDownLatch end, CloseableHttpClient httpClient) {

        this.url = url;

        this.fileName = file;

        this.offset = offset;

        this.length = length;

        this.end = end;

        this.httpClient = httpClient;

        this.context = new BasicHttpContext();

        LOGGER.debug("偏移量=" + offset + ";字节数=" + length);

    }

    public void run() {

        try {

            HttpGet httpGet = new HttpGet(this.url);
            httpGet.addHeader("Range", "bytes=" + this.offset + "-"
                    + (this.offset + this.length - 1));
            CloseableHttpResponse response = httpClient.execute(httpGet,
                    context);
            ;
            BufferedInputStream bis = new BufferedInputStream(response
                    .getEntity().getContent());

            byte[] buff = new byte[1024];

            int bytesRead;

            File newFile = new File(fileName);

            RandomAccessFile raf = new RandomAccessFile(newFile, "rw");

            while ((bytesRead = bis.read(buff, 0, buff.length)) != -1) {
                raf.seek(this.offset);
                raf.write(buff, 0, bytesRead);
                this.offset = this.offset + bytesRead;
            }
            raf.close();
            bis.close();
        } catch (ClientProtocolException e) {
            LOGGER.error("DownloadThread exception msg:{}",ExceptionUtils.getFullStackTrace(e));
        } catch (IOException e) {
            LOGGER.error("DownloadThread exception msg:{}",ExceptionUtils.getFullStackTrace(e));
        } finally {
            end.countDown();
            LOGGER.info(end.getCount() + " is go on!");
            System.out.println(end.getCount() + " is go on!");
        }
    }

}

application.xml

<bean id="taskExecutor"
        class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <!-- 线程池活跃的线程数 -->
        <property name="corePoolSize" value="5" />
        <!-- 线程池最大活跃的线程数 -->
        <property name="maxPoolSize" value="10" />
        <!-- 队列的最大容量 -->
        <property name="queueCapacity" value="600" />
    </bean>
    <bean id="downLoadManager"
        class="xx.DownLoadManagerTest">
        <property name="taskExecutor" ref="taskExecutor" />
    </bean>

测试运行,500M,我这网速得半个小时左右。要想下载更大的文件,只要jdk内存够大,就无限更改队列最大容量吧。

如果不同意见,欢迎各位大神指正。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
2年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Docker 部署SpringBoot项目不香吗?
  公众号改版后文章乱序推荐,希望你可以点击上方“Java进阶架构师”,点击右上角,将我们设为★“星标”!这样才不会错过每日进阶架构文章呀。  !(http://dingyue.ws.126.net/2020/0920/b00fbfc7j00qgy5xy002kd200qo00hsg00it00cj.jpg)  2
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这