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

Stream处理百万级对象内存占用过高问题求助

问题描述

我正在生成某地理区域内的大量坐标(由3个数字组成)。原本以为Stream比List更高效,结果应用内存被快速占满,Observatory截图也证明了这一点。我需要的是一种能逐个存入、读取后就从结构中移除的结构,按理解Stream应该是这样的——添加新值时旧值会被移除,但实际情况是Stream(或其读取端)一直在膨胀,而我只是对返回的Stream调用了.length方法。

相关代码如下:

static Stream<Coords<num>> _generateTilesComputer(
    DownloadableRegion region,
  ) async* {
    List<List<double>> serialiseOutline(l) => (l as List)
        .cast<LatLng>()
        .map((e) => [e.latitude, e.latitude])
        .toList();

    final port = ReceivePort();

    final tilesCalc = await Isolate.spawn(
      region.type == RegionType.rectangle
          ? rectangleTiles
          : region.type == RegionType.circle
              ? circleTiles
              : lineTiles,
      {
        'port': port.sendPort,
        'rectOutline': region.type != RegionType.rectangle
            ? null
            : serialiseOutline(region.points),
        'circleOutline': region.type != RegionType.circle
            ? null
            : serialiseOutline(region.points),
        'lineOutline': region.type != RegionType.line
            ? null
            : (region.points as List<List<LatLng>>)
                .chunked(4)
                .map((e) => e.map(serialiseOutline)),
        'minZoom': region.minZoom,
        'maxZoom': region.maxZoom,
        'crs': region.crs,
        'tileSize': region.options.tileSize,
      },
    );

    await for (final Coords<num>? coord in port
        .skip(region.start)
        .take(((region.end ?? double.maxFinite) - region.start).toInt())
        .cast()) {
      if (coord == null) {
        port.close();
        tilesCalc.kill();
        return;
      }
      yield coord;
    }
  }
}
问题根源

调用Stream.length()时,Dart会将流中所有事件缓存到内部列表,直到流完全结束,以此来统计总数。这直接导致所有生成的坐标都被保留在内存中,完全违背了Stream流式处理、边产生边消费的设计初衷,内存自然会快速暴涨。

解决方案

1. 替换length()为手动计数

放弃调用Stream.length(),改为遍历流时手动维护计数器:

// 示例:手动统计流中事件数量
int count = 0;
await for (final coord in _generateTilesComputer(region)) {
  // 这里可以同时处理每个coord(比如存入数据库/文件)
  count++;
}
print('总坐标数:$count');

这种方式不会缓存任何事件,每处理一个就丢弃一个,内存占用始终保持在低水平。

2. 优化流与Isolate的资源管理

  • 修正serialiseOutline的逻辑错误:原代码中[e.latitude, e.latitude]应该是[e.latitude, e.longitude],避免坐标数据错误;
  • 用Isolate.exit()替代kill():kill()是强制终止,可能导致资源泄漏,exit()会让Isolate正常清理资源后退出;
  • 确保ReceivePort在流结束后及时关闭,避免端口资源残留。

修改后的_generateTilesComputer关键部分:

// 修正serialiseOutline方法
List<List<double>> serialiseOutline(l) => (l as List)
    .cast<LatLng>()
    .map((e) => [e.latitude, e.longitude]) // 修正为longitude
    .toList();

// 终止Isolate的逻辑修改
if (coord == null) {
  port.close();
  await Isolate.exit(tilesCalc); // 优雅退出
  return;
}

内容的提问来源于stack exchange,提问作者JaffaKetchup

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 19:10:19