You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

优化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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 16:42:32