使用多线程下载文件可以更快完成文件的下载,多线程下载文件之所以快,是因为其抢占的服务器资源多。如:假设服务器同时最多服务100个用户,在服务器中一条线程对应一个用户,100条线程在计算机中并非并发执行,而是由CPU划分时间片轮流执行,如果A应用使用了99条线程下载文件,那么相当于占用了99个用户的资源,假设一秒内CPU分配给每条线程的平均执行时间是10ms,A应用在服务器中一秒内就得到了990ms的执行时间,而其他应用在一秒内只有10ms的执行时间。就如同一个水龙头,每秒出水量相等的情况下,放990毫秒的水
肯定比放10毫秒的水要多。
多线程下载的实现过程:
 
简单了解一下多条线程将一条数据流分段下载的小思路:
假设10byte的数据流分3条线程下载:
10/3+1=4,即每条线程下载4个字节,0~3,4~7,8~9三段线程数据流(第三段理论值是4个字节,但由于下载2个字节就结束数据下载内容了,所以数据流会自动关闭),那么如下计算各个线程应该从什么位置开始下载与在哪个位置结束呢?
每条线程下载的数据长度:
10%3==0? 10/3  :10/3+1 即数据总长度%线程条数
计算线程开始位置公式:
 
线程的id(即第几条线程,起始值为0)* 每条线程下载的数据长度 = ?
计算线程结束位置公式:
 
线程的id(即第几条线程,起始值为0)+1 * 每条线程下载的数据长度-1 = ?

1>首先得到下载文件的长度,然后设置本地文件
的长度。
HttpURLConnection.getContentLength();
RandomAccessFile file = new RandomAccessFile("QQWubiSetup.exe","rwd");
file.setLength(filesize);//设置本地文件的长度

2>根据文件长度和线程数计算每条线程下载的数据长度和下载位置。如:文件的长度为6M,线程数为3,那么,每条线程下载的数据长度为2M,每条线程开始下载的位置如上图所示。
3>使用Http的Range头字段指定每条线程从文件的什么位置开始下载,下载到什么位置为止,如:指定从文件的2M位置开始下载,下载到位置(4M-1byte)为止,代码如下:
HttpURLConnection.setRequestProperty("Range", "bytes=2097152-4194303");

4>保存文件,使用RandomAccessFile类指定每条线程从本地文件的什么位置开始写入数据。
RandomAccessFile threadfile = new RandomAccessFile("QQWubiSetup.exe ","rwd");
threadfile.seek(2097152);//从文件的什么位置开始写入数据
 
核心代码
import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

public class ThreadDowmLoad {

  public static void main(String[] args) {
    String path = "http://file1.51cto.com/index.php?aid=785088&k=205dfb403540e1ee182fd48c80247ab6&t=1347439680";
    int threadNum = 3;
    try {
      new ThreadDowmLoad().downLoad(path, threadNum);
    } catch (Exception e) {

      e.printStackTrace();
    }

  }

  /**
    * 从路径中获取下载文件名称
    *    
    * @param path下载文件的路径
    * @return 最后一个"/"后面字段的内容作为下载后的文件名
    */

  public static String getFileName(String path) {
    // 获得最后一个"/"后面字段的内容作为下载后的文件名
    return path.substring(path.lastIndexOf("/") + 1);
  }

  /**
    * @param path下载文件的路径
    * @param threadNum线程数
    * @throws Exception
    */

  public void downLoad(String path, int threadNum) throws Exception {
    URL url = new URL(path);
    HttpURLConnection httpURLConnection = (HttpURLConnection) url
        .openConnection();
    // 得到httpURLConnection对象进行相关设置
    // 设置请求方式
    httpURLConnection.setRequestMethod("GET");
    // 设置链接超时时间
    httpURLConnection.setConnectTimeout(5 * 1000);
    // 通过getContentLength()取得下载长度
    int downloadLength = httpURLConnection.getContentLength();
    // 生成同长度的本地文件,存放下载数据-----RandomAccessFile随机文件访问类
    String fileName = getFileName(path);// 从路径中获取下载文件名称
    // 通过相对路径将下载后的文件保存到根目录中
    File saveFile = new File(fileName);
    // "rwd"表示同步将文件下载到指定路径中,没有缓存,若没有“d”则会有缓存不同步
    RandomAccessFile randomAccessFile = new RandomAccessFile(saveFile,
        "rwd");
    // 保存在本地的文件长度=网络下载的文件长度
    randomAccessFile.setLength(downloadLength);
    randomAccessFile.close();

    // 计算每条线程下载的数据长度
    int block = downloadLength % threadNum == 0 ? (downloadLength / threadNum)
        : (downloadLength / threadNum) + 1;
    // 计算每条线程开始下载与结束下载的位置
    for (int i = 0; i < threadNum; i++) {
      // 开线程下载各自的数据段
      new DownLoadThread(url, saveFile, block, threadNum).start();

    }
  }

  /**
    *    
    * @author YanJun URL url, 路径 File saveFile,文件名 int block, 数据段长度 int
    *                 threadNum 指定线程
    */

  private final class DownLoadThread extends Thread {
    private URL url;
    private File saveFile;
    private int block;
    private int threadNum;

    public DownLoadThread(URL url, File saveFile, int block, int threadNum) {
      super();
      this.url = url;
      this.saveFile = saveFile;
      this.block = block;
      this.threadNum = threadNum;
    }

    @Override
    public void run() {
      // 计算下载文件数据的开始和结束位置
      int startPosition = threadNum * block;
      int endPosition = (threadNum + 1) * block - 1;

      try {
        // 随机文件访问类
        RandomAccessFile randomAccessFile = new RandomAccessFile(
            saveFile, "rwd");
        // 从什么位置访问数据randomAccessFile.seek(startPosition);
        randomAccessFile.seek(startPosition);
        HttpURLConnection httpURLConnection = (HttpURLConnection) url
            .openConnection();
        // 得到httpURLConnection对象进行相关设置
        // 设置请求方式
        httpURLConnection.setRequestMethod("GET");
        // 设置链接超时时间
        httpURLConnection.setConnectTimeout(5 * 1000);
        // 使用Http的Range头字段指定每条线程从文件的什么位置开始下载,下载到什么位置为止,如:指定从文件的2M位置开始下载
        httpURLConnection.setRequestProperty("Range", "bytes="
            + startPosition + "-" + endPosition);
        // 取得输入流
        InputStream inputStream = httpURLConnection.getInputStream();
        byte[] buffer = new byte[1024];
        int len = 0;// 当len=-1,表示流读取完毕
        // inputStream.read(buffer)--从输入流里读取数据到缓冲区
        while ((len = inputStream.read(buffer)) != -1) {
          randomAccessFile.write(buffer, 0, len);

        }
        inputStream.close();
        randomAccessFile.close();
        System.out.println(threadNum + "号下载完成");
      } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }

  }
}