国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

Spring Boot 二進制數據傳輸:從內存優化到流式處理與斷點續傳

開發 前端
為避免緩存問題,可通過 ETag 或 Last-Modified 實現條件請求。此外,支持 HTTP 范圍請求(ResourceRegion)可實現斷點續傳,顯著提升大文件傳輸體驗。本篇文章將結合代碼示例,系統化講解二進制數據傳輸的核心技術與優化策略。

環境:SpringBoot3.4.2

1. 簡介

在 Spring Boot 中高效傳輸二進制數據(如文件、圖片、動態生成內容)需關注 Content-Type 設置、流式處理、緩存控制及分塊下載。直接讀取文件到內存(如 byte[])適用于小文件,但大文件應使用 InputStreamResource 或 StreamingResponseBody 避免內存溢出。動態內容可通過 HttpServletResponse 直接寫入響應流,壓縮文件則可用 ZipOutputStream 實時生成。為避免緩存問題,可通過 ETag 或 Last-Modified 實現條件請求。此外,支持 HTTP 范圍請求(ResourceRegion)可實現斷點續傳,顯著提升大文件傳輸體驗。本篇文章將結合代碼示例,系統化講解二進制數據傳輸的核心技術與優化策略。

2.實戰案例

2.1 正確設置Content-Type

在響應二進制內容時,Content-Type頭部告知客戶端即將接收的數據類型。若缺少此信息,瀏覽器可能會嘗試將二進制數據作為文本顯示,或在不知如何處理的情況下直接下載。如下示例:

@GetMapping("/download1")
public ResponseEntity<byte[]> download1() throws IOException {
  Path path = Paths.get("E:/技術架構.pdf");
  byte[] pdfData = Files.readAllBytes(path);
  String fileName = "技術架構.pdf";
  fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8)
          .replaceAll("\\+", "%20");
  return ResponseEntity.ok()
      .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"")
      .contentType(MediaType.APPLICATION_PDF)
      .body(pdfData);
}

首先將整個文件讀取到內存中,通過Content-Disposition頭指示瀏覽器提示下載而非嘗試內聯渲染。對于不適合直接顯示的格式(如歸檔文件或加密數據),這將非常有用。

示例2:

@GetMapping("/images/logo")
public ResponseEntity<byte[]> fetchLogo() throws IOException {
  ClassPathResource resource = new ClassPathResource("static/7.png") ;
  byte[] bytes;
  try (InputStream in = resource.getInputStream()) {
    bytes = in.readAllBytes();
  }
  return ResponseEntity.ok()
      .contentType(MediaType.IMAGE_PNG)
      .contentLength(resource.contentLength())
      .body(bytes);
}

該示例將直接在瀏覽器中展示圖片。

2.2 流式處理大文件

當文件超過幾兆時,在發送時將內容全部加載到內存中這將帶來很大的風險。首先,這種做法會消耗不必要的內存,并在等待文件讀取完成時導致響應卡頓。流式傳輸允許數據以較小塊的形式傳輸,無需一次性存儲全部內容。Spring Boot可通過InputStreamResource、FileSystemResource控制流式傳輸。如下示例:

@GetMapping("/download2")
public ResponseEntity<InputStreamResource> download2() throws IOException {
  File file = new File("E:/技術架構.pdf");
  String fileName = "技術架構.pdf";
  fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8)
          .replaceAll("\\+", "%20");
  InputStreamResource resource = new InputStreamResource(new FileInputStream(file));
  return ResponseEntity.ok()
      .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"")
      .contentType(MediaType.APPLICATION_OCTET_STREAM)
      .contentLength(file.length())
      .body(resource);
}

此方法實現了直接流式傳輸至客戶端,不會被復制到內存中。同時客戶端可以統計設置的Content-Length判斷文件是否接收完成。

使用StreamingResponseBody實時生成響應內容

@GetMapping("/export/csv")
public ResponseEntity<StreamingResponseBody> exportCsv() {
  StreamingResponseBody stream = outputStream -> {
    String header = "姓名,年齡,郵箱\n";
    outputStream.write(header.getBytes(java.nio.charset.StandardCharsets.UTF_8));
    outputStream.write("pack,33,pack@gmail.com\n".getBytes(java.nio.charset.StandardCharsets.UTF_8));
    outputStream.write("xg,32,xg@qq.com\n".getBytes(java.nio.charset.StandardCharsets.UTF_8));
    outputStream.write("pack_xg,40,pack_xg@163.com\n".getBytes(java.nio.charset.StandardCharsets.UTF_8));
  };
  return ResponseEntity.ok()
      .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"users.csv\"")
      .contentType(MediaType.parseMediaType("text/csv; charset=UTF-8"))
      .body(stream) ;
}

這避免了臨時文件的使用,即使在數據生成過程中也能保持響應流暢。對于需要保持低內存占用的大型報告或日志導出而言,此方法非常實用。

2.3 響應動態生成的文件

并非所有文件都來自磁盤。許多服務會在內存中生成內容,例如PDF、圖像或壓縮數據集。當內容在運行時創建時,可直接寫入響應流而無需預先保存。如下示例:

@GetMapping("/generate/report")
public void generateReport(HttpServletResponse response) throws IOException {
  response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE) ;
  String filename = "報告.txt";
  filename = URLEncoder.encode(filename, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
  response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
  try (OutputStream out = response.getOutputStream()) {
    byte[] reportBytes = createText(); // 假設這個方法生成文本內容
    out.write(reportBytes);
    out.flush();
  } 
}
private byte[] createText() {
  return "這是報告內容\n第二行內容".getBytes(StandardCharsets.UTF_8);
}

如上實現沒有文件IO操作,這在處理動態生成的文檔或分析導出時,這種做法很常見。輸出流直接連接到HTTP響應,既降低了延遲,又減少了磁盤訪問。

如果你需要同時下載多個文件并且是希望生成一個壓縮文件進行下載,那么你可以采用如下的方式:

@GetMapping("/generate/zip")
public ResponseEntity<StreamingResponseBody> generateZip() {
  StreamingResponseBody stream = output -> {
    try (ZipOutputStream zipOut = new ZipOutputStream(output)) {
      ZipEntry entry = new ZipEntry("summary.txt");
      zipOut.putNextEntry(entry);
      zipOut.write("這里是摘要內容".getBytes(java.nio.charset.StandardCharsets.UTF_8));
      entry = new ZipEntry("content.txt");
      zipOut.putNextEntry(entry);
      zipOut.write("主體內容".getBytes(java.nio.charset.StandardCharsets.UTF_8));
      zipOut.closeEntry();
      zipOut.finish() ;
    }
  };
  return ResponseEntity.ok()
      .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"export.zip\"")
      .contentType(MediaType.APPLICATION_OCTET_STREAM)
      .body(stream);
}

最終生成的壓縮文件如下:

圖片圖片

2.4 避免瀏覽器緩存

瀏覽器傾向于保留已下載的文件。這對圖像或靜態資源很有幫助,但對于經常更新的數據(如每日報告或導出文件)則不然。如果響應未告知瀏覽器更新,用戶可能在不知情的情況下下載過時文件。為確保始終提供最新數據,可在響應中直接添加緩存標頭。如下示例:

@GetMapping("/download3")
public ResponseEntity<byte[]> download3() throws IOException {
  Path path = Paths.get("E:/技術架構.pdf");
  byte[] data = Files.readAllBytes(path);
  String fileName = "技術架構.pdf";
  fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8)
          .replaceAll("\\+", "%20");
  ZonedDateTime expiresTime = ZonedDateTime.now(ZoneId.systemDefault()).plusSeconds(30);
  String expiresHeader = expiresTime.format(DateTimeFormatter.RFC_1123_DATE_TIME);
  return ResponseEntity.ok()
          .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"")
          .header(HttpHeaders.EXPIRES, expiresHeader)
          .contentType(MediaType.APPLICATION_PDF)
          .body(data);
}

通過expires設置有效期為30s,運行該接口瀏覽器查看執行情況:

圖片圖片

過期后又會從服務器進行下載文件。在有效期時間內并不會請求我們的實際接口。

當我們需要更加精確的控制緩存時,可通過更具選擇性的方法。條件緩存標頭(如ETag或Last-Modified)會指示瀏覽器在復用文件前重新驗證其有效性。如下示例:

@GetMapping("/getConfig")
public ResponseEntity<Resource> getConfigFile(HttpServletRequest request) throws IOException {
  FileSystemResource resource = new FileSystemResource("E:/config.json");
  long lastModified = resource.lastModified();
  String eTag = "\"" + lastModified + "\"";
  String ifNoneMatch = request.getHeader(HttpHeaders.IF_NONE_MATCH);
  boolean tagMatches = ifNoneMatch != null && ifNoneMatch.contains(eTag);
  if (tagMatches) {
    return ResponseEntity.status(HttpStatus.NOT_MODIFIED)
        .eTag(eTag)
        .lastModified(lastModified)
        .build();
  }
  return ResponseEntity.ok()
      .eTag(eTag)
      .lastModified(lastModified)
      .contentType(MediaType.APPLICATION_JSON)
      .body(resource);
}

如上實現,在不影響文件更新性的前提下減少了不必要的帶寬消耗。當文件未變更時,客戶端將收到304未修改響應;而更新后的內容則正常發送。上面接口運行結果:

圖片圖片

當文件config.json發生變化后才會再次讀取文件內容發送。

對于靜態資源,我們可以通過如下配置進行全局設置:

@Configuration
public class CacheConfig implements WebMvcConfigurer {
  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/resources/**")
      .addResourceLocations("classpath:/public/")
      .setCacheControl(CacheControl.maxAge(Duration.ofDays(1)));
  }
}

2.5 范圍分塊下載

當需要高效傳輸大文件(如視頻、音頻、大型軟件包)或支持斷點續傳時,HTTP范圍請求可按需下載文件片段。例如視頻拖動進度條、下載工具暫停后恢復,或僅傳輸客戶端未緩存的部分,顯著節省帶寬和時間。如下示例:

@GetMapping("/download4")
public ResponseEntity<ResourceRegion> download4(@RequestHeader HttpHeaders headers) throws IOException {
  // 1.獲取系統資源文件
  FileSystemResource resource = new FileSystemResource("e:/doubao.exe");
  if (!resource.exists()) {
    return ResponseEntity.notFound().build();
  }
  long contentLength = resource.contentLength();
  MediaType mediaType = MediaTypeFactory.getMediaType(resource)
          .orElse(MediaType.APPLICATION_OCTET_STREAM);
  // 2.處理范圍請求(支持多范圍請求,但僅返回第一個范圍)
  List<HttpRange> ranges = headers.getRange();
  if (ranges.isEmpty()) {
    // 完整文件下載
    return ResponseEntity.ok()
        .header("Accept-Ranges", "bytes")
        .contentType(mediaType)
        .contentLength(contentLength)
        .body(new ResourceRegion(resource, 0, contentLength));
  }
  // 3.嚴格校驗范圍有效性(避免越界)
  HttpRange range = ranges.get(0);
  long start = range.getRangeStart(contentLength);
  long end = range.getRangeEnd(contentLength);
  if (start > end || start < 0 || end >= contentLength) {
    return ResponseEntity.status(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE)
        .header("Content-Range", "bytes */" + contentLength)
        .build();
  }
  // 4.優化分塊大小
  long rangeLength = end - start + 1;
  long chunkSize = Math.min(10 * 1024 * 1024, rangeLength); // 默認10MB,適應大文件
  ResourceRegion region = new ResourceRegion(resource, start, chunkSize);
  return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT)
          .header("Content-Range", "bytes " + start + "-" + (start + chunkSize - 1) + "/" + contentLength)
          .header("Content-Disposition", "attachment; filename=\"" + resource.getFilename() + "\"")
          .contentType(mediaType)
          .body(region);
}

注意,這里的返回值類型不能使用ResponseEntity<?> 通配符,否則將會報錯。

責任編輯:武曉燕 來源: Springboot全家桶實戰案例
相關推薦

2011-03-04 16:41:57

FileZilla

2023-04-12 16:20:00

同步數據異步數據傳輸

2020-06-12 07:50:15

大數據

2023-03-09 12:04:38

Spring文件校驗

2018-10-22 14:37:16

二進制數據存儲

2023-09-18 23:50:25

二進制文件裁剪Layout

2017-08-08 08:45:44

前端文件斷點續傳

2013-07-22 14:02:17

iOS開發ASIHTTPRequ

2010-04-07 14:54:38

2009-05-26 11:24:00

2011-03-04 13:22:10

FileZilla

2017-12-21 10:52:52

nginx日志還原

2013-11-26 15:51:45

Android編程藍牙數據傳輸

2015-10-14 09:44:55

TCP網絡協議數據傳輸

2023-06-20 19:57:13

2009-08-28 15:38:49

C#實現斷點續傳

2021-01-06 08:32:30

DTS數據傳輸數據庫

2010-07-13 15:55:12

FTP數據傳輸模式

2020-10-09 10:07:46

開發技能工具

2023-12-26 15:10:00

處理二進制文件
點贊
收藏

51CTO技術棧公眾號

成人在线观看你懂的| 麻豆av在线导航| 久久中文资源| 日韩一级免费一区| 97影院理论午夜| 激情成人午夜视频| 国产精品视频在线观看| 日韩中文视频| 欧美三级视频在线观看| 99re在线视频播放| 国产成人av电影在线播放| 极品校花啪啪激情久久| 一区二区美女| 欧美大奶子在线| 中文在线8资源库| 欧美乱妇一区二区三区不卡视频| 激情五月俺来也| 高清在线不卡av| 色狠狠久久av五月综合| 在线中文字幕第一区| 午夜精品久久久99热福利| 88xx成人永久免费观看| 日韩视频免费观看高清在线视频| 欧洲毛片在线| 精品久久久久久久久久久久久久 | 久久久久中文字幕2018| a一区二区三区| 欧美性生活影院| 小水嫩精品福利视频导航| 亚洲美女屁股眼交3| 亚洲精品视频导航| 91丝袜呻吟高潮美腿白嫩在线观看| 一区二区在线观看网站| 久久午夜精品一区二区| 久久久久久国产精品免费免费| 亚洲精品国产首次亮相| 成人h视频在线| 999精品一区| 亚洲一区久久久| 小处雏高清一区二区三区| 国产精品永久免费观看| 天天射成人网| 福利视频一区二区三区| 黑丝一区二区| 国产视频一区二区不卡| 欧美亚洲不卡| 精品一卡二卡三卡四卡日本乱码 | 欧美大片黄色| 亚洲精品电影网| 影视一区二区三区| 久久九九亚洲综合| 国产精品99久久免费观看| 91国在线精品国内播放| 欧美久久精品一级c片| 91精品视频免费| 亚洲欧美日韩在线观看a三区| 欧美另类网站| 国产不卡视频一区| 色www免费视频| 性做久久久久久久久| 91青青在线视频| 亚洲国产精品一区二区三区 | 色狠狠综合天天综合综合| 国产视频精选在线| 精品免费99久久| 91精品网站在线观看| 69av成年福利视频| 欧美二区视频| 中国人体摄影一区二区三区| www.av精品| 九色porny在线观看| 在线播放91灌醉迷j高跟美女| 校园春色亚洲| 欧美综合一区第一页| 狠狠噜噜久久| 男女裸体影院高潮| 亚洲女爱视频在线| 97超碰资源站在线观看| 色噜噜狠狠狠综合曰曰曰88av| 久久综合社区| 鲁鲁狠狠狠7777一区二区| 国产成人精品一区二区三区四区 | jizz在线观看中文| 亚洲视频在线观看视频| 外国成人在线视频| 鲁丝一区鲁丝二区鲁丝三区| 国产a视频精品免费观看| 欧美承认网站| 亚洲精选一区二区| 欧洲美女日日| 在线一区日本视频| 亚洲激情图片qvod| 两个人看的在线视频www| 97超级碰碰人国产在线观看| 国产精品美女久久久浪潮软件| 黄www在线观看| 欧美日本精品一区二区三区| 亚洲国产中文在线| 区一区二区三区中文字幕| 亚洲天堂成人在线观看| 亚洲私拍视频| 四虎成人av| 欧美这里只有精品| 国产欧美日韩另类一区| 国产91在线视频观看| 日韩五码在线观看| 亚欧色一区w666天堂| 中文字幕人成乱码在线观看| 国产精品久久久久999| 精品一区二区三区在线视频| 两个人hd高清在线观看| 亚洲视频在线观看视频| 国产综合自拍| 3d黄动漫网站| 有码中文亚洲精品| 国产精品日本| 国产污污在线观看| 久久精品2019中文字幕| 视频一区免费在线观看| 伊人色综合网| 久久免费成人精品视频| 国产一区 二区 三区一级| 酒色婷婷桃色成人免费av网| 欧美成人在线免费| 国产一区二区剧情av在线| аⅴ资源新版在线天堂| 国产成人精品av在线| www日韩大片| 色香欲www7777综合网| 蜜桃欧美视频| 在线观看成人小视频| 精品国产一区二区三区四区| 亚洲中文字幕久久精品无码喷水| 欧美一级高清片在线观看| 色中色综合网| 成人综合网址| 欧美激情一二三| 不卡一卡二卡三乱码免费网站| 性xxxfreexxxx性欧美| av日韩中文字幕| 亚洲成av人片在线观看无码| 欧美午夜寂寞| 精品久久久久久久无码| 色婷婷av一区二区三区在线观看| 久久国产乱子精品免费女| 中文字幕在线观看网站| 就去色蜜桃综合| 欧美精品乱码久久久久久按摩| 综合激情在线| 久草在线网址| 亚洲一区二区中文字幕| 欧美性猛交xxxx乱大交极品| 日本久久精品| 中出在线观看| 亚洲一区二区自拍| 欧美亚洲国产怡红院影院| 午夜精品婷婷| 日本亚洲精品| 亚洲v国产v在线观看| 亚洲精品国产美女| 国产中文字幕精品| 国产精品久久亚洲不卡| 熟女少妇在线视频播放| 欧美成人精品在线播放| 久久久久久亚洲综合影院红桃 | 国产97在线观看| 亚洲人成网站影音先锋播放| 美女毛片一区二区三区四区最新中文字幕亚洲| 中文字幕在线观看第三页| 国产综合在线看| 亚洲在线一区二区三区| 国产精品精品国产一区二区| 亚洲成人影院在线观看| 国内精品视频免费| 精品国产不卡一区二区三区| 国模一区二区三区白浆| 久久天堂影院| 二区中文字幕| 99视频在线免费观看| 日韩一区二区在线观看视频播放| 久久国产日韩欧美精品| 91麻豆精品| 中文字幕4区| 久久久久久九九| 亚洲男女性事视频| 亚洲国产精品黑人久久久| 五月综合久久| 日韩在线观看www| www.亚洲一区二区| 欧美极品美女视频网站在线观看免费 | 国产精品自在线| 欧美色视频在线| 国产精品一区二区三区乱码| 色悠久久久久综合先锋影音下载| 欧美5-7sexvideos处| 奇米888一区二区三区| 久久国产视频网站| 欧美日韩国产精品专区| 日韩精品亚洲专区| 亚洲精品不卡在线观看|