package teachercode;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
public class MultyThreadDownload {
static class DownloadTask implements Runnable {
private long start;
private long end;
private String path;
private File destFile;
public DownloadTask(long start, long end,String path, File destFile) {
this.start = start;
this.end = end;
this.path = path;
this.destFile = destFile;
}
@Override
public void run() {
RandomAccessFile dest = null;
InputStream is = null;
HttpURLConnection conn = null;
try {
// 4.线程读写
URL url = new URL(path);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
is = conn.getInputStream();
is.skip(start);// 设置源的开始位置
dest = new RandomAccessFile(destFile, "rw");
dest.seek(start);// 设置写开始的位置
byte b[] = new byte[1000];//1.设置数组的大小 2.
long total = start;//记录上一次下载的位置
int len = 0;
// start + len
while ((len = is.read(b)) > 0) {
total = total + len;
if (total > end) {
len = (int) (len - (total - end));
dest.write(b, 0, len);
break;
} else {
dest.write(b, 0, len);
}
}
System.out.println(Thread.currentThread().getName()+"完成了下载,下载的数据范围是"+":["+start+","+end+")");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(conn != null){
conn.disconnect();
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (dest != null) {
try {
dest.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
RandomAccessFile dest = null;
try {
// 1.获得服务器文件的大小,在本地新建一个相同大小的文件
// 服务器文件
String path = "http://n.sinaimg.cn/transform/20150806/4mXJ-fxftkps3371034.jpg";
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
// InputStream is = conn.getInputStream();//关联服务器上的文件输入流
// 在本地新建一个文件和服务器大小是一样
String filestr = path.substring(path.lastIndexOf("/") + 1);
File dir = new File("f:\\temp\\download");
if(!dir.exists()){
dir.mkdir();//创建目录
}
File destFile = new File(dir,filestr);//要写入的文件
dest = new RandomAccessFile(destFile,"rw");
long length = conn.getContentLength();//服务器文件的长度
dest.setLength(length);//设置本地文件的长度
// 2.决定要开几个线程下载
int num = 3;
long block =length / num;// length = 10 block=3
long start, end;
// 3.计算每个线程开始和结束的位置,开线程下载
for (int i = 0; i < num; i++) {
start = block * i;
if (i == num - 1) {
end = length;
} else {
end = start + block;
}
// 开线程并启动
new Thread(new DownloadTask(start, end, path, destFile)).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (dest != null) {
try {
dest.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
具体注解:
1. 首先获取服务器文件的大小,在本地新建一个相同大小的文件。
String path = "http://n.sinaimg.cn/transform/20150806/4mXJ-fxftkps3371034.jpg";
URL url = new URL(path);
获得所要下载的文件路径path,将path传到URL实例url中,
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
onn.setRequestMethod("GET");
然后获取url.openConnection 赋给HttpURLConnection实例conn中
通过conn.setRequestMethod(“GET”)请求获取静态页面
String filestr = path.substring(path.lastIndexOf("/") + 1);
创建一个filestr来获得path地址中最后一个”/”之后的数据;
File dir = new File("f:\\temp\\download");
if(!dir.exists()){
dir.mkdir();//创建目录
}
File destFile = new File(dir,filestr);//要写入的文件
dest = new RandomAccessFile(destFile,"rw");
通过File 创建目录,建立文件destFile,使用RandomAccessFile对创建的文件(destFile)进行读写操作,赋给dest。
long length = conn.getContentLength();
dest.setLength(length);
通过conn的getContentLength()方法获取服务器文件长度。再通过dest中的setLength(getContentLength())方法对本地文件设置长度。
2. 多线程下载分块操作
int num = 3;
long block =length/num;
定义线程数目num,使用文件长度length对num整除获取分区块数。
long start, end;
for (int i = 0; i < num; i++) {
start = block * i;
if (i == num - 1) {
end = length;
} else {
end = start + block;
}
new Thread(new DownloadTask(start, end, path, destFile)).start();
}
计算每个线程的开始(start)和结束(end)位置,用for循环依次对线程分配下载的开始和结束位置,开始为:块数大小(block)*序号(i)。
第一次开始位置:0 第一次结束位置:end=0+block;
第二次开始位置:block 第二次结束位置:end=block+block;
第三次开始位置:2*block 第三次结束位置:end=length;
由于是采用”/”整除方式,最后一块区域可能放不下文件数据。
所以当下载最后块数(第三块区域)时候,有可能所下载的长度大于 块数长度,这时候要将end为length。
3. 线程启动,线程开始对文件读写
线程启动代码:new Thread(new DownloadTask(start, end, path, destFile)).start();
通过for循环 对每个线程开启,并将start,end,path,File destFile,传入DownloadTask中(实现Runnable接口),重写Run方法:在run里完成对文件读写。
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
获得所要下载的文件路径path,将path传到URL实例url中,
然后获取url.openConnection 赋给HttpURLConnection实例conn中
通过conn.setRequestMethod(“GET”)请求获取静态页面
InputStream is = conn.getInputStream();
is.skip(start);
创建输入流is,关联coon的文件输入流。用输入流is方法中的skip(start),设置源的开始位置。
dest = new RandomAccessFile(destFile, "rw");
dest.seek(start);
通过RandomAccessFile对目的文件destFile读写操作:使用seek(start)定位到写 开始位置。然后开始写入数据。
byte b[] = new byte[1000];
long total = start;
int len = 0;
while ((len = is.read(b)) > 0) {
total = total + len;
if (total > end) {
len = (int) (len - (total - end));
dest.write(b, 0, len);
break;
} else {
dest.write(b, 0, len);
}
}
设置读取数组的大小b[]=byte[1000];
While()循环写入数据,开始位置为start,结束位置为end,每读取一次(1000)记录一下上次下载的位置(total ),方法:total = total+len,len为读取长度,初始化为0,write(b,0,len);当total>end(即:说明最后读取长度超过 本块区域(因为文件平分三块)),最后一点区域写入方法:len = len-(total - end)然后write(b,0,len).写入数据。
System.out.println(Thread.currentThread().getName()+"完成了下载,下载的数据范围是"+":["+start+","+end+")");
这段代码测试作用:最后为了方便查看哪个线程下载了哪段区域,使用Thread.currentThread().getName()获得当前线程名字[start,end) 获取区域。