«

Android多线程断点下载之多线程下载原理

时间:2024-3-2 17:13     作者:韩俊     分类: Android


1.为什么要使用多线程下载?

使用多线程下载文件可以更快完成文件的下载,多线程下载文件之所以快,是因为其抢占的服务器资源多,例如:假设服务器同时最多服务100个用户,在服务器中一条线程对应一个用户,100条线程在计算机中并非并发执行,而是由cpu划分时间片轮流执行,如果A应用使用了99条线程下载文件,那么相当于占用99个用户资源,假设一秒内cpu分配给每条线程的平均时间是10ms,A应用在服务器中一秒内就得到了990ms的执行时间,而其他应用在一秒只有10ms的执行时间,就如同一个水龙头,每秒出水量相等的情况下,放水990毫秒的水.肯定比放10毫秒的水要多.





2.多线程下载的实现过程:

1)首先得到下载文件的长度,然后设置到本地文件

HttpURLConnection.getContentLength();

RandomAccessFile file = new RandomAccessFile("vedio1.avi","nw");

file.setLength(filesize);//设置文件的长度





2)根据文件长度和线程数计算每条线程下载的数据长度和下载位置.如,文件长度为6M,线程为3个,那么每条线程下载的数据为2M.每条线程开始下载的位置如下:

int blocksize = length / threadcount 即:6M / 3Thread = 2M/Thread

线程1 下载2个byte: 0blocksize ~ 1locaksize-1

线程2 下载2个byte: 1blocksize ~ 2blocksize-1

线程3 下载2个byte: 2*blocksize ~ length-1





3)使用Http协议的Range头字段指定每条线程从文件的什么位置开始下载,下载到什么位置为止,例如指定

HttpURLConnection.setRequestProperty("Range","bytes=2097152-4194303");





4)保存文件,使用RandomAccessFile类指定每条线程从本地文件的什么位置开始写入数据

RandomAccessFile threadFile = new RandomAccessFile("video1.avi","nw");

threadfile.seek(2097152); //从文件的什么位置开始写入数据

下图为原理图:


准备工作:

搭建Tomcat服务器,放置需要下载的资源,如下图所示的video1.avi



确认资源文件的大小为23,250,432byte


开始编写代码了.以下代码都是j2se的知识,因此可以直接创建一个j2se的工程来演示

<span style="font-family:Courier New;font-size:14px;">import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
/**
 * 
 * @author Chenys
 * 多线程下载文件
 *
 */
public class MultiThreadDownload {
    /**
     * 线程的数量
     */
    private static int threadCount = 3;
    /**
     * 每个下载区块的大小
     */
    private static long blocksize;

    public static void main(String[] args) throws IOException {
        // 服务器文件的路径
        String path = "http://192.168.0.102:8080/video1.avi";
        URL url = new URL(path);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setConnectTimeout(5000);
        int code = conn.getResponseCode();
        if (code == 200) {
            // 得到服务器端返回的文件大小,单位byte,字节
            long size = conn.getContentLength();
            System.out.println("服务器文件的大小:" + size);
            // 计算每个blocksize的大小
            blocksize = size / threadCount;
            /**
             * 1.首先在本地创建一个文件大小跟服务器一模一样的空白文件,RandomAccessFile类的实例支持对随机访问文件的读取和写入
             * 参数1:目标文件 参数2:打开该文件的访问模式,"r" 以只读方式打开 ,"rw" 打开以便读取和写入
             */
            File file = new File("temp.avi");
            RandomAccessFile raf = new RandomAccessFile(file, "rw");
            raf.setLength(size);// 设置文件的大小
            // 2.开启若干个子线程,分别下载对应的资源
            for (int i = 1; i <= threadCount; i++) {

                long startIndex = (i - 1) * blocksize; // 由于服务端下载文件是从0开始的
                long endIndex = i * blocksize - 1;
                if (i == threadCount) {
                    // 最后一个线程
                    endIndex = size - 1;
                }
                System.out.println("开启线程:" + i + "下载的位置" + startIndex + "~"+ endIndex);
                new DownloadThread(path, i, startIndex, endIndex).start();
            }
        }
        conn.disconnect();
    }
    /**
     * 自定义下载线程
     * @author Chenys
     *
     */
    private static class DownloadThread extends Thread {
        private int threadId; // 线程id
        private long startIndex; // 开始下载的位置
        private long endIndex; // 结束下载的位置
        private String path;

        public DownloadThread(String path, int threadId, long startIndex,
                long endIndex) {
            this.path = path;
            this.threadId = threadId;
            this.startIndex = startIndex;
            this.endIndex = endIndex;
        }

        public void run() {
            try {
                URL url = new URL(path);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(5000);
                //设置http协议请求头: 指定每条线程从文件的什么位置开始下载,下载到什么位置为止
                conn.setRequestProperty("Range", "bytes=" + startIndex + "-"+ endIndex);
                int code = conn.getResponseCode();
                System.out.println("服务器返回码code=" + code);// 如果是下载一部分资源,那么返回码是206
                InputStream is = conn.getInputStream();
                File file = new File("temp.avi");
                RandomAccessFile raf = new RandomAccessFile(file, "rw");
                // 指定文件的下载起始位置
                raf.seek(startIndex);
                System.out.println("第" + threadId + "个线程写文件的开始位置"
                        + String.valueOf(startIndex));
                // 开始保存文件
                int len = 0;
                byte[] buff = new byte[1024];
                while ((len = is.read(buff)) != -1) {
                    raf.write(buff, 0, len);
                }
                is.close();
                raf.close();
                System.out.println("线程" + threadId + "下载完毕了");

            } catch (MalformedURLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (ProtocolException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

</span>

运行结果如下:



刷新工程目录,可以发现刚刚下载的temp.avi文件



打开工程的根目录,确认下载的文件大小是否和服务器上的文件大小一致




标签: android

热门推荐