【Java】网络编程——多线程下载文件

前言

多线程下载文件,比单线程要快,当然,线程不是越多越好,这和获取的源文件还有和网速有关。

原理:在请求服务器的某个文件时,我们能得到这个文件的大小长度信息,我们就可以下载此长度的某一个片段,来达到多线程下载的目的!每条线程分别下载他们自己的片段!

下载流程(代码片段)

1.  根据访问的URL路径调用openConnection()获得HttpURLConnection对象,接着调用getContentLengthLong()方法获得文件的  字节大小,然后通过RandomAccessFile对象调用setLength()设置本地文件的长度。(这个文件是null数据文件,通过多线程进行对RandomAccessFile对象的本地文件随机位置写入数据)

URL url = new URL(str_url);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
long fileLength = conn.getContentLengthLong(); // 得到需要下载的文件大小
RandomAccessFile file = new RandomAccessFile(storagePath, "rwd");
file.setLength(fileLength); // 关键方法 : 设置本地文件长度
file.close();
conn.disconnect();

2. 根据获得的文件长度,计算每条线程下载的起始位置与结束位置,因为不一定平均分,所以最后一条线程下载剩余的字节

long oneThreadReadByteLength = fileLength / threadNumber;
for (int i = 0; i < threadNumber; i++) {
	long startPosition = i * oneThreadReadByteLength;
	long endPosition = i == threadNumber - 1 ? fileLength : (i + 1) * oneThreadReadByteLength - 1;
}

3. 每条线程请求的范围参数设置,请求头参数 : Range:bytes=0-length

URL url = new URL(str_url);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Range", "bytes=" + startPosition + "-" + endPosition); // 关键方法: 每条线程请求的字节范围

4. 每条线程存储数据到文件的起始位置设置(RandomAccessFile的seek()方法),以及响应码206判断

if (conn.getResponseCode() == HttpURLConnection.HTTP_PARTIAL) { // 关键响应码 :206,请求成功 + 请求数据字节范围成功
	RandomAccessFile file = new RandomAccessFile(storagePath, "rwd");
	file.seek(startPosition); // 关键方法 :每条线程起始写入文件的位置
	InputStream in = conn.getInputStream();
	byte[] buf = new byte[8192];
	int len;
	while ((len = in.read(buf)) > 0) {
		file.write(buf, 0, len);
	}
}

完整代码

main.java

public class Main {
	
	public static void main(String[] args) throws Exception {
		
		String path = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1588346633185&di=8f8b2b357c8461d232fcce9e0c476f3a&imgtype=0&src=http%3A%2F%2Fi0.hdslb.com%2Fbfs%2Farticle%2Fe79d2c22d40a801e7b02183dee9db3e5c71514af.jpg";
		MultiThreadDownload mtd = new MultiThreadDownload(path, "G:\\LeiMus.jpg", 3);
		mtd.download();
		
	}

}

MultiThreadDownload.java

package com.bin.demo;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

public class MultiThreadDownload {
	
	private String str_url;
	private String storagePath;
	private int threadNumber;
	private static long downloadByteCount;
	
	MultiThreadDownload(String str_url, String storagePath, int threadNumber) {
		this.str_url = str_url;
		this.storagePath = storagePath;
		this.threadNumber = threadNumber;
	}
	
	public void download() throws IOException, InterruptedException {
		long startTime = System.currentTimeMillis();
		System.out.println("Download......");
		
		/*
		 *  首先设置本地文件的大小
		 *  当然这是个null数据的文件
		 *  这样才能通过RandomAccessFile的数组下标机制达到随机位置写入
		 */
		URL url = new URL(str_url);
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setConnectTimeout(10000);
		conn.setRequestMethod("GET");
		long fileLength = conn.getContentLengthLong(); // 得到需要下载的文件大小
		conn.disconnect();
		RandomAccessFile file = new RandomAccessFile(storagePath, "rwd");
		file.setLength(fileLength); // 关键方法 : 设置本地文件长度
		file.close();
		
		/*
		 *  计算每条线程下载的字节数,以及每条线程起始下载位置与结束的下载位置,
		 *  因为不一定平均分,所以最后一条线程下载剩余的字节
		 *  然后创建线程任务并启动
		 *  Main线程等待每条线程结束(join()方法)
		 */
		long oneThreadReadByteLength = fileLength / threadNumber;
		for (int i = 0; i < threadNumber; i++) {
			long startPosition = i * oneThreadReadByteLength;
			long endPosition = i == threadNumber - 1 ? fileLength : (i + 1) * oneThreadReadByteLength - 1;
			Thread t = new Thread(new Task(startPosition, endPosition));
			t.start();
			t.join();
		}
		
		/*
		 *  检查文件是否下载完整,不完整则删除
		 */
		if (downloadByteCount == fileLength) {
			System.out.println("ALL Thread Download OK.");
			System.out.println("time = " + ((System.currentTimeMillis() - startTime) / 1000) + " S");
		} else {
			System.out.println("Download Error.");
			new File(storagePath).delete();
		}
	}
	
	class Task implements Runnable {
		
		private long startPosition;
		private long endPosition;
		
		Task(long startPosition, long endPosition) {
			this.startPosition = startPosition;
			this.endPosition = endPosition;
		}

		@Override
		public void run() {
			try {
				URL url = new URL(str_url);
				HttpURLConnection conn = (HttpURLConnection) url.openConnection();
				conn.setConnectTimeout(10000);
				conn.setRequestMethod("GET");
				conn.setRequestProperty("Range", "bytes=" + startPosition + "-" + endPosition); // 关键方法: 每条线程请求的字节范围
				if (conn.getResponseCode() == HttpURLConnection.HTTP_PARTIAL) { // 关键响应码 :206,请求成功 + 请求数据字节范围成功
					RandomAccessFile file = new RandomAccessFile(storagePath, "rwd");
					file.seek(startPosition); // 关键方法 :每条线程起始写入文件的位置
					InputStream in = conn.getInputStream();
					byte[] buf = new byte[8192];
					int len;
					while ((len = in.read(buf)) > 0) {
						file.write(buf, 0, len);
						downloadByteCount += len;
					}
					// 关闭网络连接及本地流
					in.close();
					file.close();
					conn.disconnect();
					System.out.println(Thread.currentThread().getName() + ": download OK");
				}
			} catch (IOException e) {
				System.out.println(Thread.currentThread().getName() + "_Error : " + e);
			}
		}
		
	}

}

输出:

Download......
Thread-0: download OK
Thread-1: download OK
Thread-2: download OK
ALL Thread Download OK.
time = 1 S

我下载的是百度图片,另外多线程下载的结果时间和网速和文件大小有关。

附加我的结果图片:

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

虚妄狼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值