java 多線程下載文件

      關於下載文件,首先需要了解一點io和http請求,線程等相關知識,然後才能一步一步的趴坑整理出一個較爲滿意的結果。原先我寫過一個較爲簡單的下載是單線程的,如下

package cn.zectec.hamster.baseservice.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.*;
import java.util.HashMap;
import java.util.Map;

public class DownloadUtil {
   private static Logger logger = LoggerFactory.getLogger(DownloadUtil.class);

   public static void errorFilePath(HttpServletResponse response, String msg){
      //沒有該消息記錄直接返回無此文件
      try {
         ServletOutputStream out=response.getOutputStream();
         OutputStreamWriter ow=new OutputStreamWriter(out,"UTF-8");
         ow.write(msg);
         ow.flush();
         ow.close();
      } catch (IOException e) {

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

   }
   /**
    * @param filePath 要下載的文件路徑
    * @param returnName 返回的文件名
    * @param response HttpServletResponse
    * @param delFlag 是否刪除文件
    */
   protected void download(String filePath,String returnName,HttpServletResponse response,boolean delFlag){
      prototypeDownload(new File(filePath), returnName, response, delFlag);
   }


   /**
    * @param file 要下載的文件
    * @param returnName 返回的文件名
    * @param response HttpServletResponse
    * @param delFlag 是否刪除文件
    */
   public static void download(File file, String returnName, HttpServletResponse response, boolean delFlag){
      prototypeDownload(file, returnName, response, delFlag);
   }
   
   /**
    * @param file 要下載的文件
    * @param returnName 返回的文件名
    * @param response HttpServletResponse
    * @param delFlag 是否刪除文件
    */
   private static void prototypeDownload(File file, String returnName, HttpServletResponse response, boolean delFlag){
      // 下載文件
      FileInputStream inputStream = null;
      ServletOutputStream outputStream = null;
      try {
         if(!file.exists()) return;
         response.reset();
         //設置響應類型   PDF文件爲"application/pdf",WORD文件爲:"application/msword", EXCEL文件爲:"application/vnd.ms-excel"。
         response.setContentType("application/octet-stream;charset=utf-8");
         //設置響應的文件名稱,並轉換成中文編碼
         returnName = URLEncoder.encode(returnName,"UTF-8");
         returnName = response.encodeURL(new String(returnName.getBytes(),"iso8859-1"));    //保存的文件名,必須和頁面編碼一致,否則亂碼

         //attachment作爲附件下載;inline客戶端機器有安裝匹配程序,則直接打開;注意改變配置,清除緩存,否則可能不能看到效果
         response.addHeader("Content-Disposition",   "attachment;filename="+returnName);

         //將文件讀入響應流
         inputStream = new FileInputStream(file);
         outputStream = response.getOutputStream();
         int length = 1024;
         int readLength=0;
         byte buf[] = new byte[1024];
         readLength = inputStream.read(buf, 0, length);
         while (readLength != -1) {
            outputStream.write(buf, 0, readLength);
            readLength = inputStream.read(buf, 0, length);
         }
      }catch (IOException e) {
         errorFilePath(response,"無此文件");
      }catch (Exception e) {
         e.printStackTrace();
      }finally {
         try {
            outputStream.flush();
         } catch (IOException e) {

         }
         try {
            outputStream.close();
         } catch (IOException e) {

         }
         try {
            inputStream.close();
         } catch (IOException e) {

         }
         //刪除原文件

         if(delFlag) {
            file.delete();
         }
      }
   }

   /**
    * by tony 2013-10-17
    * @param byteArrayOutputStream 將文件內容寫入ByteArrayOutputStream
    * @param response HttpServletResponse 寫入response
    * @param returnName 返回的文件名
    */
   public void download(ByteArrayOutputStream byteArrayOutputStream, HttpServletResponse response, String returnName) throws IOException{
      response.setContentType("application/octet-stream;charset=utf-8");
      returnName = response.encodeURL(new String(returnName.getBytes(),"iso8859-1"));          //保存的文件名,必須和頁面編碼一致,否則亂碼
      response.addHeader("Content-Disposition",   "attachment;filename=" + returnName);  
      response.setContentLength(byteArrayOutputStream.size());
      
      ServletOutputStream outputstream = response.getOutputStream(); //取得輸出流
      byteArrayOutputStream.writeTo(outputstream);               //寫到輸出流
      byteArrayOutputStream.close();                         //關閉
      outputstream.flush();                                //刷數據
   }

   /***
    * 瀏覽器遠程下載文件
    */
   public static void downloadFileByHttpUrl(HttpServletResponse response,String urlStr,String fileName){
      OutputStream out = null;
      InputStream ips = null;
      try {
         logger.info("第一次請求url==>" + urlStr);
         URL url = new URL(urlStr);
         HttpURLConnection httpUrlConn = (HttpURLConnection) url.openConnection();
         httpUrlConn.addRequestProperty("Content-type", "application/json");
         //設置請求超時時間
         httpUrlConn.setReadTimeout(15000);
         httpUrlConn.setConnectTimeout(15000);
         httpUrlConn.setDoOutput(true); //允許寫出
         httpUrlConn.setDoInput(true);//允許寫入
         httpUrlConn.setUseCaches(false);//不使用緩存
         // 設置請求方式(GET/POST)
         httpUrlConn.setRequestMethod("GET");
         httpUrlConn.connect();
         int responseCode = httpUrlConn.getResponseCode();
         if(responseCode!=200){
            errorFilePath(response,"無此文件");
         }else {
            ips = httpUrlConn.getInputStream();//字節流 輸入流
            response.reset();
            response.setContentType("application/x-download");
            response.addHeader("Content-Disposition","attachment;filename="+ new String(fileName.getBytes(),"iso-8859-1"));
            response.setContentType("application/octet-stream");
            out = new BufferedOutputStream(response.getOutputStream());
            int length = 1024;
            int readLength=0;
            byte buf[] = new byte[1024];
            readLength = ips.read(buf, 0, length);
            while (readLength != -1) {
               out.write(buf, 0, readLength);
               readLength = ips.read(buf, 0, length);
            }
         }
      }catch (IOException e) {
         errorFilePath(response,"無此文件");
      }catch (Exception e) {
         e.printStackTrace();
      }finally {
         try {
            out.flush();
         } catch (IOException e) {

         }
         try {
            out.close();
         } catch (IOException e) {

         }
         try {
            ips.close();
         } catch (IOException e) {

         }
      }
   }

   /***
    * http 請求獲取下載文件保存到本地
    * urlStr 遠程地址
    * filePath 指定本地保存路徑
    */
   public static Map<String,Object> downloadFileByHttpUrl(String urlStr, String filePath){
      Map<String,Object> map = new HashMap<>();
      map.put("success",false);
      OutputStream out = null;
      InputStream ips = null;
      String requestMethod="GET";
      try {
         File file = new File(filePath);
         logger.info("===================:"+urlStr);
         logger.info("===================獲取流媒體服務中的文件:"+filePath);
         URL url = new URL(urlStr);
         HttpURLConnection httpUrlConn = (HttpURLConnection) url.openConnection();
         httpUrlConn.addRequestProperty("Content-type", "application/json");
         //httpUrlConn.addRequestProperty("accessToken",accessToken);
         //設置請求超時時間
         httpUrlConn.setReadTimeout(30000);
         httpUrlConn.setConnectTimeout(30000);
         httpUrlConn.setDoOutput(true); //允許寫出
         httpUrlConn.setDoInput(true);//允許寫入
         httpUrlConn.setUseCaches(false);//不使用緩存

         // 設置請求方式(GET/POST)
         httpUrlConn.setRequestMethod(requestMethod);
         if ("GET".equalsIgnoreCase(requestMethod)) {
            httpUrlConn.connect();
         }
         // 將返回的輸入流轉換成字符串
         ips = httpUrlConn.getInputStream();
         out = new FileOutputStream(file,false);
         int length = 1024;
         int readLength=0;
         byte buf[] = new byte[1024];
         readLength = ips.read(buf, 0, length);
         while (readLength != -1) {
            String s = new String(buf, "UTF-8");
            if (s.contains("未找到")) {
               logger.info("==================putIOToResponseByHttp錯誤信息:"+s);
               file.delete();
               map.put("msg",s);
               return map;
            }else{
               out.write(buf, 0, readLength);
               readLength = ips.read(buf, 0, length);
            }
         }
         map.put("success",true);
      }catch (IOException e) {
         logger.info("無此文件");
      }catch (Exception e) {
         e.printStackTrace();
      }finally {
         try {
            out.flush();
         } catch (IOException e) {

         }
         try {
            out.close();
         } catch (IOException e) {

         }
         try {
            ips.close();
         } catch (IOException e) {

         }
      }
      return map;
   }

   /***
    * 根據遠程http地址將流存到respons 返回失敗或者成功
    * @param response 瀏覽器response
    * @param urlStr 遠程http地址
    * @return 返回失敗或者成功
    */
   public static boolean putIOToResponseByHttp(HttpServletResponse response, String urlStr) {
      OutputStream out = null;
      InputStream ips = null;
      String requestMethod="GET";
      try {
         if (StringUtils.isEmpty(urlStr)) {
            return false;
         }else{
               URL url = new URL(urlStr);
               HttpURLConnection httpUrlConn = (HttpURLConnection) url.openConnection();
               httpUrlConn.addRequestProperty("Content-type", "application/json");
               //httpUrlConn.addRequestProperty("accessToken",accessToken);
               //設置請求超時時間
               httpUrlConn.setReadTimeout(300000);
               httpUrlConn.setConnectTimeout(300000);
               httpUrlConn.setDoOutput(true); //允許寫出
               httpUrlConn.setDoInput(true);//允許寫入
               httpUrlConn.setUseCaches(false);//不使用緩存

               // 設置請求方式(GET/POST)
               httpUrlConn.setRequestMethod(requestMethod);
               if ("GET".equalsIgnoreCase(requestMethod)) {
                  httpUrlConn.connect();
               }
               // 將返回的輸入流轉換成字符串
               ips = httpUrlConn.getInputStream();
               out = new BufferedOutputStream(response.getOutputStream());
               int length = 1024;
               int readLength=0;
               byte buf[] = new byte[1024];
               readLength = ips.read(buf, 0, length);
               boolean bool = true;
               while (readLength != -1) {
                  if (bool && new String(buf, "UTF-8").contains("未找到")) {
                     logger.info("==================putIOToResponseByHttp錯誤信息:"+new String(buf, "UTF-8"));
                     return false;
                  }else{
                     bool = false;
                     out.write(buf, 0, readLength);
                     readLength = ips.read(buf, 0, length);
                  }
               }
            }
            return true;
      }catch (IOException e) {
         errorFilePath(response,"無此文件");
      }catch (Exception e) {
         e.printStackTrace();
      }finally {
         try {
            out.flush();
         } catch (IOException e) {

         }
         try {
            out.close();
         } catch (IOException e) {

         }
         try {
            ips.close();
         } catch (IOException e) {

         }
      }
      return false;
   }
   public static InputStream returnIoByHttUrl(String requestMethod,String urlStr){
      try {
         URL url = new URL(urlStr);
         HttpURLConnection httpUrlConn = (HttpURLConnection) url.openConnection();
         httpUrlConn.addRequestProperty("Content-type", "application/json");
         //設置請求超時時間
         httpUrlConn.setReadTimeout(30000);
         httpUrlConn.setConnectTimeout(30000);
         httpUrlConn.setDoOutput(true); //允許寫出
         httpUrlConn.setDoInput(true);//允許寫入
         httpUrlConn.setUseCaches(false);//不使用緩存

         // 設置請求方式(GET/POST)
         httpUrlConn.setRequestMethod(requestMethod);
         if ("GET".equalsIgnoreCase(requestMethod)) {
            httpUrlConn.connect();
         }
         // 將返回的輸入流轉換成字符串
         return httpUrlConn.getInputStream();
      }catch (IOException e) {
         logger.info("無此文件");
      }catch (Exception e) {
         e.printStackTrace();
      }
      return null;
   }


   /***
    * 校驗遠程地址是否可用
    * @param requestUrl 請求地址
    * @return 是否可用
    */
   public static Boolean checkIP(String requestUrl){
      try {
         String hostAddress = InetAddress.getLocalHost().getHostAddress();
         logger.info("java獲取到的當前頁面地址"+hostAddress);
         URL url = new URL(requestUrl);
         HttpURLConnection httpUrlConn = (HttpURLConnection) url.openConnection();
         httpUrlConn.addRequestProperty("Content-type", "application/json");
         //設置請求超時時間
         httpUrlConn.setReadTimeout(5000);
         httpUrlConn.setConnectTimeout(5000);
         httpUrlConn.setDoOutput(true); //允許寫出
         httpUrlConn.setDoInput(true);//允許寫入
         httpUrlConn.setUseCaches(false);//不使用緩存
         // 設置請求方式(GET/POST)
         httpUrlConn.setRequestMethod("GET");
         httpUrlConn.connect();
         int responseCode = httpUrlConn.getResponseCode();
         if(responseCode==200){
            logger.info("=============="+requestUrl+":地址有效");
            return true;
         }
      }catch (Exception e){
         return false;
      }
      return false;
   }


   /***
    * 校驗遠程文件是否可用
    * @return 是否可用
    */
   public static Boolean checkFile(String urlStr){
      InputStream ips = null;
      String requestMethod="GET";
      try {
         URL url = new URL(urlStr);
         HttpURLConnection httpUrlConn = (HttpURLConnection) url.openConnection();
         httpUrlConn.addRequestProperty("Content-type", "application/json");
         //httpUrlConn.addRequestProperty("accessToken",accessToken);
         //設置請求超時時間
         httpUrlConn.setReadTimeout(30000);
         httpUrlConn.setConnectTimeout(30000);
         httpUrlConn.setDoOutput(true); //允許寫出
         httpUrlConn.setDoInput(true);//允許寫入
         httpUrlConn.setUseCaches(false);//不使用緩存

         // 設置請求方式(GET/POST)
         httpUrlConn.setRequestMethod(requestMethod);
         if ("GET".equalsIgnoreCase(requestMethod)) {
            httpUrlConn.connect();
         }
         // 將返回的輸入流轉換成字符串
         ips = httpUrlConn.getInputStream();
         int length = 1024;
         int readLength=0;
         byte buf[] = new byte[1024];
         readLength = ips.read(buf, 0, length);
         while (readLength != -1) {
            String s = new String(buf, "UTF-8");
            if (s.contains("未找到")) {
               return false;
            }else{
               return true;
            }
         }
      }catch (IOException e) {
         logger.info("無此文件");
      }catch (Exception e) {
         e.printStackTrace();
      }finally {
         try {
            ips.close();
         } catch (IOException e) {

         }
      }
      return false;
   }



}

 

用於單線程下載遇到大文件時讀寫過於緩慢,查了半天的百度收羅了很多知識,最後整理成一個較爲完整的多線程下載工具包

總共三個文件:

One:DownloadFileWithThreadPool

package cn.zectec.hamster.videorecord.download;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class DownloadFileWithThreadPool {

    private static Logger logger = LoggerFactory.getLogger(DownloadFileWithThreadPool.class);

    public void getFileWithThreadPoolByHttp(String urlLocation, String filePath, int poolLength) throws IOException {
        //有順序的線程
        ExecutorService threadPool = Executors.newSingleThreadExecutor();

        long len = getContentLength(urlLocation);
        for (int i = 0; i < poolLength; i++) {
            long start = i * len / poolLength;
            long end = (i + 1) * len / poolLength - 1;
            if (i == poolLength - 1) {
                end = len;
            }
            DownloadWithRangeByHttpUrl download = new DownloadWithRangeByHttpUrl(urlLocation, filePath, start, end);
            threadPool.execute(download);
        }
        threadPool.shutdown();
    }

    public void getFileWithThreadPoolByLocal(String localAddress, String filePath, int poolLength) throws IOException {
        //有順序的線程
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        File file = new File(localAddress);
        if(!file.exists() || !file.isFile()){
            logger.info("多線程下載本地文件:文件不存在");
            return;
        }
        long len = new File(localAddress).length();
        for (int i = 0; i < poolLength; i++) {
            long start = i * len / poolLength;
            DownloadWithRangeByLocalAddress download = new DownloadWithRangeByLocalAddress(localAddress, filePath, len,i,start);
            threadPool.execute(download);
        }
        threadPool.shutdown();
    }

    public static long getContentLength(String urlLocation) throws IOException {
        URL url = null;
        if (urlLocation != null) {
            url = new URL(urlLocation);
        }
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setReadTimeout(5000);
        conn.setRequestMethod("GET");
        long len = conn.getContentLength();

        return len;
    }

}

Two:DownloadWithRangeByHttpUrl

package cn.zectec.hamster.videorecord.download;

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

/***
 * 請求遠程http地址,指定文件地址下載文件
 */
public class DownloadWithRangeByHttpUrl implements Runnable {

    /**遠程調用地址*/
    private String urlLocation;

    /**文件保存地址*/
    private String filePath;

    /**下載起始位置*/
    private long start;

    /**下載結束位置*/
    private long end;

    DownloadWithRangeByHttpUrl(String urlLocation, String filePath, long start, long end) {
        this.urlLocation = urlLocation;
        this.filePath = filePath;
        this.start = start;
        this.end = end;
    }

    @Override
    public void run() {
        try {
            HttpURLConnection conn = getHttp();
            conn.setRequestProperty("Range", "bytes=" + start + "-" + end);

            File file = new File(filePath);
            RandomAccessFile out = null;
            if (file != null) {
                out = new RandomAccessFile(file, "rw");
            }
            out.seek(start);
            InputStream in = conn.getInputStream();
            byte[] b = new byte[1024];
            int len = 0;
            while ((len = in.read(b)) >= 0) {
                out.write(b, 0, len);
            }
            in.close();
            out.close();
        } catch (Exception e) {
            e.getMessage();
        }

    }

    public HttpURLConnection getHttp() throws IOException {
        URL url = null;
        if (urlLocation != null) {
            url = new URL(urlLocation);
        }
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        //設置請求超時時間
        conn.setReadTimeout(30000);
        conn.setConnectTimeout(30000);
        conn.setDoOutput(true); //允許寫出
        conn.setDoInput(true);//允許寫入
        conn.setUseCaches(false);//不使用緩存
        conn.setRequestMethod("GET");

        return conn;
    }

}

Three:DownloadWithRangeByLocalAddress

package cn.zectec.hamster.videorecord.download;

import sun.misc.Cleaner;
import sun.nio.ch.DirectBuffer;

import java.io.File;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

/***
 * 獲取本地資源路徑,指定文件地址保存文件
 */
public class DownloadWithRangeByLocalAddress implements Runnable {

    // 緩衝區大小爲3M
    final int BUFFER_SIZE = 0x300000;

    /**本地文件地址*/
    private String localAddress;

    /**文件保存地址*/
    private String filePath;

    /**如index/10,文件拆分爲十段,index爲第幾段*/
    private long index;

    /**下載起始位置*/
    private long start;

    /**文件大小*/
    private long len;

    DownloadWithRangeByLocalAddress(String localAddress, String filePath, long len,long index,long start) {
        this.localAddress = localAddress;
        this.filePath = filePath;
        this.index = index;
        this.start = start;
        this.len = len;
    }

    @Override
    public void run() {
        try {
            File sourcefile = new File(localAddress);
            RandomAccessFile randomAccessFile = new RandomAccessFile(sourcefile, "r");
            FileChannel channel = randomAccessFile.getChannel();
            MappedByteBuffer inputBuffer = channel.map(FileChannel.MapMode.READ_ONLY, len*index/10,len/10);

            File file = new File(filePath);
            RandomAccessFile out = null;
            if (file != null) {
                out = new RandomAccessFile(file, "rw");
            }
            out.seek(start);
            byte[] dst = new byte[BUFFER_SIZE];// 每次讀出3M的內容

            for (int offset = 0; offset < inputBuffer.capacity(); offset += BUFFER_SIZE) {
                if (inputBuffer.capacity() - offset >= BUFFER_SIZE) {

                    for (int i = 0; i < BUFFER_SIZE; i++) {
                        dst[i] = inputBuffer.get(offset + i);
                    }

                } else {

                    for (int i = 0; i < inputBuffer.capacity() - offset; i++) {
                        dst[i] = inputBuffer.get(offset + i);
                    }

                }
                out.write(dst, 0, BUFFER_SIZE);
            }
            out.close();
            inputBuffer.force();
            channel.force(true);
            channel.close();
            randomAccessFile.close();
            unmap(inputBuffer);
        } catch (Exception e) {
            e.getMessage();
        }

    }
    private void unmap(MappedByteBuffer var0) {
        Cleaner var1 = ((DirectBuffer)var0).cleaner();
        if (var1 != null) {
            var1.clean();
        }
    }
}

解說:

 DownloadFileWithThreadPool 該類用於創建線程池並執行相應的任務

裏面有兩個方法 通過調用這兩個方法 來實現多線程下載 遠程文件 或者本地文件到指定文件夾下

getFileWithThreadPoolByHttp(String urlLocation, String filePath, int poolLength)

這個方法根據參數說明:

urlLocation 視頻遠程訪問地址

filePath 文件保存的絕對路徑

poolLength 線程數

getFileWithThreadPoolByLocal(String localAddress, String filePath, int poolLength)

這個方法根據參數說明:

localAddress 本地文件路徑

filePath 文件保存的絕對路徑

poolLength 線程數

 

使用:

在最後我只寫一下我是怎麼使用的,其實調用非常簡單隻要傳對參數就行了

遠程下載請求示例:

long time = System.currentTimeMillis();
DownloadFileWithThreadPool pool = new DownloadFileWithThreadPool();
try {
    pool.getFileWithThreadPoolByHttp(urlLocation, filePath, 10);
    System.out.println("下載成功:"+( System.currentTimeMillis() - time )+"毫秒");
} catch (IOException e) {
    e.printStackTrace();
    System.out.println("服務器出錯");
}

下載本地文件請求示例

long time = System.currentTimeMillis();
DownloadFileWithThreadPool pool = new DownloadFileWithThreadPool();
try {
    pool.getFileWithThreadPoolByLocal(urlLocation, filePath, 10);
    System.out.println("下載成功:"+( System.currentTimeMillis() - time )+"毫秒");
} catch (IOException e) {
    e.printStackTrace();
    System.out.println("服務器出錯");
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章