`
liangguanhui
  • 浏览: 112943 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

【原创】一个简单的多线程、断点下载Java程序

阅读更多

因为公司不允许用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);

  }

}



 
分享到:
评论
3 楼 dava_gosling 2009-03-22  
dava_gosling 写道

为什么下载描述文件不用对象序列化?把下载信息装在一个Pojo类再序列化

我错了,对象序列化效率低。
2 楼 dava_gosling 2009-02-14  
为什么下载描述文件不用对象序列化?把下载信息装在一个Pojo类再序列化
1 楼 careprad 2008-05-30  
  不错

相关推荐

    Java多线程断点下载Sample

    在这个WindowsApplicationTest中,我们可以推测它可能是一个包含Java Swing应用程序的测试项目,用于演示上述的多线程断点下载功能。通过运行这个程序,开发者可以直观地看到多线程下载的效率提升以及断点续传的便利...

    JAVA多线程断点续传下载程序

    总的来说,这个Java多线程断点续传下载程序是一个综合性的实践项目,它涵盖了Java编程的多个重要方面,对于提升你的技能和理解并发编程有极大的帮助。通过这个项目,你不仅可以学习到多线程编程的原理,还能掌握如何...

    Java 多线程断点下载文件

    Java多线程断点下载文件是一种高效的文件下载方式,它允许在下载过程中暂停并从上次停止的地方继续,尤其适用于大文件下载。以下是实现这一功能的关键知识点: 1. **获取文件信息**: - 使用`java.net.URL`和`java...

    java多线程断点下载

    Java多线程断点下载是网络编程中一个实用的技术,尤其在处理...总的来说,Java多线程断点下载是一个涉及并发编程、文件操作、异常处理等多个领域的综合实践。通过合理设计和优化,我们可以构建出高效且可靠的下载系统。

    多线程断点续传程序 java版本

    标题中的“多线程断点续传程序 Java版本”指的是一个使用Java编程语言实现的软件,该软件具备多线程下载功能,并且支持在下载中断后从上次停止的位置继续下载,即断点续传。这样的特性对于大文件下载或者网络环境不...

    再分享一个Java 多线程断点式下载模块.rar

    多线程断点式下载示例代码,再分享一个Java 多线程断点式下载模块,主要原理是: 把context分为poolSize段,计算每段的长度。如果文件已存在,根据临时文件中记载的线程数量,继续上次的任务,如果下载的目标文件不...

    java多线程断点续传下载

    Java多线程断点续传下载是一个复杂但实用的技术,尤其在处理大文件或网络不稳定时,能够提高用户体验并优化资源利用。以下是对这个主题的详细解析: **1. Java多线程** Java多线程是指在一个Java应用程序中同时执行...

    多线程断点下载

    通过以上讲解,我们可以看到多线程断点下载技术在Java和Android平台上的实现涉及了网络编程、多线程同步、文件操作等多个方面的知识,它在提升用户体验的同时,也对开发者的技术要求较高。在实际应用中,开发者需要...

    java 多线程断点续传

    在Java编程中,多线程是一项关键特性,它允许程序同时执行多个任务,极大地提高了效率。断点续传是文件传输中的一个实用功能,尤其在大文件上传或下载时,可以避免因网络中断或其他问题导致的传输失败。下面将详细...

    android多线程断点下载

    本文将详细介绍如何在Android中实现多线程断点下载,以及如何封装一个易于使用的接口,使得即使是对Android不太熟悉的开发者也能快速上手。 首先,我们要理解什么是多线程下载。传统的单线程下载方式只有一个线程...

    Android多线程断点下载(优化)

    在Android开发中,多线程断点下载是一种常见的优化策略,尤其在处理大文件时,如应用程序、视频或音频文件的下载。这种技术允许用户在任何时间暂停下载,并在稍后从停止的地方继续,而无需重新开始。在本文中,我们...

    多线程断点下载(java线程学习)

    java中多线程下载学习,又新增了断点的实现,可以实现暂停继续下载网络文件的功能

    retrofit实现多线程断点下载,可暂停,开始

    通过上述步骤,我们可以实现一个高效且稳定的多线程断点下载功能,结合Retrofit的网络请求能力和GreenDao的数据库操作,既简化了代码逻辑,又保证了数据的准确性和一致性。在实际项目中,还需要考虑其他因素,如网络...

    Java 多线程断点下载

    Java多线程断点下载是Java编程中一种高级技术,主要应用于大文件的网络传输,如视频、软件安装包等。这种技术结合了多线程和断点续传的概念,提高了下载效率并允许用户在中断后继续从上次停止的位置开始下载。 首先...

    java ftp 多线程 断点续传等知识

    而"多线程"和"断点"这两个文件名可能是指相关示例代码或文档,可以进一步帮助你理解和实践Java FTP的多线程下载和断点续传。 在实际应用中,还需要考虑其他因素,如错误处理、网络状况的监控、文件完整性检查等。...

    OKHttp多线程断点下载

    【OKHttp多线程断点下载】是一种在Android或Java应用中实现高效文件下载的方法,它结合了OKHttp网络库的优秀性能与多线程技术,以提高下载速度,并允许在下载过程中中断和恢复,避免因网络问题或其他因素导致的下载...

    htp多线程断点下载文件

    "htp多线程断点下载文件"这一主题,涉及了网络编程、多线程技术和文件处理等多个知识点。 首先,HTTP(超文本传输协议)是我们浏览网页和下载文件的基础,它是应用层协议,负责客户端和服务器之间的通信。然而,...

    java实现多线程断点续传下载

    通过以上步骤,我们可以构建一个功能完备的Java多线程断点续传下载程序。这个项目不仅可以帮助初学者理解多线程和网络编程的基本概念,也可以作为实际项目开发中的一个参考模板。对于想要深入研究Java网络编程和并发...

Global site tag (gtag.js) - Google Analytics