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

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. nginx https性能优化

    影响HTTPS速度的主要原因:秘钥交换算法 常见的密钥交换算法有 RSA,ECDHE,DH,DHE 等算法.它们的特性如下: RSA:算法实现简单,诞生于 1977 年,历史悠久,经过了长时间的破解测 ...

  2. PHP 生成图片缩略图函数

    <?php /** * 生成缩略图函数(支持图片格式:gif.jpeg.png和bmp) * @author ruxing.li * @param string $src 源图片路径 * @pa ...

  3. 创建一个应用台程序,声明一个 int变量,并且初始化数组,通过while语句输出数组内的所有成员。

    ]{,,,,}; ; ) { Console.WriteLine("myNum[{0}]的值为{1}", s,myNum[s]); s++; } Console.ReadLine( ...

  4. python数据库操作常用功能使用详解(创建表/插入数据/获取数据)

    实例1.取得MYSQL版本 复制代码 代码如下: # -*- coding: UTF-8 -*-#安装MYSQL DB for pythonimport MySQLdb as mdbcon = Non ...

  5. Mysql 与 Python socket

    py1.py # -*- coding: utf-8 -*- import sqlalchemy import tushare import pandas import socket import s ...

  6. Nvidia Nsight + .NET

    https://devtalk.nvidia.com/default/topic/804306/nsight-4-5-can-t-debug-net-applications/ http://comm ...

  7. ecshop 配送方式支持"货到付款"

    ecshop配送方式,支持货到付款方法. 打开表 ecs_shipping,找到字段support_cod,修改1则支持货到付款 扩展 问题描述:已经修改了ecs_shipping表里的support ...

  8. yii2权限控制rbac之rule详细讲解(转)

    在我们之前yii2搭建后台以及rbac详细教程中,不知道你曾经疑惑过没有一个问题,rule表是做什么的,为什么在整个过程中我们都没有涉及到这张表? 相信我不说,部分人也都会去尝试,或百度或google ...

  9. virtualBox切换到无缝模式后,如何调出菜单

    host+c host就是指右边的那个ctrl键

  10. post提交表单

    <script type="text/javascript"> $(function () { $("#btnRefresh1").click(fu ...