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

Java 文件下載完整實戰:四種核心方式、在線預覽與斷點續傳考量

     編輯:Java 相關知識

文件下載是 Web 開發高頻功能。本文系統整理 Java Servlet 實現下載的四種核心方式,涵蓋本地檔、網路檔、支援在線預覽 (inline) 與純附件下載 (attachment),並補充關鍵 Header 設定、編碼處理、大檔流式傳輸與斷點續傳基礎知識。

核心 Header 對照表

Header用途範例值
Content-TypeMIME 類型application/octet-stream (強制下載) / image/jpeg (預覽)
Content-Disposition瀏覽器處理方式attachment; filename="test.pdf" / inline; filename="test.pdf"
Content-Length檔案大小 (bytes)102400
Accept-Ranges支援斷點續傳bytes
Content-Range斷點續傳範圍bytes 0-1023/102400

方式一:標準流式下載 (推薦通用)

public void download(HttpServletResponse response, String filePath) throws IOException {
  File file = new File(filePath);
  if (!file.exists()) {
    response.sendError(HttpServletResponse.SC_NOT_FOUND);
    return;
  }

  // 檔名編碼:RFC 5987 格式,相容中文
  String encodedName = URLEncoder.encode(file.getName(), StandardCharsets.UTF_8)
                              .replaceAll("\\+", "%20");
  
  response.reset();
  response.setContentType("application/octet-stream");
  response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedName);
  response.setHeader("Content-Length", String.valueOf(file.length()));
  response.setHeader("Accept-Ranges", "bytes");

  try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
       BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream())) {
    byte[] buffer = new byte[8192];
    int len;
    while ((len = bis.read(buffer)) != -1) {
      bos.write(buffer, 0, len);
    }
    bos.flush();
  }
}

方式二:支援在線預覽 / 下載切換

public void downloadFlexible(HttpServletResponse response, String filePath, boolean inline) 
    throws IOException {
  File file = new File(filePath);
  if (!file.exists()) { response.sendError(404); return; }

  String mime = URLConnection.guessContentTypeFromName(file.getName());
  if (mime == null) mime = "application/octet-stream";

  response.reset();
  response.setContentType(mime);
  String disposition = inline ? "inline" : "attachment";
  String encoded = URLEncoder.encode(file.getName(), "UTF-8").replace("+", "%20");
  response.setHeader("Content-Disposition", disposition + "; filename*=UTF-8''" + encoded);
  response.setContentLengthLong(file.length());

  try (InputStream in = new BufferedInputStream(new FileInputStream(file));
       OutputStream out = response.getOutputStream()) {
    byte[] buf = new byte[8192];
    int len;
    while ((len = in.read(buf)) != -1) out.write(buf, 0, len);
  }
}

方式三:下載網路資源 (代理下載)

public void downloadRemote(HttpServletResponse response, String remoteUrl, String saveAs) 
    throws IOException {
  URL url = new URL(remoteUrl);
  try (InputStream in = url.openStream();
       OutputStream out = response.getOutputStream()) {
    response.setContentType("application/octet-stream");
    response.setHeader("Content-Disposition", "attachment; filename=" + saveAs);
    byte[] buf = new byte[8192];
    int len;
    while ((len = in.read(buf)) != -1) out.write(buf, 0, len);
  }
}

方式四:Servlet 3.0+ 非阻塞異步下載 (大檔/高併發)

@WebServlet("/async-download")
public class AsyncDownloadServlet extends HttpServlet {
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
      throws IOException, ServletException {
    String filePath = req.getParameter("path");
    File file = new File(filePath);
    if (!file.exists()) { resp.sendError(404); return; }

    resp.setContentType("application/octet-stream");
    resp.setHeader("Content-Disposition", "attachment; filename=" + file.getName());
    resp.setContentLengthLong(file.length());

    // 開啟非阻塞模式
    ServletOutputStream out = resp.getOutputStream();
    out.setWriteListener(new WriteListener() {
      private final FileChannel channel = FileChannel.open(file.toPath(), StandardOpenOption.READ);
      private final ByteBuffer buffer = ByteBuffer.allocate(8192);

      @Override public void onWritePossible() throws IOException {
        while (out.isReady()) {
          int read = channel.read(buffer);
          if (read == -1) {
            out.write(buffer.array(), 0, buffer.position());
            out.close();
            channel.close();
            return;
          }
          buffer.flip();
          out.write(buffer.array(), 0, buffer.limit());
          buffer.clear();
        }
      }
      @Override public void onError(Throwable t) { t.printStackTrace(); }
    });
  }
}

關鍵最佳實踐

  1. 檔名編碼:統一用 filename*=UTF-8''encodedName (RFC 5987),避免 IE/Edge/舊版瀏覽器中文亂碼。
  2. 大檔必須流式:嚴禁 Files.readAllBytes() 或 byte[] buffer = new byte[(int)file.length()] 導致 OOM。
  3. 斷點續傳:需解析 Range Header,回傳 206 Partial Content + Content-Range,建議成熟專案直接用 HttpRange 庫或 Nginx X-Accel-Redirect 卸載。
  4. 安全性:嚴格校驗 filePath 防止目錄遍歷 (如 ../../etc/passwd),建議用預設下載目錄 + 檔名映射表。
  5. 關閉資源:全部採用 try-with-resources,確保流在異常時也能釋放。

總結

掌握 Content-Disposition 兩種模式、filename* 編碼、流式傳輸、斷點續傳 Header,即能應對 90% 下載場景。生產環境建議:小檔用標準流式、大檔配合 Nginx sendfile / X-Accel-Redirect 卸載應用伺服器、敏感檔案加簽名 URL 限時下載。

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