因爲公司不允許用fg之類的軟件,所以就搞了這個東西來下載東西。程序比較簡單,尚有多處地方沒有優化。其實這種多線程下載的難點主要是下載任務的分配 下,打個比方,一個文件的某個部分應該給哪個線程下載?爲了簡單(另一方面是我不願多想),所以分配算法也比較簡單,直接分成一塊塊,然後每個線程下載一塊。如果讀者有留意Flashget之類的軟件下載時的過程圖的話,應該會發現它們的算法比這裏的好很多。
這裏我用HttpURLConnection下載,你也可以用HttpClient或者自己實現一個Http協議(不過貌似沒有必要)
其次,你可能發現我這裏效仿迅雷,一個任務生成兩個文件,一個是任務描述文件,一個是真正的下載文件,而Flahget則是隻有一個文件。在任務描述文件裏,我把前4K用來做一些描述,然後之後的用於記錄下載的過程。
另外,這裏寫文件沒有實現緩存寫之類的功能,不過那些功能做起來不難。
最後,希望你轉載文章的時候,麻煩保留作者信息。(夏威夷雪人 or 書蟲)
- //這個是任務Bean
- public class Task {
- private String downURL;
- private String saveFile;
- private int bufferSize = 64 * 1024;
- private int workerCount;
- private int sectionCount;
- private long contentLength;
- private long[] sectionsOffset;
- public static final int HEAD_SIZE = 4096;
- //讀下載描述文件內容
- public synchronized void read(RandomAccessFile file) throws IOException {
- byte[] temp = new byte[HEAD_SIZE];
- file.seek(0);
- int readed = file.read(temp);
- if (readed != temp.length) {
- throw new RuntimeException();
- }
- ByteArrayInputStream bais = new ByteArrayInputStream(temp);
- DataInputStream dis = new DataInputStream(bais);
- downURL = dis.readUTF();
- saveFile = dis.readUTF();
- sectionCount = dis.readInt();
- contentLength = dis.readLong();
- sectionsOffset = new long[sectionCount];
- for (int i = 0; i < sectionCount; i++) {
- sectionsOffset[i] = file.readLong();
- }
- }
- //創建下載描述文件內容
- public synchronized void create(RandomAccessFile file) throws IOException {
- if (sectionCount != sectionsOffset.length) {
- throw new RuntimeException();
- }
- long len = HEAD_SIZE + 8 * sectionCount;
- file.setLength(len);
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(baos);
- dos.writeUTF(downURL);
- dos.writeUTF(saveFile);
- dos.writeInt(sectionCount);
- dos.writeLong(contentLength);
- byte[] src = baos.toByteArray();
- byte[] temp = new byte[HEAD_SIZE];
- System.arraycopy(src, 0, temp, 0, src.length);
- file.seek(0);
- file.write(temp);
- writeOffset(file);
- }
- //更新下載的過程
- public synchronized void writeOffset(RandomAccessFile file) throws IOException {
- if (sectionCount != sectionsOffset.length) {
- throw new RuntimeException();
- }
- file.seek(HEAD_SIZE);
- for (int i = 0; i < sectionsOffset.length; i++) {
- file.writeLong(sectionsOffset[i]);
- }
- }
- (下面是Getter、Setter)
- }
- //這個是下載主程序
- public class TaskAssign {
- public void work(Task task) throws IOException {
- File file = new File(task.getSaveFile());
- if (file.exists()) {
- return;
- }
- //這個是記錄是否下載成功。我這裏也沒有增加失敗回覆、重試之類的工作。
- final AtomicBoolean success = new AtomicBoolean(true);
- //任務描述文件
- File taskFile = new File(task.getSaveFile() + ".r_task");
- //真正下載的文件
- File saveFile = new File(task.getSaveFile() + ".r_save");
- boolean taskFileExist = taskFile.exists();
- RandomAccessFile taskRandomFile = null;
- RandomAccessFile downRandomFile = null;
- try {
- taskRandomFile = new RandomAccessFile(taskFile, "rw");
- downRandomFile = new RandomAccessFile(saveFile, "rw");
- long rtnLen = getContentLength(task.getDownURL());
- if (!taskFileExist) {
- //如果文件不存在,就初始化任務文件和下載文件
- task.setContentLength(rtnLen);
- initTaskFile(taskRandomFile, task);
- downRandomFile.setLength(rtnLen);
- } else {
- //任務文件存在就讀取任務文件
- task.read(taskRandomFile);
- if (task.getContentLength() != rtnLen) {
- throw new RuntimeException();
- }
- }
- int secCount = task.getSectionCount();
- //分配線程去下載,這裏用到線程池
- ExecutorService es = Executors.newFixedThreadPool(task.getWorkerCount());
- for (int i = 0; i < secCount; i++) {
- final int j = i;
- final Task t = task;
- final RandomAccessFile f1 = taskRandomFile;
- final RandomAccessFile f2 = downRandomFile;
- es.execute(new Runnable() {
- public void run() {
- try {
- down(f1, f2, t, j);
- } catch (IOException e) {
- success.set(false);
- e.printStackTrace(System.out);
- }
- }
- });
- }
- es.shutdown();
- try {
- es.awaitTermination(24 * 3600, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- taskRandomFile.close();
- taskRandomFile = null;
- downRandomFile.close();
- downRandomFile = null;
- //如果下載成功,去掉任務描述文件、幫下載文件改名
- if (success.get()) {
- taskFile.delete();
- saveFile.renameTo(file);
- }
- } finally {
- if (taskRandomFile != null) {
- taskRandomFile.close();
- taskRandomFile = null;
- }
- if (downRandomFile != null) {
- downRandomFile.close();
- downRandomFile = null;
- }
- }
- }
- public void down(RandomAccessFile taskRandomFile, RandomAccessFile downRandomFile, Task task, int sectionNo) throws IOException {
- //這裏我用HttpURLConnection下載,你也可以用HttpClient或者自己實現一個Http協議(不過貌似沒有必要)
- URL u = new URL(task.getDownURL());
- HttpURLConnection conn = (HttpURLConnection) u.openConnection();
- long start = task.getSectionsOffset()[sectionNo];
- long end = -1;
- //這裏要注意一下,這裏是計算當前塊的長度
- if (sectionNo < task.getSectionCount() - 1) {
- long per = task.getContentLength() / task.getSectionCount();
- end = per * (sectionNo + 1);
- } else {
- end = task.getContentLength();
- }
- if (start >= end) {
- System.out.println("Section has finished before. " + sectionNo);
- return;
- }
- String range = "bytes=" + start + "-" + (end - 1);
- conn.setRequestProperty("Range", range);
- conn.setRequestProperty("User-Agent", "Ray-Downer");
- try {
- conn.connect();
- if (conn.getResponseCode() != 206) {
- throw new RuntimeException();
- }
- if (conn.getContentLength() != (end - start)) {
- throw new RuntimeException();
- }
- InputStream is = conn.getInputStream();
- byte[] temp = new byte[task.getBufferSize()];
- BufferedInputStream bis = new BufferedInputStream(is, temp.length);
- int readed = 0;
- while ((readed = bis.read(temp)) > 0) {
- long offset = task.getSectionsOffset()[sectionNo];
- synchronized (task) {
- //下載之後順便更新描述文件,你可能會發現這裏效率比較低,在一個線程同步裏進行兩次文件操作。你可以自己實現一個緩衝寫。
- downRandomFile.seek(offset);
- downRandomFile.write(temp, 0, readed);
- offset += readed;
- task.getSectionsOffset()[sectionNo] = offset;
- task.writeOffset(taskRandomFile);
- }
- }
- } finally {
- conn.disconnect();
- }
- System.out.println("Section finished. " + sectionNo);
- }
- public void initTaskFile(RandomAccessFile taskRandomFile, Task task) throws IOException {
- int secCount = task.getSectionCount();
- long per = task.getContentLength() / secCount;
- long[] sectionsOffset = new long[secCount];
- for (int i = 0; i < secCount; i++) {
- sectionsOffset[i] = per * i;
- }
- task.setSectionsOffset(sectionsOffset);
- task.create(taskRandomFile);
- }
- public long getContentLength(String url) throws IOException {
- URL u = new URL(url);
- HttpURLConnection conn = (HttpURLConnection) u.openConnection();
- try {
- return conn.getContentLength();
- } finally {
- conn.disconnect();
- }
- }
- }
- //稍微測試一下。
- public class Main {
- public static void main(String[] args) throws IOException {
- test3();
- System.out.println("/n/n===============/nFinished.");
- }
- public static void test1() throws IOException {
- Task task = new Task();
- task.setDownURL("http://61.152.235.21/qqfile/qq/2007iistable/QQ2007IIKB1.exe");
- task.setSaveFile("H:/Test2.exe");
- task.setSectionCount(200);
- task.setWorkerCount(100);
- task.setBufferSize(256 * 1024);
- TaskAssign ta = new TaskAssign();
- ta.work(task);
- }
- public static void test2() throws IOException {
- Task task = new Task();
- task.setDownURL("http://student1.scut.edu.cn:8880/manage/news/data/1208421861893.xls");
- task.setSaveFile("H:/Test3.xls");
- task.setSectionCount(5);
- task.setWorkerCount(1);
- task.setBufferSize(128 * 1024);
- TaskAssign ta = new TaskAssign();
- ta.work(task);
- }
- public static void test3() throws IOException {
- Task task = new Task();
- task.setDownURL("http://go.microsoft.com/fwlink/?linkid=57034");
- task.setSaveFile("H:/vc2005express.iso");
- task.setSectionCount(500);
- task.setWorkerCount(200);
- task.setBufferSize(128 * 1024);
- TaskAssign ta = new TaskAssign();
- ta.work(task);
- }
- public static void test4() throws IOException {
- Task task = new Task();
- task.setDownURL("http://down.sandai.net/Thunder5.7.9.472.exe");
- task.setSaveFile("H:/Thunder.exe");
- task.setSectionCount(30);
- task.setWorkerCount(30);
- task.setBufferSize(128 * 1024);
- TaskAssign ta = new TaskAssign();
- ta.work(task);
- }
- }