前面写了单线程下载、断点续传、文件分隔与合并三个程序(具体可以参见我前面的程序),在这个程序的基础之上,我完成了多线程下载程序,并具备断点续传功能。
该程序具有5个文件:Main.java(主文件)、FileCombination.java(临时文件合并)、GetFileThread.java(网络文件获取)、MultiThreadGetFile.java(多线程下载调度程序)、PoliceThread.java(监视线程,确定所有的文件块都完成,并调用文件合并程序)。文件详细如下:
Main.java:
package MultiThread;
/**
* 程序主文件
*/
public class Main
{
String urlFile;//网络文件地址
int threadNum;//要启动下载的线程数
String localFileAddress;//要保存的本地地址,请保重该处没有名为"tmp"的文件夹
public Main()
{
/**
* 下面的由使用者自己设为定
*/
urlFile="http://www.netbox.cn/download/nbsetup.EXE";
threadNum=9;//要同时下载的线程数
localFileAddress="d:\\multiDownTest\\";
}
private void start()
{
Thread thread=new Thread(new MultiThreadGetFile(urlFile,threadNum,localFileAddress));
thread.start();
}
public static void main(String[] args)
{
Main main = new Main();
main.start();
}
}
FileCombination.java:
package MultiThread;
/**
* 合并文件:合并由拆分文件拆分的文件
* 要求将拆分文件放到一个文件夹中
* 主要利用随机文件读取和文件输入输出流
*/
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
{
String srcDirectory=null;//拆分文件存放的目录
String trueDirectory;//结果文件存放目录
String[] separatedFiles;//存放所有拆分文件名
String[][] separatedFilesAndSize;//存放所有拆分文件名及分件大小
int FileNum=0;//确定文件个数
String fileRealName="";//据拆分文件名确定现在原文件名
public FileCombination(String trueDirectory,String srcDirectory)
{
this.srcDirectory=srcDirectory;
this.trueDirectory=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(trueDirectory+"\\"+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();
}
}
GetFileThread.java:
package MultiThread;
/**
* 下载线程
* 原理:
* 根据传入的下载开始点以及文件下载结束点,利用HttpURLConnection的RANGE属性,
* 从网络文件开始下载,并结合判断是否下载的文件大小已经等于(文件下载结束点-下载开始点), *
* 这里结合断点续传原理,可以更快、更有效的下载文件
*/
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.*;
public class GetFileThread extends Thread
{
long startPos,endPos;//传入的文件下载开始、结束点
String currentFileThreadName;//要带上完整的路径
String urlFile;//网络文件地址
int currentThread;//当前是那个线程,这主要是用于下载完成后将对应的检测标志设为true,表示下载完成
/**
*
* @param urlFile 网络文件地址
* @param startPos 网络开始下载点
* @param endPos 网络文件结点
* @param currentFileThreadName 当前线程的完程路径及名字
* @param currentThread 当前是第几个线程
*/
public GetFileThread(String urlFile,long startPos,long endPos,String currentFileThreadName,int currentThread)
{
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;
else
return 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;
DataOutputStream dos = null;
BufferedInputStream bis = null;
FileOutputStream fos = null;
String localFile = currentFileThreadName; //文件保存的地方及文件名,具体情况可以改
String localFile_tp = localFile + ".tp"; //未下载完文件加.tp扩展名,以便于区别
long fileSize = 0;//在断点续传中,用于取得当前文件已经下载的大小
int len = 0;
byte[] bt = new byte[1024];//缓冲区
//byte[] buffer=new byte[50*1024];
RandomAccessFile raFile = null;
long TotalSize = 0; //当前块要下载的文件总大小
try
{
url = new URL(urlFile);
httpURLConnection = (HttpURLConnection) url.openConnection();
//TotalSize = Long.parseLong(urlc.getHeaderField("Content-Length"));//取得网络文件大小
TotalSize=endPos-startPos;//取得要该块文件实际要写的大小
long downSize=0;//已经下载的大小
//确定临时文件是否存在
if (FileExist(localFile_tp)) //采用断点续传,这里的依据是看下载文件是否在本地有.tp有扩展名同名文件
{
System.out.println("文件续传中...");
fileSize=new File(localFile_tp).length();//取得已经下载的大小,以便确定随机写入的位置
downSize=fileSize;//下载大小
fileSize=fileSize+startPos;//取得文件开始写入点
//设置User-Agent
//urlc.setRequestProperty("User-Agent","NetFox");
/**
* httpURLConnection属性的设置一定要在得到输入流之前,否则会报已经连接的错误
*/
//设置断点续传的开始位置
//synchronized(new Object()){
httpURLConnection.setRequestProperty("RANGE", "bytes=" + fileSize + "-");
//urlc.setRequestProperty("RANGE", "bytes="+fileSize);//这样写不行,不能少了这个"-".
//设置接受信息
httpURLConnection.setRequestProperty("Accept",
"image/gif,image/x-xbitmap,application/msword,*/*");
//}
raFile = new RandomAccessFile(localFile_tp, "rw"); //随机方位读取
raFile.seek(downSize); //定位指针到fileSize位置
bis = new BufferedInputStream(httpURLConnection.getInputStream());
while ((len = bis.read(bt)) > 0)
{
if(downSize<(endPos-startPos))
{
downSize=downSize+len;
if(downSize>(endPos-startPos))
{
len=(int)((endPos-startPos)-(downSize-len));
}
raFile.write(bt, 0, len);
}
else
break;
}
//System.out.println("文件续传接收完毕!");
}
else if(!FileExist(localFile))//采用原始下载,但保证该文件没有下载
{
//设置断点续传的开始位置
httpURLConnection.setRequestProperty("RANGE", "bytes=" + startPos + "-");
bis = new BufferedInputStream(httpURLConnection.getInputStream());
fos = new FileOutputStream(localFile_tp); //没有下载完毕就将文件的扩展名命名.tp
dos = new DataOutputStream(fos);
//System.out.println("正在接收文件...");
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 (bis != null)
bis.close();
if (dos != null)
dos.close();
if (fos != null)
fos.close();
if (raFile != null)
raFile.close();
//System.out.println("localFile_bak:" + FileSize(localFile_bak));
if (FileSize(localFile_tp) == TotalSize) //下载完毕后,将文件重命名
{
FileRename(localFile_tp, localFile);
}
MultiThreadGetFile.checkList[currentThread]=true;
}
catch (Exception e)
{
try
{
if (bis != null)
bis.close();
if (dos != null)
dos.close();
if (fos != null)
fos.close();
if (raFile != null)
raFile.close();
}
catch (IOException f)
{
f.printStackTrace();
}
e.printStackTrace();
}
}
}
MultiThreadGetFile.java:
package MultiThread;
/**
* 多线程下载调度程序
*/
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.StringTokenizer;
public class MultiThreadGetFile extends Thread
{
long startPos=0,endPos=0;
String currentFileThreadName;//要带上完整的路径
String urlFile;//网络文件地址
String urlFileName;//网络文件名
String localFileAddress;//下载文件要存放的地址
int threadNum;//要同时下载的线程数
long[] eachThreadLength;//每个线程要下功的文件分块的大小
long urlFileLength;//网络文件的大小
URL url;
HttpURLConnection httpURLConnection;
public static boolean[] checkList;//检测线程
public MultiThreadGetFile(String urlFile,int threadNum,String localFileAddress)
{
this.urlFile=urlFile;
this.threadNum=threadNum;//要同时下载的线程数
this.localFileAddress=localFileAddress;
}
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;
}
}
private String GetFileName(String file)
{
StringTokenizer st=new StringTokenizer(file,"/");
while(st.hasMoreTokens())
{
file=st.nextToken();
}
return file;
}
private void init()
{
if(!new File(localFileAddress+"tmp").mkdir())//创建一个临时文件夹
{
System.out.println("创建文件夹失败!");
}
eachThreadLength=new long[threadNum];
try
{
url=new URL(urlFile);
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=localFileAddress+"tmp\\"+urlFileName+".part"+i;
//System.out.println("startPos:"+(startPos));
//System.out.println("endPos:"+(endPos));
//System.out.println("Size:"+(endPos-startPos));
Thread thread=new Thread(new GetFileThread(urlFile,startPos,endPos,currentFileThreadName,i));
thread.start();
checkList[i]=false;//表示该线程开始
}
Thread policeThread=new Thread(new PoliceThread(threadNum,localFileAddress,localFileAddress+"tmp"));
policeThread.start();
}
catch (Exception e)
{
e.printStackTrace();
}
}
public void run()
{
init();
}
}
PoliceThread.java:
package MultiThread;
/**
* 监视线程,检测其它的线程是否已经运行完毕
* 原理:
* 在MultiThreadGetFile里定义一个全局静态boolean数组,在启动每个GetFileThread
* 的时候,就将对应的数组的值设为false,当对应线程完成后就把对应的数组设为true,
* 在当前线程采用不停检测是否所有数组的值都为true,如是那就说明所有的线程已经运行完
* 毕,如果没有就继续检测。
* 等到所有的GetFileThread线程都完成后,那么就调用文件拼合线程,合并下载的文件块并删除
* 临时文件块。
*/
public class PoliceThread
extends Thread
{
int totalThread;
String localFileAddress;
String localFileAddress_tmp;
public PoliceThread(int totalThread, String localFileAddress,
String localFileAddress_tmp)
{
this.totalThread = totalThread;
this.localFileAddress = localFileAddress;
this.localFileAddress_tmp = localFileAddress_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
{
this.sleep(500);
}
catch (Exception e)
{
e.printStackTrace();
}
if (allStop == totalThread)
isRun = false;
}
Thread thread =
new Thread(new FileCombination(localFileAddress, localFileAddress_tmp));
thread.start();
}
}
分享到:
相关推荐
在IT领域,多线程下载和断点续传是提高下载效率和用户体验的重要技术。这里,我们将深入探讨这两个概念,并结合使用HttpURLConnection实现的多线程下载工具进行讲解。 首先,多线程下载是一种利用网络资源的方式,...
Java 断点续传技术是网络传输中常用的一种机制,特别是在大文件下载或上传时,如果因为网络中断或其他原因导致传输失败,可以借助断点续传功能从上次中断的地方继续,而无需重新开始。在Java中实现断点续传,通常...
Java多线程断点续传下载是一个复杂但实用的技术,尤其在处理大文件或网络不稳定时,能够提高用户体验并优化资源利用。以下是对这个主题的详细解析: **1. Java多线程** Java多线程是指在一个Java应用程序中同时执行...
总结来说,Java的多线程下载和断点续传技术是解决大文件下载问题的有效手段,通过合理利用多线程和存储状态,可以提升下载效率并改善用户下载体验。在开发过程中,应关注线程安全、文件同步以及错误处理等细节,以...
### Java多线程下载与断点续传技术详解 #### 一、背景介绍 随着互联网技术的发展,数据传输成为日常开发中的重要环节之一。在实际应用中,常常需要下载大文件,例如视频、大型应用程序等。传统的单线程下载方式在...
通过以上步骤,我们可以构建一个功能完备的Java多线程断点续传下载程序。这个项目不仅可以帮助初学者理解多线程和网络编程的基本概念,也可以作为实际项目开发中的一个参考模板。对于想要深入研究Java网络编程和并发...
Java多线程断点续传下载程序是一种高级的软件实现技术,它结合了Java的并发处理能力和文件传输的策略,以提高下载效率和用户体验。在这个项目中,我们主要关注两个核心概念:多线程和断点续传。 首先,多线程是Java...
这篇文档中,annegu展示了一个简单的Java实现,通过HTTP协议进行多线程下载并支持断点续传的程序。 首先,程序的核心功能是分块下载大文件,每个线程负责下载文件的一部分。为了实现这一功能,程序首先从URL中解析...
Java是一种广泛使用的编程语言,尤其在开发服务器端应用和网络...通过阅读和分析这些源代码,你可以更深入地理解Java多线程和断点续传技术的具体实现细节。这个项目对于学习Java网络编程和多线程控制是很好的实践案例。
在IT领域,多线程和断点续传技术在下载管理器中被广泛使用,以提高文件下载的效率和用户体验。下面将详细讲解这两个概念及其在Java中的实现。 首先,多线程是并发编程的核心概念,它允许一个程序同时执行多个任务。...
在Android开发中,多线程断点续传下载是一项重要的技术,它允许用户在中断下载后,从上次停止的地方继续下载,提高了用户体验。这个技术主要涉及到网络编程、多线程处理以及文件操作等多个方面。接下来,我们将深入...
Java 断点续传与多线程下载是网络编程中两个重要的技术,它们在处理大文件下载时尤其有用。断点续传允许用户在下载中断后从上次停止的地方继续,而多线程下载则通过同时从服务器获取多个数据块来提高下载速度。接...
标题中的“多线程断点续传程序 Java版本”指的是一个使用Java编程语言实现的软件,该软件具备多线程下载功能,并且支持在下载中断后从上次停止的位置继续下载,即断点续传。这样的特性对于大文件下载或者网络环境不...
总的来说,Java多线程与线程安全实践对于理解和实现基于HTTP协议的断点续传功能至关重要。通过合理地运用多线程、同步机制和原子变量,可以构建出高效、安全的并发应用程序。这个实践项目会深入讲解这些概念,并提供...
"多线程断点续传工具类"通常指的是一个Java类,该类设计用于实现文件传输时的多线程功能,并且能够从上次中断的地方继续下载或上传,这在大文件传输中非常有用,因为它可以提高速度并避免因网络问题导致的传输失败。...
下面将详细探讨如何在Java中实现多线程断点续传。 首先,我们需要理解多线程的概念。在Java中,我们可以使用`Thread`类或`Runnable`接口来创建线程。当一个线程被创建后,它可以与主线程并行运行,每个线程都有自己...
本文将深入解析这一技术,并基于给出的【标题】"多线程多任务断点续传下载"和【描述】中的信息进行详细阐述。 1. **多线程下载**:多线程下载是指在一个下载任务中,同时启动多个线程(通常每个线程负责下载文件的...
综上,这个Java多线程断点续传的实现涉及了Java多线程编程的核心概念,包括线程创建、同步、通信和状态管理,以及网络I/O和文件操作。同时,为了提供用户友好的界面,还涉及到UI更新和进度反馈的实现。
这个实例展示了如何使用Java实现FTP功能,特别是多线程、批量文件传输以及断点续传。这些特性对于大文件的上传和下载尤其重要,可以显著提高效率并确保文件传输的可靠性。 1. **多线程**: 在Java中,多线程是通过...
在本项目“Java多线程与线程安全实践-基于Http协议的断点续传”中,我们将深入探讨如何利用Java的多线程机制实现HTTP协议下的断点续传功能,这对于大文件下载或上传的场景尤为实用。 断点续传是一种允许用户在中断...