文件上传下载实战
文件上传下载是 Java 开发中的常见需求。本节从最简单的实现开始,逐步演进到带进度条、断点续传的生产级方案。
单文件下载(带进度条)
java
public static void downloadFile(String serverUrl, String savePath)
throws IOException {
URL url = new URL(serverUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
int fileSize = conn.getContentLength();
try (
InputStream in = new BufferedInputStream(conn.getInputStream());
BufferedOutputStream out = new BufferedOutputStream(
new FileOutputStream(savePath))
) {
byte[] buffer = new byte[8192];
int len;
long downloaded = 0;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
downloaded += len;
// 显示进度
if (fileSize > 0) {
int percent = (int) (downloaded * 100 / fileSize);
System.out.printf("\r下载进度: %d%%", percent);
}
}
System.out.println(); // 换行
}
}单文件上传(multipart/form-data)
java
public static void uploadFile(String uploadUrl, String filePath)
throws IOException {
File file = new File(filePath);
String boundary = "----WebKitFormBoundary" + System.currentTimeMillis();
try (
FileInputStream fis = new FileInputStream(file);
BufferedOutputStream out = new BufferedOutputStream(
new FileOutputStream(FileDescriptor.out))
) {
// 构建 multipart header
String header = "--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"file\"; " +
"filename=\"" + file.getName() + "\"\r\n" +
"Content-Type: application/octet-stream\r\n\r\n";
out.write(header.getBytes(StandardCharsets.UTF_8));
// 写入文件内容
byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
// 结束标记
out.write(("\r\n--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8));
}
}断点续传
断点续传的核心是 Range 请求头,告诉服务器「从哪个位置开始下载」:
java
public static void downloadWithResume(String serverUrl, String savePath)
throws IOException {
File localFile = new File(savePath);
long downloadedSize = localFile.exists() ? localFile.length() : 0;
HttpURLConnection conn = (HttpURLConnection) new URL(serverUrl).openConnection();
// 设置 Range 头,请求部分内容
if (downloadedSize > 0) {
conn.setRequestProperty("Range", "bytes=" + downloadedSize + "-");
}
int responseCode = conn.getResponseCode();
boolean resumeSupported = (responseCode == 206); // Partial Content
int fileSize = conn.getContentLength();
if (fileSize == -1) {
fileSize = (int) downloadedSize;
}
// 追加模式继续下载
try (
InputStream in = new BufferedInputStream(conn.getInputStream());
BufferedOutputStream out = new BufferedOutputStream(
new FileOutputStream(savePath, true)) // append = true
) {
byte[] buffer = new byte[8192];
int len;
long total = downloadedSize;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
total += len;
if (fileSize > 0) {
System.out.printf("\r续传进度: %d%%", total * 100 / fileSize);
}
}
System.out.println();
}
}大文件分片下载
java
public static void downloadInChunks(String serverUrl, String savePath,
int chunkSize) throws IOException {
URL url = new URL(serverUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
int fileSize = conn.getContentLength();
try (
RandomAccessFile raf = new RandomAccessFile(savePath, "rw");
BufferedInputStream in = new BufferedInputStream(conn.getInputStream())
) {
raf.setLength(fileSize);
byte[] buffer = new byte[chunkSize];
int len;
long downloaded = 0;
while ((len = in.read(buffer)) != -1) {
raf.write(buffer, 0, len);
downloaded += len;
if (fileSize > 0) {
System.out.printf("\r分片下载: %d%%", downloaded * 100 / fileSize);
}
}
}
}常用工具类封装
java
public class FileTransfer {
// 下载文件
public static void download(String url, String savePath,
ProgressListener listener) throws IOException {
// 实现下载逻辑,listener 回调进度
}
// 上传文件
public static void upload(String url, String filePath,
ProgressListener listener) throws IOException {
// 实现上传逻辑
}
// 断点续传下载
public static void downloadWithResume(String url, String savePath,
ProgressListener listener) throws IOException {
// 实现续传逻辑
}
public interface ProgressListener {
void onProgress(int percent);
void onComplete();
void onError(Exception e);
}
}记住这些模式:
| 需求 | 实现方式 |
|---|---|
| 普通下载 | BufferedInputStream + BufferedOutputStream |
| 显示进度 | downloaded * 100 / total |
| 断点续传 | Range 请求头 + RandomAccessFile 追加 |
| 文件上传 | multipart/form-data 格式 |
HTTP 下载用 BufferedInputStream,网络延迟大时增加缓冲区效果明显。
