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

FS2飞行数据处理管道优化咨询:求代码简化与性能优化建议

代码简化建议
  • 简化过滤与转换逻辑:
    原代码先过滤出airtime和distance非空的行,后续又通过get取值(存在空指针风险)。可以直接将每行转换为Option[((String, String), (Double, Double))],仅保留有效值,逻辑更安全简洁:

    stream.flatMap { row =>
      for {
        air <- row.airtime
        dist <- row.distance
      } yield ((row.origin, row.destination), (air, dist))
    }
    
  • 直接累加求和而非存储所有元素:
    无需将所有(airtime, distance)对存入Vector再求和,而是在聚合时直接累加。利用Scala的updatedWith方法,对每个键对应的元组直接做累加操作,既简化代码又减少内存占用。

  • 简化流聚合流程:
    原代码将流拆分为块生成多个Map,再存入Vector合并。可以直接通过compile.fold完成全流聚合,避免中间Vector的存储开销:

    stream
      .flatMap(/* 转换为有效键值对 */)
      .compile
      .fold(Map.empty[(String,String), (Double, Double)]) { (acc, (key, value)) =>
        acc.updatedWith(key) {
          case None => Some(value)
          case Some((a, d)) => Some((a + value._1, d + value._2))
        }
      }
    
  • 替换线程不安全的日期解析:
    SimpleDateFormat是非线程安全的,建议使用Java 8+的java.time API,比如DateTimeFormatter( Immutable且线程安全):

    import java.time.LocalDate
    import java.time.format.DateTimeFormatter
    
    val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
    val date = LocalDate.parse(row(0), dateFormatter)
    
  • 移除冗余函数:
    stringToRows仅单纯调用castToSpeedRow,可直接将castToSpeedRow作为map的参数,或重命名为parseSpeedRow提升可读性。

性能优化方案
  • 避免频繁创建不可变Map:
    原代码每行创建一个新Map再合并,开销较大。可在块内使用scala.collection.mutable.Map累加,再转换为不可变Map,减少不可变Map的复制开销:

    stream.chunkN(10000)
      .map { chunk =>
        val mutableMap = scala.collection.mutable.Map.empty[(String,String), (Double, Double)]
        chunk.foreach { case (key, (air, dist)) =>
          mutableMap.updateWith(key) {
            case None => Some((air, dist))
            case Some((totalAir, totalDist)) => Some((totalAir + air, totalDist + dist))
          }
        }
        mutableMap.toMap
      }
    
  • 调整Chunk大小:
    当前Chunk大小为10000,可根据数据行大小和内存情况调整。过大的Chunk会增加GC压力,过小则会增加合并Map的次数,建议通过实际测试找到最优值(如20000或50000)。

  • 并行处理(谨慎使用):
    针对超大数据量,可使用parJoin进行并行块处理,但需注意线程安全。并行适合CPU密集型聚合,IO密集型场景提升有限:

    stream
      .chunkN(10000)
      .parJoin(4) // 4个并行线程
      .evalMap { chunk =>
        IO {
          val mutableMap = scala.collection.mutable.Map.empty[(String,String), (Double, Double)]
          chunk.foreach { case (key, (air, dist)) =>
            mutableMap.updateWith(key) {
              case None => Some((air, dist))
              case Some((totalAir, totalDist)) => Some((totalAir + air, totalDist + dist))
            }
          }
          mutableMap.toMap
        }
      }
      .compile
      .fold(Map.empty[(String,String), (Double, Double)]) { (acc, chunkMap) =>
        chunkMap.foldLeft(acc) { (current, (key, (air, dist))) =>
          current.updatedWith(key) {
            case None => Some((air, dist))
            case Some((totalAir, totalDist)) => Some((totalAir + air, totalDist + dist))
          }
        }
      }
    
  • 减少内存占用:
    直接累加求和而非存储所有(airtime, distance)对,能大幅减少内存使用,避免大数据量场景下的OOM问题。

优化后的示例代码
import cats.effect.{IO, IOApp}
import fs2.{Stream, text}
import fs2.io.file.{Files, Path}
import cats.syntax.all._
import java.time.LocalDate
import java.time.format.DateTimeFormatter

object FS2Optimized extends IOApp.Simple {

  case class SpeedRow(
      recordDate: LocalDate,
      origin: String,
      destination: String,
      distance: Option[Double],
      airtime: Option[Double]
  )

  val file = "src/main/FlightData/2018.csv"
  private val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")

  def parseSpeedRow(row: Vector[String]): SpeedRow = {
    val date = LocalDate.parse(row(0), dateFormatter)
    val origin = row(3)
    val destination = row(4)
    val airTime = row(20).toDoubleOption
    val distance = row(21).toDoubleOption
    SpeedRow(date, origin, destination, distance, airTime)
  }

  def getFileStream(path: String): Stream[IO, SpeedRow] = {
    val filePath = Path(path)
    Files[IO].readUtf8Lines(filePath)
      .drop(1) // 跳过表头
      .dropLast // 跳过空行
      .map(line => parseSpeedRow(line.split(",").map(_.trim).toVector))
  }

  override def run: IO[Unit] = {
    val aggregated = getFileStream(file)
      .flatMap { row =>
        for {
          air <- row.airtime
          dist <- row.distance
        } yield ((row.origin, row.destination), (air, dist))
      }
      .chunkN(10000)
      .map { chunk =>
        val mutableMap = scala.collection.mutable.Map.empty[(String, String), (Double, Double)]
        chunk.foreach { case (key, (air, dist)) =>
          mutableMap.updateWith(key) {
            case None => Some((air, dist))
            case Some((totalAir, totalDist)) => Some((totalAir + air, totalDist + dist))
          }
        }
        mutableMap.toMap
      }
      .compile
      .fold(Map.empty[(String, String), (Double, Double)]) { (acc, chunkMap) =>
        chunkMap.foldLeft(acc) { (current, (key, (air, dist))) =>
          current.updatedWith(key) {
            case None => Some((air, dist))
            case Some((totalAir, totalDist)) => Some((totalAir + air, totalDist + dist))
          }
        }
      }

    aggregated.flatMap { result =>
      IO.println(s"处理完成,共${result.size}条航线记录")
    }
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 19:25:03