字节流实战:文件上传下载
本节综合运用字节流的各类组件,实现带进度显示的文件上传下载功能。
文件下载:从服务器读取到本地
java
public static void downloadFile(String serverUrl, String savePath)
throws IOException {
URL url = new URL(serverUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(30000);
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();
}
}断点续传
中断后从上次下载的位置继续:
java
public static void downloadWithResume(String serverUrl, String savePath)
throws IOException {
File localFile = new File(savePath);
long downloadedSize = localFile.exists() ? localFile.length() : 0;
URL url = new URL(serverUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Range", "bytes=" + downloadedSize + "-");
int responseCode = conn.getResponseCode();
if (responseCode == 206) {
System.out.println("支持断点续传,从 " + downloadedSize + " 字节处继续");
} else if (responseCode == 200) {
downloadedSize = 0;
System.out.println("不支持断点续传,从头开始");
}
int fileSize = conn.getContentLength();
if (fileSize > 0) {
fileSize += downloadedSize;
}
try (
InputStream in = new BufferedInputStream(conn.getInputStream());
FileOutputStream fos = new FileOutputStream(savePath, true); // 追加模式
BufferedOutputStream out = new BufferedOutputStream(fos)
) {
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) {
int percent = (int) (total * 100 / fileSize);
System.out.printf("\r下载进度: %d%%", percent);
}
}
System.out.println();
}
}关键点:
Range: bytes=xxx-请求头告诉服务器从哪个字节开始206 Partial Content表示服务器支持断点续传FileOutputStream(path, true)以追加模式打开
大文件分片上传
对于超大文件,可以分片上传,每片独立重试:
java
public static void uploadInChunks(String uploadUrl, String filePath,
int chunkSizeMB) throws IOException {
File file = new File(filePath);
long fileSize = file.length();
int chunkSize = chunkSizeMB * 1024 * 1024;
int totalChunks = (int) ((fileSize + chunkSize - 1) / chunkSize);
try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[chunkSize];
int chunkIndex = 0;
int len;
while ((len = fis.read(buffer)) != -1) {
uploadChunk(uploadUrl, buffer, 0, len, chunkIndex, totalChunks);
chunkIndex++;
int percent = (int) (chunkIndex * 100 / totalChunks);
System.out.printf("\r分片上传进度: %d%% (%d/%d)",
percent, chunkIndex, totalChunks);
}
System.out.println();
}
}
private static void uploadChunk(String url, byte[] data,
int offset, int length, int chunkIndex, int totalChunks)
throws IOException {
// 实际实现中,这里是 HTTP 请求发送分片数据
// 省略具体 HTTP 实现
}内存占用监控
下载大文件时监控内存使用:
java
public static void downloadWithMemoryControl(String serverUrl, String savePath)
throws IOException {
URL url = new URL(serverUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
int fileSize = conn.getContentLength();
long startMemory = Runtime.getRuntime().totalMemory() -
Runtime.getRuntime().freeMemory();
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;
// 每 10MB 报告一次内存使用
if (downloaded % (10 * 1024 * 1024) < 8192) {
Runtime rt = Runtime.getRuntime();
long used = rt.totalMemory() - rt.freeMemory();
System.out.printf("内存使用: %.2f MB%n", used / 1024.0 / 1024.0);
}
}
}
}实战要点:
- 文件下载:
BufferedInputStream+BufferedOutputStream+ 进度显示 - 断点续传:
Range请求头 + 追加写入模式 - 大文件分片:分块读取上传,客户端和服务端协同
- 内存控制:注意缓冲区大小和总内存占用
