一. 多线程下载文件考虑处理步骤:

1. 如何获取文件的长度

2. 合理的创建线程数量,并计算每一个线程下载的长度

3. 如何将多个线程下载的字节写入到文件中

二. 代码实现如下:

package com.bochao.download;

import java.io.File;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; /**
* HTTP多线程下载
*
* @Author DuanCZ
* @Date 2015年9月30日-上午9:28:28
*/ public class HttpMulitThreadDownload { // 下载文件路径
private String downloadFilePath = null;
// 保存文件路径
private String saveFileDir = "c:\\";
// 默认合理并发线程数
private int threadCount = Runtime.getRuntime().availableProcessors() * 2;
// 新文件名称
private String newFileName = null; public HttpMulitThreadDownload(int threadCount, String downloadFilePath, String saveFileDir, String newFileName) {
// 用户指定线程数如果小于默认线程数则使用用户指定线程数
if (threadCount < this.threadCount) {
this.threadCount = threadCount;
}
this.downloadFilePath = downloadFilePath;
this.saveFileDir = saveFileDir;
this.newFileName = newFileName;
} public void MulitThreadDownload() { // 数据合法性验证
if (null == downloadFilePath || downloadFilePath.isEmpty()) {
throw new RuntimeException("请指定下载路径!");
}
if (null == saveFileDir || saveFileDir.isEmpty()) {
throw new RuntimeException("请指定保存路径!");
} // 创建保存文件路径如果不存在
File saveFileDirTemp = new File(saveFileDir);
if (!saveFileDirTemp.exists()) {
saveFileDirTemp.mkdirs();
} // 处理文件名称
if (null == newFileName || newFileName.isEmpty()) {
newFileName = downloadFilePath.substring(downloadFilePath.lastIndexOf("/") + 1, downloadFilePath.length());
} // 创建固定大小的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(threadCount);
try {
// 根据文件长度计算合理线程数开始
URLConnection urlConnection = new URL(downloadFilePath).openConnection();
// 获取文件长度
int downloadFileLength = urlConnection.getContentLength();
// 计算每个线程负责的文件字节长度
int averageThreadLength = downloadFileLength / threadCount;
int residueThreadLength = downloadFileLength % threadCount;
// 让每一个线程开始工作
int startIndex = 0;
int endIndex = 0;
for (int i = 0; i < threadCount; i++) { // 计算每一个线程开始和计数索引
startIndex = i * averageThreadLength;
// 如果是最后一个线程,则将剩余的全部下载
if ((i + 1) == threadCount) {
endIndex = (i + 1) * averageThreadLength + residueThreadLength - 1;
}
endIndex = (i + 1) * averageThreadLength - 1; // 创建下载线程对象
DownloadHandlerThread downloadHandlerThread = new DownloadHandlerThread();
downloadHandlerThread.setDownloadFilePath(downloadFilePath);
downloadHandlerThread.setSaveFilePath(saveFileDir + newFileName);
downloadHandlerThread.setStartIndex(startIndex);
downloadHandlerThread.setEndIndex(endIndex);
threadPool.execute(downloadHandlerThread);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭线程池
threadPool.shutdown();
}
}
}
package com.bochao.download;

import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.URL;
import java.net.URLConnection;
import java.text.SimpleDateFormat;
import java.util.Date; /**
 * 下载文件处理线程
 *
 * @Author DuanCZ
 * @Date 2015年9月30日-上午11:38:42
 */ public class DownloadHandlerThread implements Runnable {     // 待下载的HTTP文件路径
    private String downloadFilePath = null;
    // 下载保存文件路径
    private String saveFilePath = null;
    // 文件随机写入开始索引
    private int startIndex = 0;
    // 文件随机写入结束索引
    private int endIndex = 0;     @Override
    public void run() {         System.out.println("线程名称[" + Thread.currentThread().getName() + "]于时间["
                + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "]开始下载....");         // 文件输入流对象
        InputStream fileInputStream = null;
        // 随机访问文件
        RandomAccessFile randomAccessFile = null;         // ------------------------------------------------------------------------------
        // 获取随机文件流开始
        // 获取一个URL打开链接对象
        URLConnection urlConnection = null;
        try {
            urlConnection = new URL(downloadFilePath).openConnection();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        // 设置该链接允许和用户交互
        urlConnection.setAllowUserInteraction(true);
        // 设置请求属性字节范围
        urlConnection.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex + "");
        try {
            // 获取指定的文件流
            fileInputStream = urlConnection.getInputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // ------------------------------------------------------------------------------获取随机文件流结束         // ------------------------------------------------------------------------
        // 写文件流到指定的文件开始
        try {
            // 创建文件随机访问对象
            randomAccessFile = new RandomAccessFile(saveFilePath, "rw");
            // 将文件写入位置移动到其实点
            randomAccessFile.seek(startIndex);
            // 写入文件
            int bytes = 0;
            byte[] buffer = new byte[100 * 1024];
            while ((bytes = fileInputStream.read(buffer, 0, buffer.length)) != -1) {
                randomAccessFile.write(buffer, 0, bytes);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != randomAccessFile) {
                    randomAccessFile.close();
                }
                if (null != fileInputStream) {
                    fileInputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // ------------------------------------------------------------------------写文件流到指定的文件结束         System.out.println("线程名称[" + Thread.currentThread().getName() + "]于时间["
                + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "]下载完成!");
    }     public String getDownloadFilePath() {
        return downloadFilePath;
    }     public void setDownloadFilePath(String downloadFilePath) {
        this.downloadFilePath = downloadFilePath;
    }     public String getSaveFilePath() {
        return saveFilePath;
    }     public void setSaveFilePath(String saveFilePath) {
        this.saveFilePath = saveFilePath;
    }     public int getStartIndex() {
        return startIndex;
    }     public void setStartIndex(int startIndex) {
        this.startIndex = startIndex;
    }     public int getEndIndex() {
        return endIndex;
    }     public void setEndIndex(int endIndex) {
        this.endIndex = endIndex;
    } }

测试:

package com.bochao.download;

public class TestDownload {

	public static void main(String[] args) {

	    String downloadFilePath = "http://localhost:81/mulitThreadDownload/file/PowerDesigner165_Evaluation.1428562995.exe";
String saveFileDir= "f:\\Download\\"; HttpMulitThreadDownload httpMulitThreadDownload = new HttpMulitThreadDownload(2, downloadFilePath, saveFileDir, "powerdesigner.exe");
httpMulitThreadDownload.MulitThreadDownload();
} }

输出结果:

线程名称[pool-1-thread-1]于时间[2015-08-30 16:09:49]开始下载....
线程名称[pool-1-thread-2]于时间[2015-08-30 16:09:49]开始下载....
线程名称[pool-1-thread-2]于时间[2015-09-30 16:09:01]下载完成!
线程名称[pool-1-thread-1]于时间[2015-09-30 16:09:23]下载完成!

Java多线程文件下载的更多相关文章

  1. Android实现网络多线程文件下载

    实现原理 (1)首先获得下载文件的长度,然后设置本地文件的长度. (2)根据文件长度和线程数计算每条线程下载的数据长度和下载位置. 如:文件的长度为6M,线程数为3,那么,每条线程下载的数据长度为2M ...

  2. Java多线程的下载器(1)

    实现了一个基于Java多线程的下载器,可提供的功能有: 1. 对文件使用多线程下载,并显示每时刻的下载速度. 2. 对多个下载进行管理,包括线程调度,内存管理等. 一:单个文件下载的管理 1. 单文件 ...

  3. 简述Java多线程(一)

    JAVA多线程 程序:是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念. 进程:是执行程序的一次执行过程,是一个动态的概念,是系统资源分配的单位. 线程是CPU调度和执行的单位. 创 ...

  4. 40个Java多线程问题总结

    前言 Java多线程分类中写了21篇多线程的文章,21篇文章的内容很多,个人认为,学习,内容越多.越杂的知识,越需要进行深刻的总结,这样才能记忆深刻,将知识变成自己的.这篇文章主要是对多线程的问题进行 ...

  5. Java多线程基础知识篇

    这篇是Java多线程基本用法的一个总结. 本篇文章会从一下几个方面来说明Java多线程的基本用法: 如何使用多线程 如何得到多线程的一些信息 如何停止线程 如何暂停线程 线程的一些其他用法 所有的代码 ...

  6. Java多线程系列--“JUC锁”03之 公平锁(一)

    概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...

  7. Java多线程系列--“JUC锁”04之 公平锁(二)

    概要 前面一章,我们学习了“公平锁”获取锁的详细流程:这里,我们再来看看“公平锁”释放锁的过程.内容包括:参考代码释放公平锁(基于JDK1.7.0_40) “公平锁”的获取过程请参考“Java多线程系 ...

  8. Java多线程--让主线程等待子线程执行完毕

    使用Java多线程编程时经常遇到主线程需要等待子线程执行完成以后才能继续执行,那么接下来介绍一种简单的方式使主线程等待. java.util.concurrent.CountDownLatch 使用c ...

  9. Java多线程 2 线程的生命周期和状态控制

    一.线程的生命周期 线程状态转换图: 1.新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就 ...

随机推荐

  1. elk系列1之入门安装与基本操作

    preface 我们每天都要查看服务器的日志,一方面是为了开发的同事翻找日志,另一方面是巡检服务器查看日志,而随着服务器数量以及越来越多的业务上线,日志越来越多,人肉运维相当痛苦了,此时,参考现在非常 ...

  2. Yocto开发笔记之《驱动调试-华为3G模块》(QQ交流群:519230208)

    QQ群:519230208,为避免广告骚扰,申请时请注明 “开发者” 字样 ======================================================== 参考:ht ...

  3. B450黑苹果之路(1)

    安装黑苹果,采取的是懒人版写入磁盘分区中,然后再安装1)从硬盘中分两个区,一个是未来使用的目标分区,一个是磁盘镜像区2)由于HFS+支持逻辑分区,所以两个分区都放逻辑分区上,分区不格式化3)使用硬盘助 ...

  4. 《Struts2.x权威指南》学习笔记2

    在学习了第二章后,我想要将struts分类,修改一下struts.xml的默认读取路径如下图. 在IntelliJ中,resources是struts的默认路径 修改路径,需要在web.xml中添加s ...

  5. JavaScript排序算法——冒泡排序

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  6. 第1章 JavaScript简介

    概述:Javascript是一种依赖于网页浏览器的脚本语言.是一种脚本语言.由Netscape和Sun共同开发.与Java没有什么关系.作为一种语言标准,而被称为ECMACcript.一个JS的实现包 ...

  7. Jquerymobile 简单安装

    需要导入三个文件jquery,jquerymobile,css(jquerymobile地址:http://jquerymobile.com/) <script src="js/jqu ...

  8. yourphp的sql语句

    1.插入单条数据 $data[0]['cardid'] = $_POST['cardid']; $data[0]['name'] = $_POST['name']; $data[0]['mobile' ...

  9. awk操作数组注意几点

    awk的数组跟其他程序设计语言的数组有所不同:1.可以直接在awk中定义数组:2.数组元素的初始值为0或空字符串,除非他们被显示的指定初始化:3.数组可以自动扩展:4.都是关联数组,数字下标也会转成字 ...

  10. SCWS分词扩展在UNIX/LINUX下的安装方法

    <?php/** * 中文分词处理方法 *+--------------------------------- * @param stirng  $string 要处理的字符串 * @param ...