java多线程断点下载

最近闲的比较蛋疼,原本软件计划招20个人的,现在14个人的团队都是一半在打酱油,这跟全国经济形势有关,我们也没太大办法,所以最近是啥都看看。昨天晚上看了一个多线程断点下载,今天就用Java实现了一遍。
基本思路:
1、通过HttpURLConnection获取网络资源,得到资源大小等一些信息,
2、在本地创建一个和通过网络获取的资源同样大小的文件(目的是为了存放下载的资源)
3、分配每个线程下载文件的开始位置和结束位置(下载时记录每个线程下载的起始位置,方便停止后能继续下载)
4、开启线程去下载。

下面开始实现

private static int threadCount = 3;//开启3个线程
    private static int blockSize = 0;//每个线程下载的大小
    private static int runningTrheadCount = 0;//当前运行的线程数
    private static String path = "http://sw.bos.baidu.com/sw-search-sp/software/09d9bc67eab07/QQ_8.7.19075.0_setup.exe";
    private static String filename ="QQ_8.7.19075.0_setup.exe";
    /**
     * @param args
     */
    public static void main(String[] args) {

        try{
            //1.请求url地址获取服务端资源的大小
            URL url = new URL(path);
            HttpURLConnection openConnection = (HttpURLConnection) url.openConnection();
            openConnection.setRequestMethod("GET");
            openConnection.setConnectTimeout(5*1000);

            int code = openConnection.getResponseCode();
            if(code == 200){
                //获取资源的大小
                int filelength = openConnection.getContentLength();
                if(filelength==-1){
                    filelength=1024*1024*60;
                }
                System.out.println("filelength="+filelength);
                //2.在本地创建一个与服务端资源同样大小的一个文件(占位)
                RandomAccessFile randomAccessFile = new RandomAccessFile(filename, "rw");
                randomAccessFile.setLength(filelength);//设置随机访问文件的大小

                //3.要分配每个线程下载文件的开始位置和结束位置。
                blockSize = filelength/threadCount;//计算出每个线程理论下载大小
                for(int threadId =0 ;threadId < threadCount;threadId++){
                    int startIndex =  threadId * blockSize;//计算每个线程下载的开始位置
                    int endIndex = (threadId+1)*blockSize -1;//计算每个线程下载的结束位置
                    //如果是最后一个线程,结束位置需要单独计算
                    if(threadId == threadCount-1){
                        endIndex = filelength -1;
                    }

                    //4.开启线程去执行下载
                    new DownloadThread(threadId, startIndex, endIndex).start();


                }


            }


        }catch (Exception e) {
            e.printStackTrace();
        }

     }

上面代码就是按照基本思路来写的 首先通过请求url获取网络的资源,并设置为get请求,超时时间为5S 正常连接后获取资源的大小,这里要注意一下,在百度里面输入”qq下载“ 点立即下载

1.png

得到的下载连接为https://www.baidu.com/link?url=V4gOxFgauy-GHJGTah_Os4obVwMcRqSdtCT3zL6Lxyzg5tMfIMqK7OW_WIr8cUasrc-h9fDypXCPa2EYE8e1242fYJshFLA1BYdPKIu2KWy&wd=&eqid=b7133a01000210550000000658008fce
用chrome带的抓包工具发现

2.png
3.png

会有三个连接而我们用直接点击下载得到的连接其中没有Content-Length这一项,因此当调用openConnection.getContentLength();时返回值为-1, 看网上都说设置setRequestProperty(“Accept-Encoding”, “identity”); 就可以了,可是试了下并没什么卵用 我不是搞网络的出身,所以搞了个简单的方法,用下面的地址获取资源。就是抓的包最下面一个的URL地址
http://sw.bos.baidu.com/sw-search-sp/software/84b5fcf50a3de/QQ_8.7.19083.0_setup.exe

现在可以正确的获取网络资源的大小了。
继续 创建一个RandomAccessFile类型的文件,之所以要用这个类创建文件,是为了为后面的断点续传做准备的,看它名字也知道它 它可以随机的开始写入文件的位置,这个类功能非常强大,这里只用了它的randomAccessFile.seek(lastPostion)方法。
接着 就该分配线程 并确定每个线程的开始和结束位置了 这个没什么难的,就是确定用几个线程去下载,每个线程下载多少,随你怎么分配了,只要他们连起来还是资源的大小,并且是连续的就行。
接下来就是开启线程去下载了。

public static class DownloadThread  extends Thread{


        private int threadId;
        private int startIndex;
        private int endIndex;
        private int lastPostion;
        public DownloadThread(int threadId,int startIndex,int endIndex){
            this.threadId = threadId;
            this.startIndex = startIndex;
            this.endIndex = endIndex;
        }

        @Override
        public void run() {
            synchronized (DownloadThread.class) {
                
                runningTrheadCount = runningTrheadCount +1;//开启一线程,线程数加1
            }
                
            //分段请求网络连接,分段保存文件到本地
            try{
                URL url = new URL(path);
                HttpURLConnection openConnection = (HttpURLConnection) url.openConnection();
                openConnection.setRequestMethod("GET");
                openConnection.setConnectTimeout(5*1000);
                
                
                System.out.println("理论上下载:  线程:"+threadId+",开始位置:"+startIndex+";结束位置:"+endIndex);
                
                //读取上次下载结束的位置,本次从这个位置开始直接下载。
                File file2 = new File(threadId+".txt");
                if(file2.exists()){
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file2)));
                    String lastPostion_str = bufferedReader.readLine();
                    lastPostion = Integer.parseInt(lastPostion_str);//读取文件获取上次下载的位置
                    
                    //设置分段下载的头信息。  Range:做分段数据请求用的。
                    openConnection.setRequestProperty("Range", "bytes="+lastPostion+"-"+endIndex);//bytes:0-500:请求服务器资源中0-500之间的字节信息  501-1000:
                    System.out.println("实际下载1:  线程:"+threadId+",开始位置:"+lastPostion+";结束位置:"+endIndex);
                    bufferedReader.close();
                }else{
                    
                    lastPostion = startIndex;
                    //设置分段下载的头信息。  Range:做分段数据请求用的。
                    openConnection.setRequestProperty("Range", "bytes="+lastPostion+"-"+endIndex);//bytes:0-500:请求服务器资源中0-500之间的字节信息  501-1000:
                    System.out.println("实际下载2:  线程:"+threadId+",开始位置:"+lastPostion+";结束位置:"+endIndex);
                }
                
                
                
                System.out.println("getResponseCode"+openConnection.getResponseCode() );
                
                
                
                if(openConnection.getResponseCode() == 206){//200:请求全部资源成功, 206代表部分资源请求成功
                    InputStream inputStream = openConnection.getInputStream();
                    //请求成功将流写入本地文件中,已经创建的占位那个文件中
                    
                    RandomAccessFile randomAccessFile = new RandomAccessFile(filename, "rw");
                    randomAccessFile.seek(lastPostion);//设置随机文件从哪个位置开始写。
                    //将流中的数据写入文件
                    byte[] buffer = new byte[1024];
                    int length = -1;
                    int total = 0;//记录本次线程下载的总大小
                    
                    while((length= inputStream.read(buffer)) !=-1){
                        randomAccessFile.write(buffer, 0, length);
                        
                        total = total+ length;
                        //去保存当前线程下载的位置,保存到文件中
                        int currentThreadPostion = lastPostion + total;//计算出当前线程本次下载的位置
                        //创建随机文件保存当前线程下载的位置
                        File file = new File(threadId+".txt");
                        RandomAccessFile accessfile = new RandomAccessFile(file, "rwd");
                        accessfile.write(String.valueOf(currentThreadPostion).getBytes());
                        accessfile.close();
                        
                        
                        
                    }
                    //关闭相关的流信息
                    inputStream.close();
                    randomAccessFile.close();
                    
                    System.out.println("线程:"+threadId+",下载完毕");
                    
                    
                    
                    //当所有线程下载结束,删除存放下载位置的文件。
                    synchronized (DownloadThread.class) {
                        runningTrheadCount = runningTrheadCount -1;//标志着一个线程下载结束。
                        if(runningTrheadCount == 0 ){
                            System.out.println("所有线程下载完成");
                            for(int i =0 ;i< threadCount;i++){
                                File file = new File(i+".txt");
                                System.out.println(file.getAbsolutePath());
                                file.delete();
                            }
                        }
                        
                    }
                
                    
                }
                

            }catch (Exception e) {
                e.printStackTrace();
            }



            super.run();
        }

    }
    

上面代码主要做了4 件事
1、设置分段下载的头信息;
2、分段下载网络资源
3、当中断时把当前各个线程当前下载的位置分别保存到一个临时文件中
4、下载完成后把临时文件删除 上面代码中都给出了详细的注释

其中有一点要注意
openConnection.setRequestProperty("Range", "bytes="+lastPostion+"-"+endIndex);
如果"bytes=格式不对的话会导致设置不成功,返回的将不是部分资源的返回码
另一个要说明的就是randomAccessFile.seek(startThread);是设置各个线程下载的开始位置
现在的开源项目xutils也可以实现多线程断点下载 不过还是附上demo吧

https://github.com/solary2014/Muchdownload
http://download.csdn.net/detail/asd1031/9654085

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 什么是多线程下载 举例: 一个装有水的水桶(要下载的资源),出水口(下载线程),出水口越多,水流的越快。即多个线程...
    ccplay5grate阅读 275评论 0 2
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,398评论 25 707
  • 1.普通单线程下载文件: 直接使用URLConnection.openStream();打开网络输入流,然后将流写...
    JuSong阅读 2,566评论 2 10
  • 早晨早早出门,开车上班。太阳已升起,露出红扑扑笑脸,照的世界金灿灿的。今年倒春寒,寒流赖着不走,非要和春姑娘纠缠,...
    永远是我阅读 698评论 4 4
  • 有叔叔阿姨帮忙,同学们干的又快又好。参与实践活动让孩子们感受到老师在开学初为他们所做的一切,体会老师的辛苦。再次感...
    孩儿王阅读 204评论 0 0