Java 多线程断点下载(面向对象)

来源:互联网 发布:哈达迪nba数据 编辑:程序博客网 时间:2024/06/09 17:10

主程序

/** * 客户端 */package ThreadDownload;public class Client {public String urlFile; // 网络文件地址public int threadNum; // 需要启动下载的线程数public String localFilePath; // 需要保存下载文件的本地地址,保证该目录下没有名为"tmp"的文件夹/** * 客户端自己设定 */public Client() {urlFile = "http://dl.google.com/android/android-sdk_r12-windows.zip";threadNum = 9;localFilePath = "d://";}private void start() {Thread thread = new Thread(new MultiThreadGetFile(urlFile,localFilePath, threadNum));thread.start();}public static void main(String[] args) {Client client = new Client();client.start();}}


多线程下载调度程序

/** * 多线程下载调度程序 */package ThreadDownload;import java.io.File;import java.net.HttpURLConnection;import java.net.URL;import java.util.StringTokenizer;public class MultiThreadGetFile extends Thread {public long startPos = 0;public long endPos = 0;public String currentFileThreadName; // 要带上完整的路径public String urlFile; // 网络文件地址public String urlFileName; // 网络文件名public String localFilePath; // 需要保存下载文件的本地地址public int threadNum; // 需要启动下载的线程数public long[] eachThreadLength; // 每个线程要下载的文件分块的大小public long urlFileLength; // 网络文件的大小URL url;HttpURLConnection httpURLConnection;public static boolean[] checkList; // 检测线程/** * @param urlFile 网络文件地址 * @param localFilePath 需要保存下载文件的本地地址 * @param threadNum 需要启动下载的线程数 */public MultiThreadGetFile(String urlFile, String localFilePath,int threadNum) {super();this.urlFile = urlFile;this.localFilePath = localFilePath;this.threadNum = threadNum;}/** * 确定每个线程文件最终要写的文件的大小 */private void init_getEachThreadLength() {long l;l = urlFileLength / threadNum;for (int i = 0; i < threadNum; i++) {if (i == threadNum - 1) {eachThreadLength[i] = urlFileLength - i * l;} else {eachThreadLength[i] = l;}}}/** * 得到传递过来的带路径的文件的文件名 eg:GetFileName("d://test/test.txt") 返回为:test.txt */private String getFileName(String file) {StringTokenizer st = new StringTokenizer(file, "/");while (st.hasMoreTokens()) {file = st.nextToken();}return file;}private void init() {// 创建一个临时文件夹tmpif (!new File(localFilePath + "tmp").mkdir()) {// System.out.println("创建文件夹失败!");}eachThreadLength = new long[threadNum];try {url = new URL(urlFile);// 此处的Connection仅仅是用于获取服务端的要下载的资源的名称和资源的大小,所以一旦得到后就关闭httpURLConnection = (HttpURLConnection) url.openConnection();urlFileLength = Long.parseLong(httpURLConnection.getHeaderField("Content-Length"));urlFileName = url.getFile(); // 取得在服务器上的路径及文件名urlFileName = getFileName(urlFileName); // 只取得文件名init_getEachThreadLength(); // 确定每个线程最终要写的文件的大小httpURLConnection.disconnect();checkList = new boolean[threadNum + 1]; // 记载每个线程是否下载完毕for (int i = 1; i <= threadNum; i++) {if (i > 1) {startPos = startPos + eachThreadLength[i - 2];}endPos = startPos + eachThreadLength[i - 1];currentFileThreadName = localFilePath + "tmp\\" + urlFileName+ ".part" + i;System.out.println("part" + i + ": " + startPos + "--->"+ endPos + ", size: " + (endPos - startPos));Thread thread = new Thread(new GetFileThread(startPos, endPos,currentFileThreadName, urlFile, i));thread.start();checkList[i] = false; // 表示该线程开始}Thread monitorThread = new Thread(new MonitorThread(threadNum,localFilePath, localFilePath + "tmp"));monitorThread.start();} catch (Exception e) {e.printStackTrace();}}public void run() {init();}}

下载线程

/**  * 下载线程  * 原理:  * 根据传入的下载开始点以及文件下载结束点,利用HttpURLConnection的RANGE属性  * 从网络文件开始下载,并结合判断是否下载的文件大小已经等于(文件下载结束点-下载开始点)  * 这里结合断点续传原理,可以更快、更有效的下载文件  */package ThreadDownload;import java.io.BufferedInputStream;import java.io.DataOutputStream;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.RandomAccessFile;import java.net.HttpURLConnection;import java.net.URL;public class GetFileThread extends Thread {public long startPos; // 传入的文件下载开始点public long endPos; // 传入的文件下载结束点public String currentFileThreadName; // 当前线程的完程路径及名字public String urlFile; // 网络文件地址int currentThread; // 当前线程,用于下载完成后将对应的检测标志设为true,表示下载完成/** * @param startPos 传入的文件下载开始点 * @param endPos 传入的文件下载结束点 * @param currentFileThreadName 当前线程的完程路径及名字 * @param urlFile 网络文件地址 * @param currentThread 当前线程 */public GetFileThread(long startPos, long endPos,String currentFileThreadName, String urlFile, int currentThread) {super();this.startPos = startPos;this.endPos = endPos;this.currentFileThreadName = currentFileThreadName;this.urlFile = urlFile;this.currentThread = currentThread;}private boolean fileExist(String pathAndFile) {File file = new File(pathAndFile);if (file.exists())return true;elsereturn false;}private long fileSize(String pathAndFile) {long fileSize = 0;File filet = new File(pathAndFile);fileSize = filet.length();return fileSize;}private void fileRename(String fName, String nName) {File file = new File(fName);file.renameTo(new File(nName));file.delete();}public void run() {URL url = null;HttpURLConnection httpURLConnection = null;BufferedInputStream bis = null; // 缓存流int len = 0;byte[] bt = new byte[1024]; // 缓冲区DataOutputStream dos = null;FileOutputStream fos = null;RandomAccessFile raf = null;String localFile = currentFileThreadName; // 文件保存的地方及文件名,具体情况可以改String localFile_tp = localFile + ".tp"; // 未下载完文件加.tp扩展名,以便于区别long fileSize = 0; // 在断点续传中,用于取得当前文件已经下载的大小long totalSize = 0; // 当前块要下载的文件总大小try {url = new URL(urlFile);httpURLConnection = (HttpURLConnection) url.openConnection();totalSize = endPos - startPos; // 取得该快实际要写的文件大小long downSize = 0; // 已经下载的大小// 确定临时文件是否存在if (fileExist(localFile_tp)) { // 采用断点续传,这里的依据是看下载文件是否在本地有.tp有扩展名同名文件System.out.println("线程" + currentThread + "文件正在续传...");fileSize = new File(localFile_tp).length(); // 取得已经下载的大小,以便确定随机写入的位置downSize = fileSize; // 下载大小fileSize = startPos + fileSize; // 取得文件开始写入点// httpURLConnection属性的设置一定要在得到输入流之前,否则会报已经连接的错误httpURLConnection.setRequestProperty("RANGE", "bytes="+ fileSize + "-");// 设置接受信息httpURLConnection.setRequestProperty("Accept","image/gif,image/x-xbitmap,application/msword,*/*");raf = new RandomAccessFile(localFile_tp, "rw"); // 随机方式读取raf.seek(downSize); // 定位指针到downSize已下载文件大小的位置bis = new BufferedInputStream(httpURLConnection.getInputStream()); // 缓存流while ((len = bis.read(bt)) > 0) {if (downSize < (endPos - startPos)) {downSize = downSize + len;// 说明上一步是多加了一个downSize,这里再减去一个downSizeif (downSize > (endPos - startPos)) {len = (int) ((endPos - startPos) - (downSize - len));}raf.write(bt, 0, len);} else {break;}}System.out.println("线程" + currentThread + "文件续传完成...");}// 采用原始下载,但保证该文件没有下载else if (!fileExist(localFile)) {// 设置断点续传的开始位置httpURLConnection.setRequestProperty("RANGE", "bytes="+ startPos + "-");bis = new BufferedInputStream(httpURLConnection.getInputStream());fos = new FileOutputStream(localFile_tp); // 没有下载完毕就将文件的扩展名命名.tpdos = new DataOutputStream(fos);System.out.println("线程" + currentThread + "正在接收文件...");while ((len = bis.read(bt)) > 0) {// 确定没有下载完毕if (downSize < (endPos - startPos)) {downSize = downSize + len;// 如果当前下载的加上要下载的已经超过要求的下载范围if (downSize > (endPos - startPos)) {// 就只取满足要求的下载部分len = (int) ((endPos - startPos) - (downSize - len));}dos.write(bt, 0, len); // 写文件} else {break;}}}// 下载完毕后,将文件重命名if (fileSize(localFile_tp) == totalSize) {fileRename(localFile_tp, localFile);}MultiThreadGetFile.checkList[currentThread] = true;} catch (Exception e) {e.printStackTrace();} finally {try {if (bis != null) {bis.close();}if (dos != null) {dos.close();}if (fos != null) {fos.close();}if (raf != null) {raf.close();}} catch (IOException ioe) {ioe.printStackTrace();}}}}

监视线程

/** * 监视线程,检测其它的线程是否已经运行完毕 * 原理: * 在MultiThreadGetFile里定义一个全局静态boolean数组,在启动每个GetFileThread的时候 * 就将对应的数组的值设为false,当对应线程完成后就把对应的数组设为true * 在当前线程采用不停检测是否所有数组的值都为true, * 如是那就说明所有的线程已经运行完毕,如果没有就继续检测。 * 等到所有的GetFileThread线程都完成后,那么就调用文件拼合线程,合并下载的文件块并删除 临时文件块。 */package ThreadDownload;public class MonitorThread extends Thread {public int totalThread;public String localFilePath;public String localFilePath_tmp;/** * @param totalThread 总线程数 * @param localFilePath 本地下载文件存放路径 * @param localFilePath_tmp 正在下载的文件存放路径 */public MonitorThread(int totalThread, String localFilePath,String localFilePath_tmp) {super();this.totalThread = totalThread;this.localFilePath = localFilePath;this.localFilePath_tmp = localFilePath_tmp;}public void run() {boolean isRun = true;int allStop = 0;while (isRun) {allStop = 0;for (int i = 1; i <= totalThread; i++) {if (MultiThreadGetFile.checkList[i] == true) {allStop++;}}try {Thread.sleep(500);} catch (Exception e) {e.printStackTrace();}// 说明都已经下载完毕,此时停止线程下载if (allStop == totalThread) {isRun = false;}}// 到此,说明九个线程均已下载完毕,开始合并文件的线程操作Thread thread = new Thread(new FileCombination(localFilePath,localFilePath_tmp));thread.start();}}

文件合并线程

/** * 合并文件:合并由拆分文件拆分的文件 * 要求将拆分文件放到一个文件中 * 主要利用随机文件读取和文件输入输出流 */package ThreadDownload;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.RandomAccessFile;import java.util.Arrays;import java.util.StringTokenizer;public class FileCombination extends Thread {public String srcDirectory = null; // 拆分文件存放的目录public String saveDirectory; // 结果文件存放目录public String[] separatedFiles; // 存放所有拆分文件名public String fileRealName = ""; // 据拆分文件名确定现在原文件名public String[][] separatedFilesAndSize; // 存放所有拆分文件名及分件大小public int FileNum = 0; // 确定文件个数/** * @param srcDirectory 拆分文件存放的目录 * @param saveDirectory 结果文件存放目录 */public FileCombination(String trueDirectory, String srcDirectory) {super();this.srcDirectory = srcDirectory;this.saveDirectory = trueDirectory;}/** * @param sFileName 任意一个拆分文件名 * @return 原文件名 */private String getRealName(String sFileName) {StringTokenizer st = new StringTokenizer(sFileName, ".");return st.nextToken() + "." + st.nextToken();}/** * @param FileName 拆分的文件名 * @return 取得指定拆分文件模块的文件大小 */private long getFileSize(String FileName) {FileName = srcDirectory + "//" + FileName;return (new File(FileName).length());}/** * 生成一些属性,做初使化 *  * @param drictory 拆分文件属性 */private void getFileAttribute(String drictory) {File file = new File(drictory);separatedFiles = new String[file.list().length];// 依文件数目动态生成一维数组,只有文件名separatedFiles = file.list();// 依文件数目动态生成二维数组,包括文件名和文件大小// 第一维装文件名,第二维为该文件的字节大小separatedFilesAndSize = new String[separatedFiles.length][2];Arrays.sort(separatedFiles); // 排序FileNum = separatedFiles.length; // 当前文件夹下面有多少个文件for (int i = 0; i < FileNum; i++) {separatedFilesAndSize[i][0] = separatedFiles[i]; // 文件名separatedFilesAndSize[i][1] = String.valueOf(getFileSize(separatedFiles[i])); // 文件大小}fileRealName = getRealName(separatedFiles[FileNum - 1]); // 取得文件分隔前的原文件名}/** * 合并文件:利用随机文件读写 *  * @return true 成功合并文件 */private boolean CombFile() {RandomAccessFile raf = null;long alreadyWrite = 0;FileInputStream fis = null;int len = 0;byte[] bt = new byte[1024];try {raf = new RandomAccessFile(saveDirectory + "//" + fileRealName,"rw");for (int i = 0; i < FileNum; i++) {raf.seek(alreadyWrite);// System.out.println("alreadyWrite:"+alreadyWrite);fis = new FileInputStream(srcDirectory + "//"+ separatedFilesAndSize[i][0]);while ((len = fis.read(bt)) > 0) {raf.write(bt, 0, len);}fis.close();alreadyWrite = alreadyWrite+ Long.parseLong(separatedFilesAndSize[i][1]);}raf.close();} catch (Exception e) {e.printStackTrace();try {if (raf != null)raf.close();if (fis != null)fis.close();} catch (IOException f) {f.printStackTrace();}return false;}return true;}public void deleteTmp() {for (int i = 0; i < FileNum; i++) {File file = new File(srcDirectory + "//"+ separatedFilesAndSize[i][0]);file.delete();}File file1 = new File(srcDirectory);file1.delete();}public void run() {getFileAttribute(srcDirectory);CombFile();deleteTmp();}}

代码测试:

第一次下载

part1: 0--->4054021, size: 4054021part2: 4054021--->8108042, size: 4054021part3: 8108042--->12162063, size: 4054021part4: 12162063--->16216084, size: 4054021part5: 16216084--->20270105, size: 4054021part6: 20270105--->24324126, size: 4054021part7: 24324126--->28378147, size: 4054021part8: 28378147--->32432168, size: 4054021part9: 32432168--->36486190, size: 4054022线程3正在接收文件...线程2正在接收文件...线程9正在接收文件...线程6正在接收文件...线程7正在接收文件...线程4正在接收文件...线程1正在接收文件...线程5正在接收文件...线程8正在接收文件...

第二次下载

part1: 0--->4054021, size: 4054021part2: 4054021--->8108042, size: 4054021part3: 8108042--->12162063, size: 4054021线程1文件正在续传...线程2文件正在续传...part4: 12162063--->16216084, size: 4054021part5: 16216084--->20270105, size: 4054021线程3文件正在续传...part6: 20270105--->24324126, size: 4054021线程5文件正在续传...part7: 24324126--->28378147, size: 4054021part8: 28378147--->32432168, size: 4054021线程6文件正在续传...part9: 32432168--->36486190, size: 4054022线程7文件正在续传...线程4文件正在续传...线程9文件正在续传...线程8文件正在续传...线程5文件续传完成...线程9文件续传完成...线程7文件续传完成...线程6文件续传完成...线程3文件续传完成...线程2文件续传完成...线程1文件续传完成...线程4文件续传完成...线程8文件续传完成...


0 0