Java--使用多线程下载,断点续传技术原理(RandomAccessFile)

一.基础知识

1.什么是线程?什么是进程?它们之间的关系?

可以参考之前的一篇文章:java核心知识点学习----并发和并行的区别,进程和线程的区别,如何创建线程和线程的四种状态,什么是线程计时器

简单说一个进程可以由多个线程组成,一个操作系统可以多个进程,它们都是可以同时进行工作的.

2.什么是下载?如何多线程进行下载?如何断点续传?

广义上说,凡是在屏幕上看到的不属于本地计算机上的内容,皆是通过"下载"得来。狭义上人们只认为那些自定义了下载文件的本地磁盘存储位置的操作才是"下载";。

WEB下载方式分为HTTP与FTP两种类型,它们分别是Hyper Text Transportation Protocol(超文本传输协议)与File Transportation Protocol(文件传输协议)的缩写,它们是计算机之间交换数据的方式,也是两种最经典的下载方式,该下载方式原理非常简单,就是用户两种规则(协议)和提供文件的服务器取得联系并将文件搬到自己的计算机中来,从而实现下载的功能。
多线程下载,即是一个文件能过多个线程进行下载;而断点续传说的是当一个文件下载到一半时突然由于某个原因下载中断了,比如突然电脑关机了,那么当再开机时已经下载到一半的文件不需要重头开始,而是接着下载;其原理很简单:首先,下载中断时记住上一个时点下载的位置,然后接着这个位置继续下载,这个继续下载可以是人手工触发的也可以是程序运行时自动识别进行下载的.

3.什么是RandomAccessFile?

RandomAccessFile的唯一父类是Object,与其他流父类不同。是用来访问那些保存数据记录的文件的,这样你就可以用seek( )方法来访问记录,并进行读写了。这些记录的大小不必相同;但是其大小和位置必须是可知的。

RandomAccessFile的工作方式是,把DataInputStream和DataOutputStream粘起来,再加上它自己的一些方法,比如定位用的getFilePointer( ),在文件里移动用的seek( ),以及判断文件大小的length( )。此外,它的构造函数还要一个表示以只读方式("r"),还是以读写方式("rw")打开文件的参数 (和C的fopen( )一模一样)。它不支持只写文件,从这一点上看,假如RandomAccessFile继承了DataInputStream,它也许会干得更好。

所以,本例中是利用RandomAccessFile的seek记住上次的访问记录,然后接着上次的访问进行下载的.

RandomAccess直译过来是随机访问,这样理解很容易造成困扰,即然是随机的,那么又怎么来控制进度呢?

由Random这个单词的示意:

random
[ ‘rænd?m ]
adj. 随意的,任意,随机的,随机挑选的,任意随机的,胡乱任意的,随机性,任意的,胡乱的,随便的;(话等)信口乱说的;(人等)偶然碰到的,随意选择的,随便,无关紧要的,漫不经心的

可以看到其还有任意的意思,这就表明可以访问文件的任意位置,这样就能解释为何可以断点续传了.

二.程序实现

这里以tomcat的下载为例:http://tomcat.apache.org/download-70.cgi#7.0.54 (tomcat目前最新的版本是7.0.54,2014-07-02)

F12打开chrome的WebDeveloper NetWork窗口,然后点击下载,如下图所示:

这里需要注意的是Request Headers里的内容,如RequestMethod,Accept,Accept-Language,Connection等,这里在发出请求时需要将这些东西带上,这里找到下载tomcat 7.0.54的下载链接:http://mirrors.cnnic.cn/apache/tomcat/tomcat-7/v7.0.54/bin/apache-tomcat-7.0.54.zip

实现代码:DownUtil.java


package com.amos.tool;

import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * Created by amosli on 14-7-2.
 */
public class DownUtil
{
    // 定义下载资源的路径
    private String path;
    // 指定所下载的文件的保存位置
    private String targetFile;
    // 定义需要使用多少线程下载资源
    private int threadNum;
    // 定义下载的线程对象
    private DownThread[] threads;
    // 定义下载的文件的总大小
    private int fileSize;

    public DownUtil(String path, String targetFile, int threadNum)
    {
        this.path = path;
        this.threadNum = threadNum;
        // 初始化threads数组
        threads = new DownThread[threadNum];
        this.targetFile = targetFile;
    }

    public void download() throws Exception
    {
        URL url = new URL(path);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setConnectTimeout(5 * 1000);
        conn.setRequestMethod("GET");
        conn.setRequestProperty(
                "Accept",
                "image/gif, image/jpeg, image/pjpeg, image/pjpeg, "
                        + "application/x-shockwave-flash, application/xaml+xml, "
                        + "application/vnd.ms-xpsdocument, application/x-ms-xbap, "
                        + "application/x-ms-application, application/vnd.ms-excel, "
                        + "application/vnd.ms-powerpoint, application/msword, */*");
        conn.setRequestProperty("Accept-Language", "zh-CN");
        conn.setRequestProperty("Charset", "UTF-8");
        conn.setRequestProperty("Connection", "Keep-Alive");
        // 得到文件大小
        fileSize = conn.getContentLength();
        conn.disconnect();
        int currentPartSize = fileSize / threadNum + 1;//这里不必一定要加1,不加1也可以
        RandomAccessFile file = new RandomAccessFile(targetFile, "rw");
        // 设置本地文件的大小
        file.setLength(fileSize);
        file.close();
        for (int i = 0; i < threadNum; i++)
        {
            // 计算每条线程的下载的开始位置
            int startPos = i * currentPartSize;
            // 每个线程使用一个RandomAccessFile进行下载
            RandomAccessFile currentPart = new RandomAccessFile(targetFile, "rw");
            // 定位该线程的下载位置
            currentPart.seek(startPos);
            // 创建下载线程
            threads[i] = new DownThread(startPos, currentPartSize, currentPart);
            // 启动下载线程
            threads[i].start();
        }
    }

    // 获取下载的完成百分比
    public double getCompleteRate()
    {
        // 统计多条线程已经下载的总大小
        int sumSize = 0;
        for (int i = 0; i < threadNum; i++)
        {
            sumSize += threads[i].length;
        }
        // 返回已经完成的百分比
        return sumSize * 1.0 / fileSize;
    }

    private class DownThread extends Thread
    {
        // 当前线程的下载位置
        private int startPos;
        // 定义当前线程负责下载的文件大小
        private int currentPartSize;
        // 当前线程需要下载的文件块
        private RandomAccessFile currentPart;
        // 定义已经该线程已下载的字节数
        public int length;

        public DownThread(int startPos, int currentPartSize,RandomAccessFile currentPart)
        {
            this.startPos = startPos;
            this.currentPartSize = currentPartSize;
            this.currentPart = currentPart;
        }

        @Override
        public void run()
        {
            try
            {
                URL url = new URL(path);
                HttpURLConnection conn = (HttpURLConnection)url.openConnection();
                conn.setConnectTimeout(5 * 1000);
                conn.setRequestMethod("GET");
                conn.setRequestProperty(
                        "Accept",
                        "image/gif, image/jpeg, image/pjpeg, image/pjpeg, "
                                + "application/x-shockwave-flash, application/xaml+xml, "
                                + "application/vnd.ms-xpsdocument, application/x-ms-xbap, "
                                + "application/x-ms-application, application/vnd.ms-excel, "
                                + "application/vnd.ms-powerpoint, application/msword, */*");
                conn.setRequestProperty("Accept-Language", "zh-CN");
                conn.setRequestProperty("Charset", "UTF-8");
                InputStream inStream = conn.getInputStream();
                // 跳过startPos个字节,表明该线程只下载自己负责哪部分文件。
                inStream.skip(this.startPos);
                byte[] buffer = new byte[1024];
                int hasRead = 0;
                // 读取网络数据,并写入本地文件
                while (length < currentPartSize
                        && (hasRead = inStream.read(buffer)) != -1)
                {
                    currentPart.write(buffer, 0, hasRead);
                    // 累计该线程下载的总大小
                    length += hasRead;
                }
                currentPart.close();
                inStream.close();
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }
}

测试:DownUtilTest.java

package com.amos;

import com.amos.tool.DownUtil;
import org.omg.PortableServer.THREAD_POLICY_ID;

/**
 * Created by amosli on 14-7-2.
 */
public class DownUtilTest {

    public static void main(String args[]) throws Exception {
        final DownUtil downUtil = new DownUtil("http://mirrors.cnnic.cn/apache/tomcat/tomcat-7/v7.0.54/bin/apache-tomcat-7.0.54.zip", "tomcat-7.0.54.zip", 3);

        downUtil.download();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while(downUtil.getCompleteRate()<1){
                    System.out.println("已完成:"+downUtil.getCompleteRate());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }
        }).start();
    }

}

结果:

下载我的头像:

  final com.amos.DownUtil downUtil = new com.amos.DownUtil("http://pic.cnitblog.com/avatar/534352/20131215160918.png", "amosli.png", 2);

Java--使用多线程下载,断点续传技术原理(RandomAccessFile)

时间: 2024-11-07 01:39:00

Java--使用多线程下载,断点续传技术原理(RandomAccessFile)的相关文章

java实现多线程下载

本篇博客可认为是对 使用java实现http多线程下载 一文的再次解读. 首先,从宏观来说 java实现多线程下载这个功能的实现由以下几部分组成: 1 建立多个线程去分别下载文件的一部分. 2 将多个线程下载的文件(还在内存中),写入硬盘中的一个文件. 3 断点续传 GET /Path/FileName HTTP/1.0 Host: www.server.com:80 Accept: */* User-Agent: GeneralDownloadApplication Connection: c

Android多线程下载断点续传

先上图看卡结果: GITHUB:Android多线程下载断点续传 如图所示点击下载就开始下载,点击停止就会停止再次点击下载就会接着下载了. 设计思路是这样的: 首先通过广播将下载信息传递给DownService,DownService根据文件URL获取文件大小,再通过DownTask将下载任务分配,并且通过广播当点击停止下载时将下载进度保存在数据库中,当点击开始下载时再从数据库中获取到保存的进度,继续下载. 代码结构: 核心类是 DownLoadService,java 和DownTask.ja

多线程下载 断点续传

package wml.dl;import java.io.BufferedInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.InputStream;import java.net.HttpURLConnection;import java.util.Properties; import wml.dl.io.BufferedRandomOutputStream; *

Java实现多线程下载、断点续传

开三个线程下载,代码: package demo; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; public class MutilDownLoad { // 放在Tomcat下的一个文件 static String path = "http://192.168.87.1:8080/lol.exe"

图解:HTTP 范围请求,助力断点续传、多线程下载的核心原理

题图:by Charles Loyer 一.序 Hi,大家好,我是承香墨影! HTTP 协议在网络知识中占据了重要的地位,HTTP 协议最基础的就是请求和响应的报文,而报文又是由报文头(Header)和实体组成.大多数 HTTP 协议的使用方式,都是依赖设置不同的 HTTP 请求/响应 的 Header 来实现的. 本系列<实用 HTTP>就抛开常规的 Header 讲解式的表述方式,从实际问题出发,来分析这些 HTTP 协议的使用方式,到底是为了解决什么问题?同时讲解它是如何设计的和它实现原

Java之多线程下载

多线程下载的原理在于,每个线程下载文件的一部分,每个线程将自己下载的一部分写入文件中它应该的位置,所有线程下载完成时,文件下载完成.其关键点在于:RandomAccessFile.seek(beginIndex)和URLConnection.setRequestProperty("Range", "bytes=" + beginIndex + "-" + endIndex). 转载请注明原创地址,请尊重原创,谢谢. 代码如下,以下代码copy后可

Java实现多线程下载 URL以及URLConnection

主线程: public class MultiThreadDown { public static void main(String[] args) throws Exception{ //初始化Downutil对象 final DownUtil downutil = new DownUtil("http://www.crazyit.org/" +"attachment.PHP?aid=MTY0NXxjNjBIYznjN3wxMzE1NTQ2MjU5fGNho" +

java中多线程下载

package com.download; import java.io.File; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; public class MutileThreadDown { private static int blockCount=3; private static int blockS

Java多线程下载器FileDownloader(支持断点续传、代理等功能)

前言 在我的任务清单中,很早就有了一个文件下载器,但一直忙着没空去写.最近刚好放假,便抽了些时间完成了下文中的这个下载器. 介绍 同样的,还是先上效果图吧. Jar包地址位于 FileDownloader 目前实现的主要功能有: 多线程下载 断点续传 自定义头部等 即将完成的包括: 添加代理功能 ... 感觉做了回标题党,代理功能由于时间关系,将在下次更新加入. 关于设置代理,我这篇文章 Java实现Ip代理池 中有具体的设置方法. 另外除了这个代理功能,我也实在不知道下载器能加些啥功能了..