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

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内存够大,就无限更改队列最大容量吧。

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

时间: 2024-07-31 16:12:07

java实现大文件下载(http方式)的相关文章

【原创】用JAVA实现大文件上传及显示进度信息

用JAVA实现大文件上传及显示进度信息 ---解析HTTP MultiPart协议 一. 大文件上传基础描述: 各种WEB框架中,对于浏览器上传文件的请求,都有自己的处理对象负责对Http MultiPart协议内容进行解析,并供开发人员调用请求的表单内容. 比如: Spring 框架中使用类似CommonsMultipartFile对象处理表二进制文件信息. 而.NET 中使用HtmlInputFile/ HttpPostedFile对象处理二进制文件信息. 优点:使用框架内置对象可以很方便的

java+Mysql大数据的一些优化技巧

众所周知,java在处理数据量比较大的时候,加载到内存必然会导致内存溢出,而在一些数据处理中我们不得不去处理海量数据,在做数据处理中,我们常见的手段是分解,压缩,并行,临时文件等方法; 例如,我们要将数据库(不论是什么数据库)的数据导出到一个文件,一般是Excel或文本格式的CSV;对于Excel来讲,对于POI和JXL的接口,你很多时候没有办法去控制内存什么时候向磁盘写入,很恶心,而且这些API在内存构造的对象大小将比数据原有的大小要大很多倍数,所以你不得不去拆分Excel,还好,POI开始意

OC - 16.大文件下载

大文件下载注意事项 若不对下载的文件进行转存,会造成内存消耗急剧升高,甚至耗尽内存资源,造成程序终止. 在文件下载过程中通常会出现中途停止的状况,若不做处理,就要重新开始下载,浪费流量. 大文件下载的解决方案 对下载文件进行处理,每下载一点数据,就将数据写到磁盘中(通常是沙盒中),避免在内存累积数据(NSURLConnection下载) 使用NSFileHandle类实现写数据 使用NSOutputStream类实现写数据 当下载任务终止时,记录任务终止时的位置信息,以便下次开始继续下载 大文件

Django 大文件下载

django提供文件下载时,若果文件较小,解决办法是先将要传送的内容全生成在内存中,然后再一次性传入Response对象中: def simple_file_download(request): # do something... content = open("simplefile", "rb").read() return HttpResponse(content) 如果文件非常大时,最简单的办法就是使用静态文件服务器,比如Apache或者Nginx服务器来处理

Java查询大文本

但JAVA本身缺少相应的类库,需要硬编码才能实现结构化文件计算,代码复杂且可读性差,难以实现高效的并行处理. 使用免费的集算器可以弥补这一不足.集算器封装了丰富的结构化文件读写和游标计算函数,书写简单代码就能实现并行计算,并提供了易用的JDBC接口.JAVA应用程序可以将集算器脚本文件当做数据库存储过程执行,传入参数并用JDBC获得返回结果 下面举例说明集算器协助JAVA查询大文本的过程. 源数据sOrder.txt如下: 要查询起止时间是startDate.endDate之间,金额大于argA

Java序列化的几种方式以及序列化的作用

Java序列化的几种方式以及序列化的作用 本文着重讲解一下Java序列化的相关内容. 如果对Java序列化感兴趣的同学可以研究一下. 一.Java序列化的作用    有的时候我们想要把一个Java对象变成字节流的形式传出去,有的时候我们想要从一个字节流中恢复一个Java对象.例如,有的时候我们想要 把一个Java对象写入到硬盘或者传输到网路上面的其它计算机,这时我们就需要自己去通过java把相应的对象写成转换成字节流.对于这种通用 的操作,我们为什么不使用统一的格式呢?没错,这里就出现了java

java—servlet实现文件下载功能

最近在学javaweb技术,利用所学知识做了一个简单的项目,在服务器上实现给用户下载文件的功能.自动列出文件夹下的文件提供下载,支持中文文件名. 结果图 其中使用了tomcat来部署服务器,代码中应用了少许EL表达式和JSTL标签,使用了jsp和servlet,当然还有java和http的基础知识,但是个人觉得重点是http的Content-Disposition头信息和Content-type头信息在servlet中下载方面的应用,详见DownloadPageServlet.java源码. 关

Java查询大文本文件的处理方法

有时我们需要查询大文本而不是数据库,这时就需要流式读入文件并实现查询算法,还要进行并行处理以提高性能.但JAVA本身缺少相应的类库,需要硬编码才能实现结构化文件计算,代码复杂且可读性差,难以实现高效的并行处理. 使用免费的集算器可以弥补这一不足.集算器封装了丰富的结构化文件读写和游标计算函数,书写简单代码就能实现并行计算,并提供了易用的JDBC接口.JAVA应用程序可以将集算器脚本文件当做数据库存储过程执行,传入参数并用JDBC获得返回结果. 集算器与Java应用程序的集成结构如下: 下面举例说

ios开发网络学习三:NSURLConnection小文件大文件下载

一:小文件下载 #import "ViewController.h" @interface ViewController ()<NSURLConnectionDataDelegate> /** 注释 */ @property (nonatomic, strong) NSMutableData *fileData; @property (nonatomic, assign) NSInteger totalSize; @property (weak, nonatomic) IB