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

Java 下載文件 getContentLength() 返回 -1 問題分析與解決方案

     編輯:Java 相關知識

問題現象

使用 `HttpURLConnection` 下載文件時,`conn.getContentLength()` 返回 `-1`,無法獲取文件大小、顯示下載進度。

根因分析

HTTP 規範:`Content-Length` 標頭非強制。以下情況會缺失:

  • 服務端使用 **Chunked Transfer Encoding** 分塊傳輸
  • 動態生成內容(如 JSP、Servlet 流式輸出)
  • 壓縮傳輸(gzip/deflate)導致實際傳輸長度與原始長度不一致
  • 服務端配置未發送 Content-Length

無Content-Length響應頭

有Content-Length響應頭

解決方案

方案一:偽裝瀏覽器 User-Agent + 代理調試

// 1. 設置 User-Agent 模擬瀏覽器
conn.setRequestProperty("User-Agent", 
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36");

// 2. 可選:設置代理抓包分析(Fiddler/Charles)
String host = "127.0.0.1";
String port = "8888";
System.setProperty("proxySet", "true");
System.setProperty("proxyHost", host);
System.setProperty("proxyPort", port);

方案二:禁用壓縮傳輸(關鍵)


// 關鍵:要求服務端不壓縮,返回原始 Content-Length
conn.setRequestProperty("Accept-Encoding", "identity");

JavaDoc 證明:

By default, this implementation of HttpURLConnection requests that servers use gzip compression. Since getContentLength() returns the number of bytes transmitted, you cannot use that method to predict how many bytes can be read from getInputStream(). Instead, read that stream until it is exhausted: when read() returns -1.

默認請求 gzip 壓縮導致 `getContentLength()` 返回**傳輸字節數**(壓縮後),而非原始文件大小。設置 `identity` 禁用壓縮可獲取真實長度。

方案三:流式讀取不依賴 Content-Length(通用最佳實踐)

public void download(String urlStr, String destPath) throws IOException {
    HttpURLConnection conn = (HttpURLConnection) new URL(urlStr).openConnection();
    conn.setRequestProperty("Accept-Encoding", "identity"); // 禁用壓縮
    conn.setRequestProperty("User-Agent", "Mozilla/5.0...");
    
    try (InputStream in = conn.getInputStream();
         FileOutputStream out = new FileOutputStream(destPath)) {
        
        byte[] buffer = new byte[8192];
        int len;
        long total = 0;
        while ((len = in.read(buffer)) != -1) {
            out.write(buffer, 0, len);
            total += len;
            // 這裡可顯示進度:total / (Content-Length > 0 ? Content-Length : total)
        }
    }
}

進階:獲取 Content-Length 的替代方案

方法適用場景可靠性
`conn.getContentLengthLong()` (JDK 8+)支持 >2GB 文件同 getContentLength()
`conn.getHeaderField("Content-Length")`直接讀取標頭字符串同左
HTTP HEAD 請求預檢先發 HEAD 獲取長度,再 GET 下載高(需服務端支持 HEAD)
Range 斷點續傳大文件分段下載最高(需服務端支持 Range)

總結建議

  1. **首選方案二**:設置 `Accept-Encoding: identity` 禁用壓縮,配合 `User-Agent` 模擬瀏覽器
  2. **代碼層面**:**永遠不要依賴 ContentLength 判斷讀取結束**,而應以 `read() == -1` 為準
  3. **生產環境**:大文件下載建議實現 Range 斷點續傳,提升穩健性
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved