首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 分段讀取檔案並透過 HTTP Multipart 上傳:大檔案斷點續傳核心邏輯

     編輯:Java 相關知識

應用場景

大檔案(GB 級)上傳不可一次性讀入記憶體,需分段讀取並以 multipart/form-data 串流上傳,支援斷點續傳、並行分片。

核心步驟

  1. RandomAccessFile 定位分段:seek(offset) + read(bytes)
  2. HttpURLConnection 發送 Multipart:手動組裝 Boundary、Header、Body
  3. 串流寫入:邊讀邊寫,不佔用堆外記憶體

完整上傳邏輯

public void uploadChunk(String actionUrl, File uploadFile, long startOffset, long chunkSize, 
                        String fileName, String boundary) throws IOException {
    
    URL url = new URL(actionUrl);
    HttpURLConnection con = (HttpURLConnection) url.openConnection();
    con.setDoInput(true);
    con.setDoOutput(true);
    con.setUseCaches(false);
    con.setRequestMethod("POST");
    con.setRequestProperty("Connection", "Keep-Alive");
    con.setRequestProperty("Charset", "UTF-8");
    con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

    try (DataOutputStream dos = new DataOutputStream(con.getOutputStream());
         RandomAccessFile raf = new RandomAccessFile(uploadFile, "r")) {

        // 定位到分段起始位置
        raf.seek(startOffset);
        long remaining = chunkSize;
        byte[] buffer = new byte[8192];  // 8KB 緩衝

        // 寫入 Multipart Header
        dos.writeBytes("--" + boundary + "\r\n");
        dos.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\"" + fileName + "\"\r\n");
        dos.writeBytes("Content-Type: application/octet-stream\r\n");
        dos.writeBytes("Content-Range: bytes " + startOffset + "-" + (startOffset + chunkSize - 1) + "/" + uploadFile.length() + "\r\n");
        dos.writeBytes("\r\n");

        // 串流讀寫檔案內容
        int read;
        while (remaining > 0 && (read = raf.read(buffer, 0, (int)Math.min(buffer.length, remaining))) != -1) {
            dos.write(buffer, 0, read);
            remaining -= read;
        }

        // 結束 Boundary
        dos.writeBytes("\r\n--" + boundary + "--\r\n");
        dos.flush();

        // 檢查回應碼
        int respCode = con.getResponseCode();
        if (respCode != 200) {
            try (BufferedReader br = new BufferedReader(new InputStreamReader(con.getErrorStream()))) {
                String line;
                StringBuilder err = new StringBuilder();
                while ((line = br.readLine()) != null) err.append(line);
                throw new IOException("Upload failed: " + respCode + " " + err);
            }
        }
    }
}

關鍵細節解析

細節說明
Boundary隨機字串(如 UUID),前綴 --,結尾 --boundary--
Content-Range標準 HTTP Range 標頭,伺服端據此合併分片
RandomAccessFileseek() 定位 O(1),支援任意位移讀取,斷點續傳核心
緩衝區大小8KB-64KB 平衡系統呼叫次數與記憶體佔用
Keep-Alive多片上傳複用連線,減少 TLS 握手開銷
錯誤處理讀取 ErrorStream 獲取伺服端詳細錯誤訊息

客戶端分片策略建議

// 簡單固定分片
long fileSize = file.length();
long chunkSize = 5 * 1024 * 1024;  // 5MB
int totalChunks = (int) Math.ceil((double) fileSize / chunkSize);

for (int i = 0; i < totalChunks; i++) {
    long offset = i * chunkSize;
    long size = Math.min(chunkSize, fileSize - offset);
    uploadChunk(url, file, offset, size, fileName, boundary);
    // 記錄已上傳片段索引至本地 SQLite/LevelDB,支援中斷續傳
}

伺服端合併邏輯(概念)

  1. 接收分片 → 暫存目錄 /tmp/uploads/{fileId}/part_{index}
  2. 資料庫記錄 fileId, totalChunks, receivedChunks(bitmap/set)
  3. 收齊所有分片 → 按序 Files.copy(part, mergedFile) 合併
  4. 驗證 MD5/SHA256 → 移動至正式儲存、清理暫存

總結

分段上傳核心在於 RandomAccessFile 定位 + Multipart 串流組裝 + Content-Range 標準化。Java 原生 HttpURLConnection 雖較繁瑣,但零依賴、完全可控,適合嵌入式、Android 或無法引入 OkHttp/Apache HttpClient 的環境。生產系統建議封裝為 Uploader 元件,統一處理重試、限流、進度回調、校驗等橫切關注點。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved