Java实现 多线程的下载器

理论基础:

 1.从服务器获取文件大小,本地创建一个和服务器一样大的临时文件 
 2. 计算分配几个线程去下载服务器上资源,每个线程知道写入本地文件位置,RandomAccessFile
 100M  文件 
线程1下载位置 0-33M, 写入位置 0 
线程2下载位置 33-66M , 写入位置33M
线程3下载位置 66—100M , 写入位置 66 M
 3. 开启多个3个线程,每一个线程从对应位置开始下载写入到本地位置,同时创建临时文件

 4. 下载中断,开始下载,读取临时文件大小,根据临时文件设置range头
 4. 所有线程下载完毕,下载完毕,RandomAccessFile 写入完毕,删除临时文件
 注意点:
 在HTTP协议中可以通过Range头字段指定每条线程从文件什么位置开始下载,
 下载到什么位置即可,
 比如指定从文件2M位置开始下载, 下载到位置(4M-1 byte)为止 

RandomAccessFile  可以指定写入起始位置

如何实现断点下载:
   为了实现断点下载,需要创建临时文件,第二次下载的时候读取临时文件大小   

====================================================================================

package download3;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;


public class MultiDownloader {
     // 线程的数量
    private static int threadCount = 3;
    // 正在运行的线程的个数 
    private static int runningThreadCount ;
    public static void main(String[] args) throws Exception {
        //采用多个线程把服务器的资源下载下来
        String path = "http://192.168.168.157:8080/qq.txt";//推荐使用exe文件
        URL url  = new URL(path);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setConnectTimeout(5000);
        int code = conn.getResponseCode();
        if(code == 200){
            int length = conn.getContentLength();
            System.out.println("服务器文件的大小为:"+length);
            //1. 确定文件大小,在本地创建一个大小和服务器一模一样的空文件。RandomAccessFile
            RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rw");
            raf.setLength(length);
            raf.close();
            conn.disconnect();  // 断开http
            //2.等份服务器的资源为若干份,让每个子线程下载自己的部分。
            int blocksize = length/threadCount;
            runningThreadCount = threadCount;
            for(int threadid =0;threadid<threadCount;threadid++){
                int startIndex = threadid*blocksize;
                int endIndex = (threadid+1)*(blocksize)-1;
                //特殊情况
                if(threadid==(threadCount-1)){
                    //最后一个线程。结束位置为文件总长度-1
                    endIndex = length -1;
                }

             //   3. 启动线程开始下载
                new DownloadThread(threadid, startIndex, endIndex, path).start();
            }
        }
    }
  //    下载资源的线程
    public static class DownloadThread extends Thread{
        private int threadid;
        private int startindex;
        private int endindex;
        private String path;
        public DownloadThread(int threadid, int startindex, int endindex,
                String path) {
            this.threadid = threadid;
            this.startindex = startindex;
            this.endindex = endindex;
            this.path = path;
        }
        @Override
        public void run() {
            System.out.println("线程id:"+threadid+"理论下载位置:"+startindex+"~"+endindex);
            try {
                URL url = new URL(path);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(5000);
                //查看文件里面是否记录有当前线程的下载开始位置,实现断点下载
                File infofile = new File(threadCount+getFileName(path)+threadid+".txt");
                if(infofile.exists()&&infofile.length()>0){
                    FileInputStream fis = new FileInputStream(infofile);
                    BufferedReader br = new BufferedReader(new InputStreamReader(fis));
                    String newstartindex = br.readLine();   

//如果本地文件存在计算开始下载位置
                    conn.setRequestProperty("Range", "bytes="+newstartindex+"-"+endindex);
                //    System.out.println("线程id:"+threadid+"真实的下载位置:"+newstartindex+"~"+endindex);
                    startindex = Integer.parseInt(newstartindex);
                    fis.close();//记得释放文件的引用
                }else{
                //4.  不是下载整个文件,而是下载文件的一部分。 告诉服务器 下载的资源就是一部分
                //HttpURLConnection.setRequestProperty("Range", "bytes=2097152-4194303");
                    conn.setRequestProperty("Range", "bytes="+startindex+"-"+endindex);
                    System.out.println("线程id:"+threadid+"真实的下载位置:"+startindex+"~"+endindex);
                }
                int code = conn.getResponseCode();

//200 OK  206 请求部分数据成功,下载部分文件服务器返回206
                System.out.println("返回状态码:code"+ code );
                if(code == 206){
                    //当前的线程 就应用下载这一部分的数据。开始下载数据
                    InputStream is = conn.getInputStream();
                    RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rw");
                    raf.seek(startindex);

//注意:不同线程存文件的开始位置是不相同的,要从自己对应的位置存文件
                    byte[] buffer = new byte[1024*4];
                    int len = -1;
                    int total = 0;//代表当前线程下载的总大小
                    while(( len = is.read(buffer))!=-1){

// 写入 本地创建的文件 
                        raf.write(buffer, 0, len);
                        total+=len;
                        int currentposition = startindex+total;//当前线程下载的位置。
                        System.err.println("线程id:"+threadid+  " 下载到:"+ currentposition);

// 为了确保断点下载,写入临时文件数据
                        File file = new File(threadCount+getFileName(path)+threadid+".txt");
                        //确保了每次循环都会把进度写到底层存储设备里面。
                        /**  r: 文件只读方式打开
                        *   rw: 文件读写方式打开
                        * rws: 文件读写方式打开,对文件内容或者元数据的每个更新都同步到写入底层设备
                        rwd:文件读写方式打开, 对文件内容的每个更新都写入到底层设备,断电了数据也立刻写入
                        **/
                        RandomAccessFile rafos = new RandomAccessFile(file, "rwd");
                        //把当前线程的位置信息写入到一个文件里面
                        rafos.write(String.valueOf(currentposition).getBytes());
                        rafos.close();//数据并不是直接保存到底层的存储设备里面,保存到缓存,缓存空间满了,数据会同步到底层设备。
                    }
                    is.close();
                    raf.close();
                    System.out.println("线程:"+threadid+"下载完毕了。");
                    synchronized (MultiDownloader.class) {
                        runningThreadCount--;
                        if(runningThreadCount<=0){
                            System.out.println("线程全部下载完毕了。");
                            for(int i=0;i<threadCount;i++){
                                File f = new File(threadCount+getFileName(path)+i+".txt");

// 删除临时文件
                                f.delete();
                            }
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 获取文件名称
     * @param path
     * @return
     */
    public static String getFileName(String path){
        int startindex = path.lastIndexOf("/")+1;
        return path.substring(startindex);
    }
}

====================================================================================

完整源码:

tomcat配置路径:D:\web\Apache Tomcat 7.0.41\webapps\ROOT\qq.txt   启动tomcat

package download3;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * 多线程的下载器 javase代码
 *
 */
public class MultiDownloader {
	/**
	 * 线程的数量
	 */
	private static int threadCount = 3;
	
	/**
	 * 正在运行的线程的个数
	 */
	private static int runningThreadCount ;
	
	public static void main(String[] args) throws Exception {
		//采用多个线程把服务器的资源下载下来。
		String path = "http://192.168.168.157:8080/qq.txt";//推荐使用exe文件
		URL url  = new URL(path);
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setRequestMethod("GET");
		conn.setConnectTimeout(5000);
		int code = conn.getResponseCode();
		if(code == 200){
			int length = conn.getContentLength();
			System.out.println("服务器文件的大小为:"+length);
			//1.在本地创建一个大小和服务器一模一样的空文件。RandomAccessFile
			RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rw");
			raf.setLength(length);
			raf.close();
			conn.disconnect();  // 断开http
			//2.等份服务器的资源为若干份,让每个子线程下载自己的部分。
			int blocksize = length/threadCount;
			runningThreadCount = threadCount;
			for(int threadid =0;threadid<threadCount;threadid++){
				int startIndex = threadid*blocksize;
				int endIndex = (threadid+1)*(blocksize)-1;
				//特殊情况
				if(threadid==(threadCount-1)){
					//最后一个线程。结束位置为文件总长度-1
					endIndex = length -1;
				}
				new DownloadThread(threadid, startIndex, endIndex, path).start();
			}
		}
	}
	/**
	 * 下载资源的线程
	 *
	 */
	public static class DownloadThread extends Thread{
		private int threadid;
		private int startindex;
		private int endindex;
		private String path;
		public DownloadThread(int threadid, int startindex, int endindex,
				String path) {
			this.threadid = threadid;
			this.startindex = startindex;
			this.endindex = endindex;
			this.path = path;
		}
		@Override
		public void run() {
			System.out.println("线程id:"+threadid+"理论下载位置:"+startindex+"~"+endindex);
			try {
				URL url = new URL(path);
				HttpURLConnection conn = (HttpURLConnection) url.openConnection();
				conn.setRequestMethod("GET");
				conn.setConnectTimeout(5000);
				//查看文件里面是否记录有当前线程的下载开始位置。
				File infofile = new File(threadCount+getFileName(path)+threadid+".txt");
				if(infofile.exists()&&infofile.length()>0){
					FileInputStream fis = new FileInputStream(infofile);
					BufferedReader br = new BufferedReader(new InputStreamReader(fis));
					String newstartindex = br.readLine();
					conn.setRequestProperty("Range", "bytes="+newstartindex+"-"+endindex);
				//	System.out.println("线程id:"+threadid+"真实的下载位置:"+newstartindex+"~"+endindex);
					startindex = Integer.parseInt(newstartindex);
					fis.close();//记得释放文件的引用
				}else{
				//不是下载整个文件,而是下载文件的一部分。 告诉服务器 下载的资源就是一部分
				//HttpURLConnection.setRequestProperty("Range", "bytes=2097152-4194303");
					conn.setRequestProperty("Range", "bytes="+startindex+"-"+endindex);
					System.out.println("线程id:"+threadid+"真实的下载位置:"+startindex+"~"+endindex);
				}
				int code = conn.getResponseCode();//200 OK  206 请求部分数据成功
				System.out.println("返回状态码:code"+ code );
				if(code == 206){
					//当前的线程 就应用下载这一部分的数据。
					InputStream is = conn.getInputStream();
					RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rw");
					raf.seek(startindex);//注意:不同线程存文件的开始位置是不相同的,要从自己对应的位置存文件
					byte[] buffer = new byte[1024*4];
					int len = -1;
					int total = 0;//代表当前线程下载的总大小
					while(( len = is.read(buffer))!=-1){
						raf.write(buffer, 0, len);
						total+=len;
						int currentposition = startindex+total;//当前线程下载的位置。
						System.err.println("线程id:"+threadid+  " 下载到:"+ currentposition);
						File file = new File(threadCount+getFileName(path)+threadid+".txt");
						//确保了每次循环都会把进度写到底层存储设备里面。
						/**  r: 文件只读方式打开
						*   rw: 文件读写方式打开
						* rws: 文件读写方式打开,对文件内容或者元数据的每个更新都同步到写入底层设备
						rwd:文件读写方式打开, 对文件内容的每个更新都写入到底层设备,断电了数据也立刻写入
						**/
						RandomAccessFile rafos = new RandomAccessFile(file, "rwd");
						//把当前线程的位置信息写入到一个文件里面
						rafos.write(String.valueOf(currentposition).getBytes());
						rafos.close();//数据并不是直接保存到底层的存储设备里面,保存到缓存,缓存空间满了,数据会同步到底层设备。
					}
					is.close();
					raf.close();
					System.out.println("线程:"+threadid+"下载完毕了。");
					synchronized (MultiDownloader.class) {
						runningThreadCount--;
						if(runningThreadCount<=0){
							System.out.println("线程全部下载完毕了。");
							for(int i=0;i<threadCount;i++){
								File f = new File(threadCount+getFileName(path)+i+".txt");
								f.delete();
							}
						}
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	/**
	 * 获取文件名称
	 * @param path
	 * @return
	 */
	public static String getFileName(String path){
		int startindex = path.lastIndexOf("/")+1;
		return path.substring(startindex);
	}
}
发布了113 篇原创文章 · 获赞 125 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/dreams_deng/article/details/105269489