java实现文件断点下载

一、线程池的目的:
1、如果采用多个线程执行任务,为了防止线程被频繁的创建和销毁,这个时候就需要用线程池来管理这些线程。 这样可减少线程的创建和销毁次数,减少系统资源开销。
2、if(线程创建时间 + 线程执行时间 <  线程销毁时间){
     则需要用线程池来管理线程;


二、线程池工作原理:
当前线程数量:poolSize
线程池维护的最少线程数量:corePoolSize
线程池的最大线程数量:maximumPoolSize
线程池中允许线程的空闲时间:keepAliveTime( 默认状态是false ,设置为true时,可将超出空闲时间的线程来执行其他任务。
核心线程池是否退出:allowCoreThreadTimeout

if( poolSize < corePoolSize ){
	    新建一个线程来执行任务,就算线程池当中有空闲线程    
	}

	if(poolSize >= corePoolSize ){
	    任务队列未满时,新来的任务将它缓存在其中
	    while( 空闲线程时间 > keepAliveTime ){
	        将空闲线程来执行该任务
	    }

	    任务队列已经满的时候
	    if(poolSize < maximumPoolSize){
	        创建新的线程来执行任务
	    }else{
	        抛出异常
	    }
	}
}


    
}

三、应用:
java四种线程池的使用:
通过Executors提供四种线程池,分别是:
1、newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
.
.
cachedThreadPool.execute(new Runnable)

2、newFixedThreadPool :创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);

.

.

fixedThreadPool.execute(new Runnable)


3、newScheduledThreadPool :创建一个定长线程池,支持定时及周期性任务执行。

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

.

.

scheduledThreadPool.schedule(new Runnable)


4、newSingleThreadExecutor: 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

.

 scheduledThreadPool.scheduleAtFixedRate(new Runnable)

四、实例:

应用线程池实现断点下载:

public class DownloadStartup {

	private static final String encoding = "utf-8";
	public static void main(String[] args) {

		DownloadTask downloadManager = new DownloadTask();
		String urlStr = "http://dldir1.qq.com/weixin/Windows/WeChatSetup.exe";
		
		downloadManager.setSleepSeconds(5);
		String downladFileName = downloadManager.download(urlStr, encoding);
		System.out.println("Download file is " + downladFileName + ".");		
	}
	
}

public class DownloadTask {
	// 分段下载的线程个数
	private int threadNum = 5;
	private URL url = null;
	private long threadLength = 0;
	// 目标文件路径与名字
	public String fileDir = "/Users/apple/CSDN/jsFile/";
	public String fileName = "test.html";
	public boolean statusError = false;
	private String charset;

	public long sleepSeconds = 5;

	public String download(String urlStr, String charset) {
		statusError = false;
		this.charset = charset;
		long contentLength = 0;
		CountDownLatch latch = new CountDownLatch(threadNum);
		ChildThread[] childThreads = new ChildThread[threadNum];
		long[] startPos = new long[threadNum];
		long endPos = 0;

		try {
			// 从url中获得下载的文件格式与名字   自动命名方法
			this.fileName = urlStr.substring(urlStr.lastIndexOf("/") + 1, urlStr.lastIndexOf("?")>0 ? urlStr.lastIndexOf("?") : urlStr.length());
			if("".equalsIgnoreCase(this.fileName)){
				this.fileName = UUID.randomUUID().toString();
			}

			this.url = new URL(urlStr);
			HttpURLConnection con = (HttpURLConnection) url.openConnection();
			setHeader(con);
			// 得到content的长度
			contentLength = con.getContentLength();
			// 把context分为threadNum段的话,每段的长度。
			this.threadLength = contentLength / threadNum;

			// 第一步,分析已下载的临时文件,设置断点,如果是新的下载任务,则建立目标文件。
			startPos = setThreadBreakpoint(fileDir, fileName, contentLength,startPos);
			//---------------------------------------------------线程池管理线程----------------
			// 第二步,分多个线程下载文件
			ExecutorService exec = Executors.newCachedThreadPool();
			for (int i = 0; i < threadNum; i++) {
				// 创建子线程来负责下载数据,每段数据的起始位置为(threadLength * i + 已下载长度)
				startPos[i] += threadLength * i;

				/*
				 * 设置子线程的终止位置,非最后一个线程即为(threadLength * (i + 1) - 1)
				 * 最后一个线程的终止位置即为下载内容的长度
				 */
				if (i == threadNum - 1) {
					endPos = contentLength;
				} else {
					endPos = threadLength * (i + 1) - 1;
				}
				// 开启子线程,并执行。
				ChildThread thread = new ChildThread(this, latch, i,
						startPos[i], endPos);
				childThreads[i] = thread;
				exec.execute(thread);
			}

			try {
				// 等待CountdownLatch信号为0,表示所有子线程都结束。
				latch.await();
				exec.shutdown();

				// 第三步,把分段下载下来的临时文件中的内容写入目标文件中。
				tempFileToTargetFile(childThreads);

			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

		return fileDir + fileName;
	}

	//将目标文件写到临时文件中
	private void tempFileToTargetFile(ChildThread[] childThreads) {
		try {
			BufferedOutputStream outputStream = new BufferedOutputStream(
					new FileOutputStream(fileDir + fileName));

			// 遍历所有子线程创建的临时文件,按顺序把下载内容写入目标文件中
			for (int i = 0; i < threadNum; i++) {
				if (statusError) {
					for (int k = 0; k < threadNum; k++) {
						if (childThreads[k].tempFile.length() == 0)
							childThreads[k].tempFile.delete();
					}
					System.out.println("本次下载任务不成功,请重新设置线程数。");
					break;
				}

				BufferedInputStream inputStream = new BufferedInputStream(
						new FileInputStream(childThreads[i].tempFile));
				System.out.println("Now is file " + childThreads[i].id);
				int len = 0;
				long count = 0;
				byte[] b = new byte[1024];
				while ((len = inputStream.read(b)) != -1) {
					count += len;
					outputStream.write(b, 0, len);
					if ((count % 4096) == 0) {
						outputStream.flush();
					}
				}

				inputStream.close();
				// 删除临时文件
				if (childThreads[i].status == ChildThread.STATUS_HAS_FINISHED) {
					childThreads[i].tempFile.delete();
				}
			}

			outputStream.flush();
			outputStream.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	//判断文件是否之前存在
	private long[] setThreadBreakpoint(String fileDir2, String fileName2,
			long contentLength, long[] startPos) {
		File file = new File(fileDir + fileName);
		long localFileSize = file.length();

		if (file.exists()) {
			System.out.println("file " + fileName + " has exists!");
			// 下载的目标文件已存在,判断目标文件是否完整
			if (localFileSize < contentLength) {
				System.out.println("Now download continue ... ");

				// 遍历目标文件的所有临时文件,设置断点的位置,即每个临时文件的长度
				File tempFileDir = new File(fileDir);
				File[] files = tempFileDir.listFiles();
				for (int k = 0; k < files.length; k++) {
					String tempFileName = files[k].getName();
					// 临时文件的命名方式为:目标文件名+"_"+编号
					if (tempFileName != null && files[k].length() > 0
							&& tempFileName.startsWith(fileName + "_")) {
						int fileLongNum = Integer.parseInt(tempFileName
								.substring(tempFileName.lastIndexOf("_") + 1,
										tempFileName.lastIndexOf("_") + 2));
						// 为每个线程设置已下载的位置
						startPos[fileLongNum] = files[k].length();
					}
				}
			}
		} else {
			// 如果下载的目标文件不存在,则创建新文件
			try {
				file.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		return startPos;
	}

	/**
	 * 
	 * @author annegu
	 * @since 2009-07-16
	 * 
	 */
	public class ChildThread extends Thread {
		public static final int STATUS_HASNOT_FINISHED = 0;
		public static final int STATUS_HAS_FINISHED = 1;
		public static final int STATUS_HTTPSTATUS_ERROR = 2;
		private DownloadTask task;
		private int id;
		private long startPosition;
		private long endPosition;
		private final CountDownLatch latch;
		// private RandomAccessFile tempFile = null;
		private File tempFile = null;
		//线程状态码
		private int status = ChildThread.STATUS_HASNOT_FINISHED;

		public ChildThread(DownloadTask task, CountDownLatch latch, int id,
				long startPos, long endPos) {
			super();
			this.task = task;
			this.id = id;
			this.startPosition = startPos;
			this.endPosition = endPos;
			this.latch = latch;

			try {
				tempFile = new File(this.task.fileDir + this.task.fileName
						+ "_" + id);
				if (!tempFile.exists()) {
					tempFile.createNewFile();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}

		}

		public void run() {
			System.out.println("Thread " + id + " run ...");
			HttpURLConnection con = null;
			InputStream inputStream = null;
			BufferedOutputStream outputStream = null;
			long count = 0;
			long threadDownloadLength = endPosition - startPosition;

			try {
				outputStream = new BufferedOutputStream(new FileOutputStream(
						tempFile.getPath(), true));
			} catch (FileNotFoundException e2) {
				e2.printStackTrace();
			}

			for (;;) {
				try {
					// 打开URLConnection
					con = (HttpURLConnection) task.url.openConnection();
					setHeader(con);
					con.setAllowUserInteraction(true);
					// 设置连接超时时间为10000ms
					con.setConnectTimeout(10000);
					// 设置读取数据超时时间为10000ms
					con.setReadTimeout(10000);

					if (startPosition < endPosition) {
						// 设置下载数据的起止区间
						con.setRequestProperty("Range", "bytes="
								+ startPosition + "-" + endPosition);
						System.out.println("Thread " + id
								+ " startPosition is " + startPosition);
						System.out.println("Thread " + id + " endPosition is "
								+ endPosition);

						//判断http status是否为HTTP/1.1 206 Partial Content或者200 OK
						//如果不是以上两种状态,把status改为STATUS_HTTPSTATUS_ERROR
						if (con.getResponseCode() != HttpURLConnection.HTTP_OK
								&& con.getResponseCode() != HttpURLConnection.HTTP_PARTIAL) {
							System.out.println("Thread " + id + ": code = "
									+ con.getResponseCode() + ", status = "
									+ con.getResponseMessage());
							status = ChildThread.STATUS_HTTPSTATUS_ERROR;
							this.task.statusError = true;
							outputStream.close();
							con.disconnect();
							System.out.println("Thread " + id + " finished.");
							latch.countDown();
							break;
						}

						inputStream = con.getInputStream();
						int len = 0;
						byte[] b = new byte[1024];
						while (!this.task.statusError
								&& (len = inputStream.read(b)) != -1) {
							outputStream.write(b, 0, len);
							
							count += len;
							startPosition += len;
							// 每读满4096个byte(一个内存页),往磁盘上flush一下
							if (count % 4096 == 0) {
								outputStream.flush();
							}
						}

						if (count >= threadDownloadLength) {
							status = ChildThread.STATUS_HAS_FINISHED;
						}
						outputStream.flush();
						outputStream.close();
						inputStream.close();
						con.disconnect();
					} else {
						status = ChildThread.STATUS_HAS_FINISHED;
					}

					
					System.out.println("Thread " + id + " finished.");
					latch.countDown();
					break;
				} catch (IOException e) {
					try {
						outputStream.flush();
						TimeUnit.SECONDS.sleep(getSleepSeconds());
					} catch (InterruptedException e1) {
						e1.printStackTrace();
					} catch (IOException e2) {
						e2.printStackTrace();
					}
					continue;
				}
			}
			if (outputStream != null) {
				try {
					outputStream.close();
					con.disconnect();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	private void setHeader(URLConnection con) {
		con.setRequestProperty("User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.3) Gecko/2008092510 Ubuntu/8.04 (hardy) Firefox/3.0.3");
		con.setRequestProperty("Accept-Language", "en-us,en;q=0.7,zh-cn;q=0.3");
		con.setRequestProperty("Accept-Encoding", "aa");
		con.setRequestProperty("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7");
		con.setRequestProperty("Keep-Alive", "300");
		con.setRequestProperty("Connection", "keep-alive");
		con.setRequestProperty("If-Modified-Since", "Fri, 02 Jan 2009 17:00:05 GMT");
		con.setRequestProperty("If-None-Match", "\"1261d8-4290-df64d224\"");
		con.setRequestProperty("Cache-Control", "max-age=0");
		con.setRequestProperty("Referer", "http://www.skycn.com/soft/14857.html");
	}

	public long getSleepSeconds() {
		return sleepSeconds;
	}

	public void setSleepSeconds(long sleepSeconds) {
		this.sleepSeconds = sleepSeconds;
	}

}


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值