一、需求:
根据Url下载大文件,创建本地文件,从输入流读取并写入文件,要求能满足大文件的下载,不能出现OOM
二、基于OKHttp
1.引入依赖
<!-- OK HTTP -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
2.下载文件代码
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
/**
* 下载文件并保存
* @param url 下载地址
* @param folder 本地保存文件夹路径,比如:E:\test\down
* @return 文件(下载失败返回null)
*/
public File downloadFile(String url, String folder){
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
try(Response response = client.newCall(request).execute()){
//从Url提取文件名
String fileName = getFileNameFromUrl(url);
//拼接完整文件路径
String filePath = folder + File.separator + fileName;
if(response.isSuccessful()){
if(Objects.isNull(response.body())){
logger.info("下载失败,响应体为空");
return null;
}
//创建本地文件
File file = new File(filePath);
try(
InputStream in = response.body().byteStream();//获取响应输入流
FileOutputStream out = new FileOutputStream(file)//创建文件输出流
){
//写入本地文件
int len;
byte[] buffer = new byte[4096];
while((len = in.read(buffer)) != -1) { //从输入流中读取数据到缓冲区
out.write(buffer, 0, len); //将缓冲区的数据写入输出流
}
out.flush(); //刷新输出流缓冲区
}
return file;
}else{
logger.info("响应失败:httpStatus={}", response.code());
return null;
}
}catch (Exception e){
logger.info("文件下载失败,地址:{},错误:{}", url, e.getMessage(), e);
return null;
}
}
3.下载超时&https
OkHttpClient client = new OkHttpClient.Builder()
//如果下载超时,可以调整超时时间
.readTimeout(16, TimeUnit.MINUTES)
//下面两项配置可以解决https链接下载报错的问题(通过屏蔽SSL证书验证)
.sslSocketFactory(IgnoreSSL.sslSocketFactory, IgnoreSSL.x509TrustManager)
.hostnameVerifier(IgnoreSSL.hostnameVerifier)
.build();
public class IgnoreSSL {
public static final HostnameVerifier hostnameVerifier = (hostname, session) -> true;
public static final SSLSocketFactory sslSocketFactory = sslContext().getSocketFactory();
public static final X509TrustManager x509TrustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[] {};
}
};
public static SSLContext sslContext(){
try{
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[] { x509TrustManager }, new SecureRandom());
return sslContext;
}catch (NoSuchAlgorithmException|KeyManagementException e) {
throw new RuntimeException("SSL ignore error: "+e.getMessage(), e);
}
}
}
三、基于Hutool
1.引入依赖
<!-- Hutool工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.16</version>
</dependency>
2.下载文件代码
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
/**
* 下载文件并保存
* @param url 下载地址
* @param folder 本地保存文件夹路径,比如:E:\test\down
* @return 文件(下载失败返回null)
*/
public File downloadFile(String url, String folder){
//注意必须使用:executeAsync()
try(HttpResponse response = HttpUtil.createGet(url).executeAsync()){
//从Url提取文件名
String fileName = getFileNameFromUrl(url);
//拼接完整路径(含文件名)
String filePath = folder+ File.separator +fileName;
//响应处理
if(response.isOk()){
File file = new File(filePath);//创建本地文件
try(
InputStream in = response.bodyStream();//获取响应输入流
FileOutputStream out = new FileOutputStream(file)//创建文件输出流
){
//写入本地文件
int len;
byte[] buffer = new byte[4096];
while((len = in.read(buffer)) != -1) { //从输入流中读取数据到缓冲区
out.write(buffer, 0, len); //将缓冲区的数据写入输出流
}
out.flush(); //刷新输出流缓冲区
}
return file;
}else{
logger.info("响应失败:httpStatus={}", response.getStatus());
return null;
}
}catch (Exception e){
logger.info("文件下载失败,地址:{},错误:{}", url, e.getMessage(), e);
return null;
}
}
四、辅助方法
import cn.hutool.core.net.URLDecoder;
import java.nio.charset.StandardCharsets;
/**
* 从Url提取文件名
* @param url 文件路径
* @return 文件名
*/
private static String getFileNameFromUrl(String url){
String uri = URLDecoder.decode(url.trim(), StandardCharsets.UTF_8);
int index = uri.lastIndexOf("/");
return index==-1 ? uri : uri.substring(index+1);
}