免费资源下载
一、项目概述
多线程下载器通过将大文件分割成多个部分,使用多个线程同时下载不同部分,最后合并成一个完整文件,可以显著提高下载速度。本教程将实现一个功能完整的Java多线程下载器。
二、项目结构设计
/src
/main
/java
/com
/downloader
MultiThreadDownloader.java # 主下载器类
DownloadTask.java # 下载任务类
ProgressBar.java # 进度条显示类
FileUtils.java # 文件工具类
/resources
/test
/java
/com
/downloader
DownloadTest.java # 测试类
三、核心组件实现
1. 主下载器类 (MultiThreadDownloader.java)
package com.downloader;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class MultiThreadDownloader {
private String fileUrl;
private String savePath;
private int threadCount;
private long fileSize;
private ProgressBar progressBar;
public MultiThreadDownloader(String fileUrl, String savePath, int threadCount) {
this.fileUrl = fileUrl;
this.savePath = savePath;
this.threadCount = threadCount;
this.progressBar = new ProgressBar();
}
public void download() throws Exception {
// 获取文件大小
fileSize = getFileSize();
System.out.println("文件大小: " + FileUtils.formatFileSize(fileSize));
// 创建空文件
RandomAccessFile file = new RandomAccessFile(savePath, "rw");
file.setLength(fileSize);
file.close();
// 计算每个线程下载的字节范围
long chunkSize = fileSize / threadCount;
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
// 提交下载任务
for (int i = 0; i < threadCount; i++) {
long startByte = i * chunkSize;
long endByte = (i == threadCount - 1) ? fileSize - 1 : (i + 1) * chunkSize - 1;
executor.execute(new DownloadTask(fileUrl, savePath, i, startByte, endByte, progressBar));
}
// 显示进度条
progressBar.start(fileSize);
// 关闭线程池
executor.shutdown();
// 等待所有任务完成
try {
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("下载被中断", e);
}
// 停止进度条
progressBar.stop();
System.out.println("n下载完成! 文件保存至: " + savePath);
}
private long getFileSize() throws IOException {
URL url = new URL(fileUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("HEAD");
int responseCode = conn.getResponseCode();
if (responseCode != HttpURLConnection.HTTP_OK) {
throw new IOException("服务器返回错误响应: " + responseCode);
}
String contentLength = conn.getHeaderField("Content-Length");
if (contentLength == null) {
throw new IOException("无法获取文件大小");
}
return Long.parseLong(contentLength);
}
}
2. 下载任务类 (DownloadTask.java)
package com.downloader;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
public class DownloadTask implements Runnable {
private String fileUrl;
private String savePath;
private int threadId;
private long startByte;
private long endByte;
private ProgressBar progressBar;
public DownloadTask(String fileUrl, String savePath, int threadId,
long startByte, long endByte, ProgressBar progressBar) {
this.fileUrl = fileUrl;
this.savePath = savePath;
this.threadId = threadId;
this.startByte = startByte;
this.endByte = endByte;
this.progressBar = progressBar;
}
@Override
public void run() {
InputStream inputStream = null;
RandomAccessFile file = null;
try {
// 创建HTTP连接
URL url = new URL(fileUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Range", "bytes=" + startByte + "-" + endByte);
int responseCode = conn.getResponseCode();
if (responseCode != HttpURLConnection.HTTP_PARTIAL) {
throw new IOException("服务器不支持范围请求,响应码: " + responseCode);
}
// 获取输入流
inputStream = conn.getInputStream();
// 随机访问文件写入数据
file = new RandomAccessFile(savePath, "rw");
file.seek(startByte);
byte[] buffer = new byte[4096];
int bytesRead;
long totalRead = 0;
while ((bytesRead = inputStream.read(buffer)) != -1) {
file.write(buffer, 0, bytesRead);
totalRead += bytesRead;
// 更新进度
progressBar.update(bytesRead);
}
System.out.println("线程 " + threadId + " 下载完成: " + (endByte - startByte + 1) + " 字节");
} catch (IOException e) {
System.err.println("线程 " + threadId + " 下载出错: " + e.getMessage());
} finally {
try {
if (inputStream != null) inputStream.close();
if (file != null) file.close();
} catch (IOException e) {
System.err.println("关闭资源出错: " + e.getMessage());
}
}
}
}
3. 进度条显示类 (ProgressBar.java)
package com.downloader;
public class ProgressBar {
private volatile long totalDownloaded = 0;
private long totalSize;
private boolean running = false;
private Thread displayThread;
public void start(long totalSize) {
this.totalSize = totalSize;
this.running = true;
displayThread = new Thread(() -> {
while (running) {
printProgress();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
printProgress(); // 最终打印一次
});
displayThread.start();
}
public void stop() {
running = false;
try {
displayThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public synchronized void update(long bytes) {
totalDownloaded += bytes;
}
private synchronized void printProgress() {
double percent = (double) totalDownloaded / totalSize * 100;
int filledLength = (int) (50 * totalDownloaded / totalSize);
StringBuilder bar = new StringBuilder("[");
for (int i = 0; i < 50; i++) {
if (i ");
} else {
bar.append(" ");
}
}
bar.append("]");
System.out.printf("r%s %.2f%% (%s/%s)",
bar.toString(),
percent,
FileUtils.formatFileSize(totalDownloaded),
FileUtils.formatFileSize(totalSize));
}
}
4. 文件工具类 (FileUtils.java)
package com.downloader;
public class FileUtils {
public static String formatFileSize(long size) {
if (size < 1024) {
return size + " B";
} else if (size < 1024 * 1024) {
return String.format("%.2f KB", size / 1024.0);
} else if (size < 1024 * 1024 * 1024) {
return String.format("%.2f MB", size / (1024.0 * 1024));
} else {
return String.format("%.2f GB", size / (1024.0 * 1024 * 1024));
}
}
}
四、使用示例
1. 创建测试类 (DownloadTest.java)
package com.downloader;
import java.util.Scanner;
public class DownloadTest {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("=== Java多线程下载器 ===");
System.out.print("请输入下载URL: ");
String url = scanner.nextLine();
System.out.print("请输入保存路径: ");
String savePath = scanner.nextLine();
System.out.print("请输入线程数(建议1-8): ");
int threadCount = scanner.nextInt();
try {
MultiThreadDownloader downloader = new MultiThreadDownloader(url, savePath, threadCount);
downloader.download();
} catch (Exception e) {
System.err.println("下载失败: " + e.getMessage());
e.printStackTrace();
} finally {
scanner.close();
}
}
}
2. 运行示例
=== Java多线程下载器 ===
请输入下载URL: https://example.com/largefile.zip
请输入保存路径: C:/Downloads/largefile.zip
请输入线程数(建议1-8): 4
文件大小: 256.78 MB
[=================> ] 45.32% (116.42 MB/256.78 MB)
五、高级功能扩展
1. 断点续传实现
通过保存每个线程的下载进度,可以在程序重启后继续未完成的下载:
// 在DownloadTask中添加断点续传支持
private long currentByte;
public void run() {
// 读取保存的进度
long startPoint = readDownloadProgress();
// 修改Range请求
conn.setRequestProperty("Range", "bytes=" + startPoint + "-" + endByte);
// 下载过程中保存进度
saveDownloadProgress(startPoint + bytesRead);
}
2. 下载速度限制
添加下载速度控制功能,避免占用过多带宽:
public class SpeedLimiter {
private final long maxBytesPerSecond;
private long bytesThisSecond;
private long startTime;
public SpeedLimiter(long maxBytesPerSecond) {
this.maxBytesPerSecond = maxBytesPerSecond;
this.bytesThisSecond = 0;
this.startTime = System.currentTimeMillis();
}
public void acquire(int bytes) throws InterruptedException {
bytesThisSecond += bytes;
long elapsed = System.currentTimeMillis() - startTime;
if (elapsed >= 1000) {
resetCounter();
return;
}
long expectedTime = (long) (bytesThisSecond / (maxBytesPerSecond / 1000.0));
long sleepTime = expectedTime - elapsed;
if (sleepTime > 0) {
Thread.sleep(sleepTime);
}
}
private void resetCounter() {
bytesThisSecond = 0;
startTime = System.currentTimeMillis();
}
}
3. 代理服务器支持
添加代理服务器支持,方便在企业网络环境下使用:
public class ProxySupport {
public static HttpURLConnection createConnection(String url, Proxy proxy) throws IOException {
if (proxy != null) {
return (HttpURLConnection) new URL(url).openConnection(proxy);
} else {
return (HttpURLConnection) new URL(url).openConnection();
}
}
}
六、异常处理与优化
1. 网络异常重试机制
public class RetryMechanism {
public static <T> T executeWithRetry(Callable<T> task, int maxRetries)
throws Exception {
int retries = 0;
while (true) {
try {
return task.call();
} catch (Exception e) {
if (retries >= maxRetries) {
throw e;
}
retries++;
System.out.println("操作失败,第" + retries + "次重试...");
Thread.sleep(1000 * retries); // 指数退避
}
}
}
}
2. 资源清理确保
// 使用try-with-resources确保资源正确释放
try (InputStream inputStream = conn.getInputStream();
RandomAccessFile file = new RandomAccessFile(savePath, "rw")) {
// 下载逻辑
} catch (IOException e) {
// 异常处理
}
七、项目构建与运行
1. Maven依赖配置
<dependencies>
<!-- 本项目无需额外依赖,使用标准Java库 -->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.downloader.DownloadTest</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
2. 编译与打包
# 使用Maven打包
mvn clean package
# 运行程序
java -jar downloader.jar
八、总结
本教程详细介绍了如何使用Java实现一个功能完整的多线程文件下载器。通过这个项目,你学习了:
- Java多线程编程和线程池管理
- HTTP范围请求的处理方法
- 文件分块下载与合并技术
- 进度显示和用户交互实现
- 异常处理和资源管理
这个下载器不仅具有实际使用价值,也是学习Java并发和网络编程的绝佳项目。你可以在此基础上继续扩展功能,如添加GUI界面、支持下载队列管理、集成到更大系统中等。

