關於下載文件,首先需要了解一點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("服務器出錯");
}