优化Spotify历史JSON数组查询:解决内存溢出与高CPU问题
解决Spotify播放历史统计的内存溢出与CPU占用过高问题
问题根源
- 全量加载JSON并将所有播放记录封装为
Song对象存入ArrayList,导致内存占用急剧膨胀(JSON文本转Java对象后内存占用通常是原文本的3-5倍甚至更高) - 大量对象存储引发频繁GC,进一步推高CPU占用
- 23MB的JSON对应数十万条播放记录,全量存储远超默认堆内存上限
核心优化方案
1. 流式解析JSON,避免全量加载
放弃将整个JSON一次性解析为对象列表,改用JSON流式解析API(如Jackson的JsonParser或Gson的JsonReader),逐个读取播放记录节点,处理完即丢弃,不保留所有对象。
2. 实时统计时长,无需存储所有记录
用HashMap<String, Long>替代ArrayList<Song>,以**歌曲唯一标识(如歌名+艺术家)**为Key,累计播放时长为Value,每读取一条记录就更新对应Key的Value,全程仅存统计结果而非原始记录。
具体代码实现示例
用Jackson流式解析+HashMap统计
import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; public class SpotifyStats { public static void main(String[] args) throws IOException { Map<String, Long> songTotalDuration = new HashMap<>(); JsonFactory factory = new JsonFactory(); JsonParser parser = factory.createParser(new File("your_spotify_history.json")); String currentField = null; String songName = null; String artistName = null; long durationMs = 0; while (parser.nextToken() != JsonToken.END_OBJECT) { String fieldName = parser.getCurrentName(); if ("items".equals(fieldName)) { parser.nextToken(); // 进入播放记录数组 while (parser.nextToken() != JsonToken.END_ARRAY) { // 解析单条播放记录的字段 while (parser.nextToken() != JsonToken.END_OBJECT) { currentField = parser.getCurrentName(); parser.nextToken(); // 移动到字段值位置 if ("trackName".equals(currentField)) { songName = parser.getText(); } else if ("artistName".equals(currentField)) { artistName = parser.getText(); } else if ("msPlayed".equals(currentField)) { durationMs = parser.getLongValue(); } } // 生成唯一键,避免同名不同歌被合并 String songKey = songName + "|" + artistName; // 累加当前歌曲的播放时长 songTotalDuration.put(songKey, songTotalDuration.getOrDefault(songKey, 0L) + durationMs); // 重置临时变量,准备处理下一条记录 songName = null; artistName = null; durationMs = 0; } } } parser.close(); // 遍历统计结果,找出总时长最长的歌曲 Map.Entry<String, Long> longestSong = null; for (Map.Entry<String, Long> entry : songTotalDuration.entrySet()) { if (longestSong == null || entry.getValue() > longestSong.getValue()) { longestSong = entry; } } if (longestSong != null) { String[] songInfo = longestSong.getKey().split("\\|"); System.out.println("播放总时长最长的歌曲:"); System.out.println("歌名:" + songInfo[0]); System.out.println("艺术家:" + songInfo[1]); System.out.println("总时长(毫秒):" + longestSong.getValue()); } } }
辅助优化:调整JVM堆内存(治标)
如果临时需要快速验证现有逻辑,可在Eclipse中调整运行配置:
- 右键项目 -> Run As -> Run Configurations
- 切换到Arguments标签,在VM arguments中添加:
-Xmx512m(将堆内存上限设为512MB,可根据需要调整为1G即-Xmx1024m) - 此方法仅为临时方案,无法从根本解决内存膨胀问题
优化效果
- 内存占用大幅降低:仅存储统计结果,而非数十万条
Song对象 - CPU占用显著下降:避免全量解析和频繁GC,流式解析更高效
- 彻底解决
OutOfMemoryError问题
内容的提问来源于stack exchange,提问作者kaiswonderland
相关产品推荐
相关产品推荐

