Java实现请求下载的核心思路是通过网络请求获取目标资源,并将其流式写入本地文件,根据下载场景的不同(如HTTP/HTTPS文件、网络资源、大文件等),实现方式可分为基础实现、进阶功能(进度监控、断点续传)及性能优化等层次,以下从具体实践出发,分模块介绍实现方法。

基础HTTP文件下载:原生与第三方库结合
使用HttpURLConnection实现简单下载
Java原生提供的HttpURLConnection是处理HTTP请求的基础工具,适合简单的文件下载场景,其核心步骤包括:建立连接、设置请求头、获取输入流、写入本地文件。
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
public class SimpleDownload {
public static void downloadFile(String fileUrl, String savePath) throws IOException {
URL url = new URL(fileUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置请求方法为GET,设置连接和读取超时(单位:毫秒)
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
connection.setReadTimeout(10000);
// 检查响应码(200表示成功)
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
throw new IOException("Server returned HTTP code: " + connection.getResponseCode());
}
// 获取输入流和输出流
try (InputStream inputStream = connection.getInputStream();
FileOutputStream outputStream = new FileOutputStream(savePath)) {
byte[] buffer = new byte[4096]; // 4KB缓冲区
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
} finally {
connection.disconnect(); // 断开连接
}
}
}
关键点说明:
- 请求头设置:可通过
connection.setRequestProperty("User-Agent", "Mozilla/5.0")模拟浏览器请求,避免被某些服务器拦截。 - 流处理:使用
try-with-resources自动关闭流,避免资源泄漏。 - 缓冲区:合理设置缓冲区大小(如4KB~8KB)可减少IO次数,提升性能。
使用Apache HttpClient优化请求
原生HttpURLConnection功能有限,实际开发中更推荐Apache HttpClient(或OkHttp等第三方库),它提供了更强大的连接管理、重试机制和请求配置。
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
public class HttpClientDownload {
public static void download(String fileUrl, String savePath) throws IOException {
try (CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(fileUrl)) {
CloseableHttpResponse response = httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
if (entity != null) {
try (InputStream inputStream = entity.getContent();
FileOutputStream outputStream = new FileOutputStream(savePath)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
EntityUtils.consume(entity); // 确保实体内容完全消费,释放连接
}
}
}
}
优势:
- 连接池:内置连接池管理,可复用TCP连接,减少握手开销。
- 异常处理:提供更细粒度的异常类(如
ConnectTimeoutException),便于精准捕获错误。
进阶功能:进度监控与断点续传
下载进度监控
对于大文件下载,实时显示进度(如百分比、已下载字节数)能提升用户体验,可通过计算已读取字节数与总文件大小的比例实现。

public class ProgressMonitorDownload {
public static void downloadWithProgress(String fileUrl, String savePath, DownloadListener listener) throws IOException {
URL url = new URL(fileUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
int fileSize = connection.getContentLength(); // 获取文件总大小(字节)
listener.onProgress(0, fileSize); // 初始化进度
try (InputStream inputStream = connection.getInputStream();
FileOutputStream outputStream = new FileOutputStream(savePath)) {
byte[] buffer = new byte[4096];
int bytesRead;
int totalBytesRead = 0;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
listener.onProgress(totalBytesRead, fileSize); // 实时更新进度
}
} finally {
connection.disconnect();
}
}
public interface DownloadListener {
void onProgress(int downloadedBytes, int totalBytes);
}
}
使用示例:
ProgressMonitorDownload.downloadWithProgress(
"https://example.com/largefile.zip",
"largefile.zip",
(downloaded, total) -> {
int progress = (int) ((downloaded * 100.0) / total);
System.out.printf("下载进度:%d%%%n", progress);
}
);
断点续传实现
断点续传的核心是记录已下载的字节位置,并在中断后从该位置继续下载,需通过HTTP请求头Range实现,并使用RandomAccessFile随机写入文件。
import java.io.RandomAccessFile;
public class ResumableDownload {
private static final String TEMP_FILE = "temp_download.tmp";
public static void downloadWithResume(String fileUrl, String savePath) throws IOException {
File tempFile = new File(TEMP_FILE);
long downloadedBytes = 0;
// 检查临时文件,获取已下载字节数
if (tempFile.exists()) {
downloadedBytes = tempFile.length();
}
URL url = new URL(fileUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Range", "bytes=" + downloadedBytes + "-"); // 设置Range请求头
if (connection.getResponseCode() != HttpURLConnection.HTTP_PARTIAL) {
throw new IOException("Server does not support resume download");
}
try (RandomAccessFile randomAccessFile = new RandomAccessFile(savePath, "rw");
InputStream inputStream = connection.getInputStream()) {
randomAccessFile.seek(downloadedBytes); // 定位到文件末尾
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
randomAccessFile.write(buffer, 0, bytesRead);
downloadedBytes += bytesRead;
// 更新临时文件(记录已下载字节数)
try (FileOutputStream tempOutputStream = new FileOutputStream(tempFile)) {
tempOutputStream.write(String.valueOf(downloadedBytes).getBytes());
}
}
}
// 下载完成后删除临时文件
if (tempFile.exists()) {
tempFile.delete();
}
}
}
关键逻辑:
- Range请求头:
bytes=100-表示从第100字节开始下载,服务器需支持206 Partial Content响应。 - 随机写入:
RandomAccessFile的seek()方法允许从任意位置写入,避免覆盖已下载内容。
异常处理与健壮性设计
下载过程中可能因网络波动、服务器错误、磁盘空间不足等问题中断,需做好异常捕获和资源释放:
- 网络异常:捕获
SocketTimeoutException、UnknownHostException等,提示用户检查网络。 - IO异常:捕获
FileNotFoundException(如无写入权限)、IOException(如磁盘满),给出具体错误信息。 - 资源释放:确保所有流(
InputStream、OutputStream)和连接(HttpURLConnection、HttpClient)在finally块中关闭,或使用try-with-resources。
public void safeDownload(String fileUrl, String savePath) {
try {
SimpleDownload.downloadFile(fileUrl, savePath);
System.out.println("下载完成");
} catch (SocketTimeoutException e) {
System.err.println("连接超时,请检查网络");
} catch (FileNotFoundException e) {
System.err.println("文件写入失败,请检查路径或权限");
} catch (IOException e) {
System.err.println("下载失败:" + e.getMessage());
}
}
性能优化:多线程与分片下载
对于大文件(如GB级别),单线程下载效率较低,可采用多线程分片下载:将文件分成多个片段,每个线程下载一个片段,最后合并。

实现步骤:
- 获取文件总大小,计算分片数量(如每个片段10MB)。
- 为每个分片创建下载线程,使用
Range头指定下载范围。 - 所有分片下载完成后,按顺序合并文件。
import java.io.*;
import java.util.concurrent.*;
public class MultiThreadDownload {
private static final int THREAD_COUNT = 4;
private static final String PART_PREFIX = "part_";
public static void downloadInParts(String fileUrl, String savePath) throws IOException, InterruptedException {
URL url = new URL(fileUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
int fileSize = connection.getContentLength();
int partSize = fileSize / THREAD_COUNT;
ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
// 提交分片下载任务
for (int i = 0; i < THREAD_COUNT; i++) {
int start = i * partSize;
int end = (i == THREAD_COUNT - 1) ? fileSize - 1 : start + partSize - 1;
executor.execute(new DownloadTask(fileUrl, start, end, PART_PREFIX + i, latch));
}
latch.await(); // 等待所有分片下载完成
executor.shutdown();
// 合并分片文件
mergeFiles(savePath, THREAD_COUNT);
}
private static void mergeFiles(String targetFile, int partCount) throws IOException {
try (FileOutputStream outputStream = new FileOutputStream(targetFile)) {
for (int i = 0; i < partCount; i++) {
File partFile = new File(PART_PREFIX + i);
try (FileInputStream inputStream = new FileInputStream(partFile)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
partFile.delete(); // 删除分片文件
}
}
}
static class DownloadTask implements Runnable {
private final String fileUrl;
private final int start;
private final int end;
private final String partPath;
private final CountDownLatch latch;
public DownloadTask(String fileUrl, int start, int end, String partPath, CountDownLatch latch) {
this.fileUrl = fileUrl;
this.start = start;
this.end = end;
this.partPath = partPath;
this.latch = latch;
}
@Override
public void run() {
try {
URL url = new URL(fileUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Range", "bytes=" + start + "-" + end);
try (InputStream inputStream = connection.getInputStream();
RandomAccessFile partFile = new RandomAccessFile(partPath, "rw")) {
partFile.seek(0);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
partFile.write(buffer, 0, bytesRead);
}
}
} catch (IOException e) {
System.err.println("分片下载失败:" + e.getMessage());
} finally {
latch.countDown();
}
}
}
}
Java实现请求下载需根据场景选择合适的技术栈:基础下载可用原生HttpURLConnection或HttpClient;进阶功能(进度监控、断点续传)需结合请求头和文件操作;大文件下载则需通过多线程分片提升效率,无论哪种方式,异常处理和资源释放都是保证程序健壮性的关键,实际开发中,还需考虑服务器兼容性(如Range头支持)、网络波动重试机制等细节,以构建稳定可靠的下载系统。















