一个多线程下载与断点续传的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性能会好点儿?