一个多线程下载与断点续传的demo...
import java.io.*;
import java.net.*;
import java.util.concurrent.*;
public class Downloader3 {
URL url;
File des;
File cfg;
long size;
long sum;
int taskNum = 10;
CountDownLatch latch;
String path = "C:/Documents and Settings/Administrator/桌面/";
class Task extends Thread {
int id;
long start;
long end;
boolean error;
int errTimes;
public Task(int id, long start, long end) {
this.id = id;
this.start = start;
this.end = end;
}
public void run() {
for (long now = start ; now <= end; ) {
if (error) {
if (++errTimes > 5) {
latch.countDown();
System.err.format("Task%2d ERROR\n", id);
return;
}
sum -= now - start;
now = start;
error = false;
}
System.out.format("Task%2d: [%d, %d]\n", id, now, end);
try {
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestProperty("RANGE", "bytes=" + now + "-" + end);
int code = con.getResponseCode();
if (code / 100 != 2) {
System.err.format("Task%2d ERROR: %d\n", id, code);
throw new IOException();
}
byte[] buf = new byte[4096];
InputStream is = con.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
RandomAccessFile fos = new RandomAccessFile(des, "rw");
RandomAccessFile raf = new RandomAccessFile(cfg, "rw");
fos.seek(now);
for (int len = -1; (len = bis.read(buf)) != -1; ) {
fos.write(buf, 0, len);
sum += len;
now += len;
raf.seek(8);
raf.writeLong(sum);
raf.seek(20 + id * 24 + 16);
raf.writeLong(now);
System.out.format("%.2f%%\n", sum * 100.0 / size);
}
fos.close();
raf.close();
bis.close();
con.disconnect();
} catch (IOException e) {
e.printStackTrace();
error = true;
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
continue;
}
} // end for
latch.countDown();
}
}
public void download(String address) {
long begin = System.currentTimeMillis();
String name = address.substring(address.lastIndexOf('/') + 1);
des = new File(path + name + ".tmp");
cfg = new File(path + name + ".cfg");
latch = new CountDownLatch(taskNum);
ExecutorService exec = Executors.newCachedThreadPool();
long[] starts = new long[taskNum];
long[] ends = new long[taskNum];
try {
url = new URL(address);
analyse(starts, ends);
for (int i = 0; i < taskNum; i++)
exec.execute(new Task(i, starts[i], ends[i]));
latch.await();
exec.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
if (sum >= size) {
des.renameTo(new File(path + name));
cfg.delete();
}
long time = System.currentTimeMillis() - begin;
System.out.format("Time: %.2fs, Speed: %.2fk/s\n",
0.001 * time, 0.9765625 * size / time);
}
private void analyse(long[] starts, long[] ends) throws IOException {
RandomAccessFile raf = new RandomAccessFile(cfg, "rw");
if (!des.exists()) {
HttpURLConnection con = (HttpURLConnection) url.openConnection();
size = con.getContentLength();
long part = size / taskNum;
raf.writeLong(size);
raf.writeLong(sum);
raf.writeInt(taskNum);
for (int i = 0; i < taskNum; i++) {
starts[i] = part * i;
ends[i] = (i == taskNum - 1) ? size - 1 : part * (i + 1) - 1;
raf.writeLong(starts[i]);
raf.writeLong(ends[i]);
raf.writeLong(starts[i]); // now
}
} else {
size = raf.readLong();
sum = raf.readLong();
taskNum = raf.readInt();
for (int i = 0; i < taskNum; i++) {
raf.seek(20 + i * 24 + 8);
ends[i] = raf.readLong();
starts[i] = raf.readLong();
}
}
raf.close();
}
public static void main(String[] args) {
Downloader3 d = new Downloader3();
String address = "http://cidian.youdao.com/download/YoudaoDict.exe";
d.download(address);
}
}
总结:
1. "RANGE"属于闭区间类型, 对右端点值不好理解可以通过干脆不写来变通, 如果区间不合理会导致下载文件空洞...
2. 每个下载子线程对主文件和配置文件的访问要定义局部的RandomAccessFile, 否则会有线程错误...
3. 关闭配置文件的全部RandomAccessFile流, 否则会导致下载完成时删除配置文件失败...
4. 程序处理异常部分极度ugly, 怎么改也是难看...
5. 已用下载时间, 平均下载速度统计信息用专门的线程来做统计比较好...
6. 没实现各种运行参数的灵活设置...
7. 非首次运行时线程调度部分实现的不合理, 难以实现高效下载...
8. 对网络, 线程, IO的API远不够熟悉, 难以实现灵活合理的运用. 也许使用NIO性能会好点儿?