A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© daoqin 高级黑马   /  2014-9-15 12:41  /  1019 人查看  /  1 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

本帖最后由 daoqin 于 2014-9-15 14:03 编辑

把这几天学的多线程、IO流、网络编程汇总,根据毕老师视频里的提示,写了一个支持断点续传的多线程下载
用到的知识点:
多线程的创建:继承Thread或者用Runnable接口
IO流中RandomAccessFile类相关方法的使用:seek(),readInt()、write()、writeInt()
File类中相关方法的使用:File()、exists()、delete()
字节输入流InputStream的读取方法:read()
HttpURLConnection类的使用:setConnectTimeout()、getContentLength()、setRequestProperty()
  1. import java.io.File;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. import java.io.RandomAccessFile;
  5. import java.net.HttpURLConnection;
  6. import java.net.URL;
  7. public class MutThreadDown extends Thread{
  8.         private static final String DIR_PATH = "E://Download";//下载目录
  9.         private static final int THREAD_COUNT = 3;//下载线程数量
  10.         private URL  url;//目标下载地址
  11.         private File dataFile;//下载的存储文件
  12.         private File tempFile;//用来存储每个线程下载的进度的临时文件
  13.         private int  totalLength;//待下载文件的总长度
  14.         private int  threadLength;//每个线程要下载的长度
  15.         private int  finishLength;//当前已完成的长度
  16.         private long beginTime;//开始下载时间
  17.         public MutThreadDown(String address) throws IOException{
  18.                 url = new URL(address);//下载地址
  19.                 dataFile = new File(DIR_PATH,address.substring(address.lastIndexOf("/")+1));//创建本地文件
  20.                 tempFile = new File(dataFile.getAbsolutePath()+".temp");//创建临时文件
  21.         }
  22.         @Override
  23.         public void run() {
  24.                 // TODO Auto-generated method stub
  25.                 try {
  26.                         HttpURLConnection conn = (HttpURLConnection) url.openConnection();  
  27.             conn.setConnectTimeout(3000);  
  28.                         
  29.                         totalLength = conn.getContentLength();// 获取服务端发送过来的文件长度
  30.                         threadLength = (totalLength + THREAD_COUNT -1)/THREAD_COUNT;// 获取服务端发送过来的文件长度
  31.                         finishLength = 0;
  32.                         
  33.                         if(!tempFile.exists()){// 如果临时文件不存在
  34.                                 RandomAccessFile raf = new RandomAccessFile(tempFile, "rw");
  35.                                 for (int i = 0; i < THREAD_COUNT; i++) {// 创建临时文件, 用来记录每个线程已下载多少
  36.                                         raf.writeInt(0);
  37.                                 }
  38.                                 raf.close();
  39.                         }
  40.                         for (int i = 0; i < THREAD_COUNT; i++) {
  41.                                 new DownLoadThread(i).start();  // 开启线程, 每个线程将会下载一部分数据到本地文件中
  42.                         }
  43.                         beginTime = System.currentTimeMillis();
  44.                         
  45.                 } catch (Exception e) {
  46.                         // TODO: handle exception
  47.                         e.printStackTrace();
  48.                 }
  49.         }
  50.         private class DownLoadThread extends Thread{
  51.                 private int ID;

  52.                 public DownLoadThread(int iD) {
  53.                         this.ID = iD;
  54.                 }
  55.                 @Override
  56.                 public void run() {
  57.                         try {
  58.                                 RandomAccessFile tempRaf = new RandomAccessFile(tempFile, "rw"); // 记录进度的临时文件  
  59.                                 tempRaf.seek(ID*4); // 将指针移动到当前线程的位置(每个线程写1个int值, 占4字节)  
  60.                                 int threadFinish =tempRaf.readInt();  // 读取当前线程已完成了多少
  61.                                 synchronized (this) {
  62.                                         finishLength += threadFinish;// 统计所有线程总共完成了多少
  63.                                 }
  64.                                 // 计算当前线程起始位置和结束位置
  65.                                 int start = ID*threadLength + threadFinish;
  66.                                 int end = ID*threadLength + threadLength-1;
  67.                                 
  68.                                 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
  69.                                 conn.setConnectTimeout(3000);
  70.                                 conn.setRequestProperty("Range", "bytes="+start+"-"+end); // 设置当前线程下载的范围
  71.                                 
  72.                                 System.out.println("线程" + ID + "开始下载"+start+"-"+end);
  73.                                 
  74.                                 InputStream in = conn.getInputStream();//获取连接的输入流
  75.                                 RandomAccessFile dataRaf = new RandomAccessFile(dataFile, "rw");//保存数据的本地文件
  76.                                 dataRaf.seek(start);//设置当前线程保存数据的位置
  77.                                 //开始读取和写入数据
  78.                                 byte[] buf = new byte[1024*100];
  79.                                 int len = 0;
  80.                                 while((len = in.read(buf))!=-1){
  81.                                         dataRaf.write(buf, 0, len);//写入本地文件
  82.                                         threadFinish += len;//统计当前线程完成量
  83.                                         tempRaf.seek(ID*4);
  84.                                         tempRaf.writeInt(threadFinish);//将当前线程完成了多少写入到临时文件
  85.                                         synchronized (this) {
  86.                                                 finishLength += len;  // 统计所有线程总共完成了多少  
  87.                                         }
  88.                                 }
  89.                                 in.close();
  90.                                 tempRaf.close();
  91.                                 dataRaf.close();
  92.                                 System.out.println("线程" + ID + "下载完毕");
  93.                                 if(totalLength == finishLength){// 如果已完成长度等于服务端文件长度(代表下载完成)
  94.                                         System.out.println("下载完成, 耗时: " + (System.currentTimeMillis() - beginTime));  
  95.                     tempFile.delete();   
  96.                                 }
  97.                                 
  98.                         } catch (Exception e) {
  99.                                 // TODO: handle exception
  100.                         }
  101.                 }
  102.                
  103.         }
  104.         public static void main(String[] args) throws IOException {
  105.                 // TODO Auto-generated method stub
  106.                 new MutThreadDown("需要下载文件的URL").start();
  107.         }
  108. }
复制代码
其实迅雷等下载软件下载原理也是这样的,每一个服务器对下载的请求都有带宽限制的,所以多个线程下载从整体上是增加了下载的带宽,不过启动的线程也不是没有限制的,这个受限于你的真实的物理带宽。


1 个回复

倒序浏览
求加个技术分!写的好辛苦啊!呵呵
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马