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

如何用Scala将大型JSON文件转为二进制并高效读取?

Scala处理大型JSON文件:转换为二进制格式并高效读取

方案一:使用Jackson Smile(二进制JSON)

Smile是Jackson提供的二进制JSON格式,兼容原有JSON结构,转换流程简单且性能优异,无需额外定义数据结构IDL。

步骤1:添加依赖

在build.sbt中加入以下依赖:

libraryDependencies ++= Seq(
  "com.fasterxml.jackson.core" % "jackson-core" % "2.15.2",
  "com.fasterxml.jackson.core" % "jackson-databind" % "2.15.2",
  "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.15.2",
  "com.fasterxml.jackson.dataformat" % "jackson-dataformat-smile" % "2.15.2"
)

步骤2:定义数据模型

用Scala case class映射JSON结构:

case class DataRecord(a: Int, b: Int, c: String, d: Int, e: Int)

步骤3:JSON转Smile二进制(流式处理)

通过Jackson流式API逐行解析JSON,避免加载整个文件到内存:

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.smile.SmileFactory
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import scala.util.Using

object JsonToSmile {
  def main(args: Array[String]): Unit = {
    val jsonPath = "/path/to/your/large.json"
    val smilePath = "/path/to/output.smile"

    val jsonMapper = new ObjectMapper().registerModule(DefaultScalaModule)
    val smileMapper = new ObjectMapper(new SmileFactory()).registerModule(DefaultScalaModule)

    // 使用Using自动管理IO资源
    Using(jsonMapper.createParser(new java.io.File(jsonPath))) { jsonParser =>
      Using(smileMapper.createGenerator(new java.io.File(smilePath))) { smileGenerator =>
        jsonParser.nextToken() // 跳过JSON数组开头的`[`
        // 逐记录解析并写入Smile格式
        while (jsonParser.nextToken() != null) {
          val record = jsonMapper.readValue(jsonParser, classOf[DataRecord])
          smileMapper.writeValue(smileGenerator, record)
        }
      }
    }
  }
}

步骤4:高效读取Smile文件

同样采用流式方式读取,避免内存溢出:

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.smile.SmileFactory
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import scala.util.Using

object ReadSmile {
  def main(args: Array[String]): Unit = {
    val smilePath = "/path/to/output.smile"

    val smileMapper = new ObjectMapper(new SmileFactory()).registerModule(DefaultScalaModule)

    Using(smileMapper.createParser(new java.io.File(smilePath))) { smileParser =>
      var record: DataRecord = null
      // 逐记录读取并处理
      while ({record = smileMapper.readValue(smileParser, classOf[DataRecord]); record != null}) {
        // 替换为你的业务逻辑
        println(s"处理记录: a=${record.a}, b=${record.b}, c=${record.c}")
      }
    }
  }
}

方案二:使用Protocol Buffers(高效跨语言二进制格式)

Protobuf是Google推出的轻量高效二进制序列化格式,压缩率更高,适合长期存储和跨系统交互,需要提前定义数据结构IDL。

步骤1:配置Protobuf生成工具

在project/plugins.sbt中添加插件:

addSbtPlugin("com.thesamet" % "sbt-protoc" % "1.0.6")

在build.sbt中添加依赖:

libraryDependencies += "com.thesamet.scalapb" %% "scalapb-runtime" % scalapb.compiler.Version.scalapbVersion % "protobuf"

步骤2:定义Protobuf IDL

在src/main/protobuf/data.proto中写入数据结构定义:

syntax = "proto3";
package com.example;

message DataRecord {
  int32 a = 1;
  int32 b = 2;
  string c = 3;
  int32 d = 4;
  int32 e = 5;
}

运行sbt compile会自动生成对应的Scala类。

步骤3:JSON转Protobuf二进制

流式解析JSON并转换为Protobuf对象,写入二进制文件(写入时记录每个对象的字节长度,方便读取):

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.example.DataRecord
import com.google.protobuf.util.JsonFormat
import scala.util.Using

object JsonToProtobuf {
  def main(args: Array[String]): Unit = {
    val jsonPath = "/path/to/your/large.json"
    val protoPath = "/path/to/output.bin"

    val jsonMapper = new ObjectMapper().registerModule(DefaultScalaModule)
    val protoJsonParser = JsonFormat.parser()

    Using(jsonMapper.createParser(new java.io.File(jsonPath))) { jsonParser =>
      Using(new java.io.FileOutputStream(protoPath)) { outputStream =>
        jsonParser.nextToken() // 跳过JSON数组开头的`[`
        while (jsonParser.nextToken() != null) {
          val jsonNode = jsonMapper.readTree(jsonParser)
          // 将JSON转换为Protobuf对象
          val recordBuilder = DataRecord.newBuilder()
          protoJsonParser.merge(jsonNode.toString, recordBuilder)
          val record = recordBuilder.build()

          // 先写入对象字节长度,再写入对象数据
          val recordBytes = record.toByteArray
          outputStream.write(java.nio.ByteBuffer.allocate(4).putInt(recordBytes.length).array())
          outputStream.write(recordBytes)
        }
      }
    }
  }
}

步骤4:高效读取Protobuf二进制

按写入时的格式(长度+数据)流式读取:

import com.example.DataRecord
import scala.util.Using

object ReadProtobuf {
  def main(args: Array[String]): Unit = {
    val protoPath = "/path/to/output.bin"

    Using(new java.io.FileInputStream(protoPath)) { inputStream =>
      val lengthBuffer = new Array[Byte](4)
      // 循环读取每个对象的长度和数据
      while (inputStream.read(lengthBuffer) != -1) {
        val recordLength = java.nio.ByteBuffer.wrap(lengthBuffer).getInt()
        val recordBytes = new Array[Byte](recordLength)
        inputStream.read(recordBytes)

        // 反序列化为Protobuf对象并处理
        val record = DataRecord.parseFrom(recordBytes)
        println(s"处理记录: a=${record.getA}, b=${record.getB}, c=${record.getC}")
      }
    }
  }
}

关键优化点

  • 流式处理:所有操作均采用流式API,避免一次性加载1.5GB文件到内存,防止内存溢出。
  • 资源管理:使用scala.util.Using自动关闭IO资源,避免资源泄漏。
  • 格式选择:Smile适合快速转换、兼容JSON的场景;Protobuf适合高性能、跨语言、长期存储的场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 22:20:14