多线程实现多线程下载文件
时间:2023-01-16 13:30:00
下载文件时,一个大文件被切成许多片,用多线程下载,速度会更快
阅读代码时,注意查看代码中的注释
多线程下载文件,
第一:必须理解RandomAccessFile 类,这是一个可以设置的随机访问文件类 访问的 开始地址和结束地址,以及可写。
RandomAccessFileout = new RandomAccessFile(file, "rw"); 这意味着这种可读可写。 out.seek(start) 开始读取的位置可以定位。
第二:既然是网络文件下载,就必须了解 URL 类,该类是 java.net 提供一个包 可用于网络连接类。
URL url = new URL(urlLocation); 可以这样实例化,然后打开连接,HttpURLConnection conn = (HttpURLConnection) url.openConnection(),还可以设置其他参数,如设置超时,设置访问方法,设置 访问 起点等等。
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
conn.setRequestProperty("Range", "bytes=" start "-" end );
第三:了解线程,我们在这里使用它 java 1.5之后引入的 concurrent 包里面的 Executors.newCachedThreadPool() 线程池
第四:最基本的 io读写得知道
看看代码。
1.为了方便起见,我写了一个工具的工具类 Util类别用于提供获取 HttpURLConnection 连接
public class Util {
// 记录读多少,读多少
public static long start;
// 记录文件的总大小
public static long sum;
/**
*
* @Title: getHttpConnection
* @Description: 获取 url 连接
* @param: @param urlLocation
* @param: @return HttpURLConnection实例化对象
* @param: @throws IOException
* @return: HttpURLConnection
* @throws
*/
public static HttpURLConnection getHttpConnection(String urlLocation) throws IOException {
URL url = new URL(urlLocation);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
return conn;
}
}
2.文件切片分别指定 注意这里的起点是包头也包尾,0-10/11-20/21-30 这种
public class DownloadFilePool {
// 网络资源路径
private String urlLocation;
// 存储路径
private String filePath;
// 多少个线程
private int poolLength;
public DownloadFilePool(String urlLocation, String filePath, int poolLength) {
super();
// 如果 保存路径为空,默认存在 D盘,文件名和下载名一样
if( filePath==null ) {
String fileName = urlLocation.substring( urlLocation.lastIndexOf("/") 1);
filePath = "D:/" fileName;
}
this.urlLocation = urlLocation;
this.filePath = filePath;
this.poolLength = poolLength;
}
public void getFile() {
try {
// 获取文件长度
long fileLength = Util.getHttpConnection(urlLocation).getContentLengthLong();
Util.sum = fileLength;
ExecutorService pool = Executors.newCachedThreadPool();
// 获取每片大小
long slice = fileLength/poolLength;
for(int i = 0 ;i < poolLength; i ) {
long start = i*slice;
long end = (i 1)*slice -1;
if(i==poolLength-1) {
start = i*slice;
end = fileLength ;
}
System.out.println( start "---" end );
// 创建下载类
DownloadFileRang downloadFileRang = new DownloadFileRang(start, end, urlLocation, filePath);
// 执行线程
pool.execute(downloadFileRang);
}
// 关闭线程池
pool.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.文件下载类,在这里使用 继承Runnable 实现多线程
public class DownloadFileRang implements Runnable {
// 文件开始位置
private long start ;
// 文件结束位置
private long end;
// url地址
private String urlLocation;
// 文件存储位置
private String filePath;
public DownloadFileRang(long start, long end, String urlLocation, String filePath) {
super();
this.start = start;
this.end = end;
this.urlLocation = urlLocation;
this.filePath = filePath;
}
@Override
public void run() {
try {
// 获取连接
HttpURLConnection conn = Util.getHttpConnection(urlLocation);
// 设置获取资源的范围
conn.setRequestProperty("Range", "bytes=" start "-" end );
File file = new File(filePath);
RandomAccessFile out = null;
if(file!=null) {
out = new RandomAccessFile(file, "rw");
}
out.seek(start);
// 获取网络连接 输入流
InputStream is = conn.getInputStream();
byte [] data = new byte[1024];
int len = 0;
while( (len= is.read(data))!=-1 ) {
out.write(data, 0, len);
synchronized (Util.class) {
Util.start += len;
}
}
// 关闭连接
out.close();
is.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.实现显示实时网速
实时网速,其实就是单位时间内,所读取到的 资源,我们这里就是读取到的 len,观察我的代码,可以发现,我在 Util类里面写了两个静态属性,分别是 start 用来记录一共读了多少,一个是 sum,记录总文件大小。
线程类里面的 out.write 方法里面我 用 start 进行累加 len,这就是记录一共读取了多少资源,因为 多线程不能保证数据的原子性,所以我这里累加的时候,为避免因为线程原因出现数据错误,则进行加锁,加上 Util 对象的锁,告诉别的线程,这个 strat 同意时刻内只能一个线程来进行 操作。然后 用 每个时间段,比如说我这里是 500ms ,内读取的资源 / 500ms 就是网速了。
5.调用实例
public static void main(String[] args) {
Date startDate = new Date();
DownloadFilePool pool = new DownloadFilePool("http://fs.w.kugou.com/201809152325/5cbbb70b45431a17cad6ddd6d5342ef5/G108/M03/0C/01/rA0DAFk_VuiALN9DADmXB0zHYTA058.mp3", null, 100);
pool.getFile();
long old = 0;
long now = 0;
while( Util.sum >= Util.start ) {
now = Util.start - old;
old = Util.start;
if(Util.sum == Util.start) {
long t = new Date().getTime() - startDate.getTime();
double speed = ((double)Util.sum / (t/1000.0))/1024.0/1024.0;
System.out.println( "下载完成,用时:" + t/1000.0 +" s 平均网速:" + speed +" M/s" );
break;
}
System.out.println( "网速:" + ((double)(now/0.5))/1024.0/1024.0 +" M/s,已完成:" + (Util.start / (double)Util.sum)*100 +"%" );
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行效果:
---------------------
作者:临窗,听雨声
来源:CSDN
原文:https://blog.csdn.net/yali_aini/article/details/82713368
版权声明:本文为博主原创文章,转载请附上博文链接!