Spring Boot+Vue.js加载大JSON内存溢出及分页方案问询
大JSON文件流式传输内存溢出与前端分页解决方案
问题背景
使用Spring Boot以TEXT_EVENT_STREAM流式传输210MB以上的JSON文件到Vue.js前端时,后端触发Java堆内存溢出错误,前端加载全量数据后出现严重卡顿,需解决内存占用问题并实现分页展示。
问题根源分析
- 后端:原代码直接按字节流转发整个JSON文件,Spring容器可能会缓冲响应内容,导致大文件全部加载到内存引发溢出;且未对JSON结构做流式解析,无法实现逐对象输出。
- 前端:使用axios一次性接收全量响应并存入数组,渲染大量DOM节点导致页面卡顿。
后端优化:解决内存溢出
采用Jackson流式API解析JSON数组,逐个输出对象作为SSE事件,避免全量数据加载到内存,同时禁用响应缓冲。
修改后的Spring Boot代码
import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; @RestController @RequestMapping("/apii") public class DataController { @RequestMapping(value = "/getData", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public void getData(HttpServletResponse response) throws IOException { // 配置SSE响应头,禁用缓存,保持连接 response.setContentType(MediaType.TEXT_EVENT_STREAM_VALUE); response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache"); response.setHeader(HttpHeaders.CONNECTION, "keep-alive"); File jsonFile = new File("My_Data.json"); JsonFactory factory = new JsonFactory(); // 流式解析JSON,自动关闭资源 try (JsonParser parser = factory.createParser(jsonFile); var writer = response.getWriter()) { // 跳过JSON数组起始标记 [ if (parser.nextToken() == JsonToken.START_ARRAY) { // 遍历数组内的每个对象 while (parser.nextToken() != JsonToken.END_ARRAY) { // 读取单个JSON对象并转为字符串 String jsonObject = parser.readValueAsTree().toString(); // 按SSE格式输出单个对象,每个事件单独换行 writer.write("data: " + jsonObject + "\n\n"); // 强制刷新输出流,避免容器缓冲 writer.flush(); } } } } }
前端优化:流式接收+分页展示
改用EventSource接收SSE流式数据,逐对象存储;同时实现分页逻辑,仅渲染当前页数据,减少DOM节点数量。
修改后的Vue.js代码
export default { data() { return { currentItems: [], // 当前页展示的数据 allReceivedItems: [], // 存储已接收的所有数据 currentPage: 1, pageSize: 50, // 每页展示50条 eventSource: null }; }, mounted() { this.initSSEConnection(); }, beforeUnmount() { // 组件销毁前关闭SSE连接 if (this.eventSource) { this.eventSource.close(); } }, methods: { initSSEConnection() { this.eventSource = new EventSource('http://localhost:8080/apii/getData'); // 接收单个SSE事件 this.eventSource.onmessage = (event) => { const item = JSON.parse(event.data); this.allReceivedItems.push(item); // 更新当前页数据 this.refreshCurrentPage(); }; // 处理连接错误 this.eventSource.onerror = (error) => { console.error('SSE连接异常:', error); this.eventSource.close(); }; }, refreshCurrentPage() { const startIndex = (this.currentPage - 1) * this.pageSize; const endIndex = startIndex + this.pageSize; this.currentItems = this.allReceivedItems.slice(startIndex, endIndex); }, // 切换到指定页 switchPage(pageNum) { this.currentPage = pageNum; this.refreshCurrentPage(); }, // 上一页 prevPage() { if (this.currentPage > 1) { this.currentPage--; this.refreshCurrentPage(); } }, // 下一页 nextPage() { const totalPages = Math.ceil(this.allReceivedItems.length / this.pageSize); if (this.currentPage < totalPages) { this.currentPage++; this.refreshCurrentPage(); } } }, computed: { totalPages() { return Math.ceil(this.allReceivedItems.length / this.pageSize); } } };
修改后的前端展示代码(带分页控件)
<div class="data-container"> <table class="content-table"> <thead> <tr> <th>Data1</th> <th>Data2</th> <th>Data3</th> <th>Data4</th> <th>Data5</th> <th>Data6</th> <th>More</th> </tr> </thead> <tbody> <tr v-for="item in currentItems" :key="item.id"> <td>{{ item.Data1 }}</td> <td>{{ item.Data2 }}</td> <td>{{ item.Data3 }}</td> <td>{{ item.Data4 }}</td> <td>{{ item.Data5 }}</td> <td>{{ item.Data6 }}</td> <td> <button @click="afficher(item)"> <a href="/Detail">...</a> </button> </td> </tr> </tbody> </table> <!-- 分页操作栏 --> <div class="pagination-bar"> <button @click="prevPage" :disabled="currentPage === 1">上一页</button> <span>第 {{ currentPage }} 页 / 共 {{ totalPages }} 页</span> <button @click="nextPage" :disabled="currentPage === totalPages">下一页</button> </div> </div>
额外优化建议
- 后端:若JSON文件超大型(千万级数据),建议将数据存入数据库,通过分页查询接口替代文件流式读取,进一步降低内存占用。
- 前端:数据量达百万级时,推荐使用虚拟滚动组件(如vue-virtual-scroller),仅渲染当前视口内的DOM节点,彻底解决页面卡顿问题。
内容的提问来源于stack exchange,提问作者Mass Mak
相关产品推荐
相关产品推荐

