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

使用IntelliJ与Kotlin读取大型CSV文件时遭遇Java堆内存溢出问题求助

解决Kotlin读取大CSV文件的堆内存溢出问题

嘿,我完全懂你现在的困扰——直接把350万条Product记录全塞进ArrayList里,就算把堆内存调到10G也可能撑不住,这根本不是堆大小的问题,而是数据处理方式的问题。

问题根源

你当前的代码把所有解析后的Product对象都存在一个MutableList里,这意味着程序运行时,350万个对象会同时占用内存。就算每个Product只占100字节(实际远不止),350万条也得350MB,再加上CSV解析过程中的临时对象、JVM本身的内存开销,很容易触发OutOfMemoryError。调堆内存只是治标,而且文件再大一点(比如1亿条),堆再大也顶不住。

正确的解决方案:流式/分批处理

核心思路是不要一次性加载所有数据到内存,而是逐行读取、处理,处理完就释放内存;或者分批读取,处理一批清空一批。下面给你两种具体实现方案:

方案1:流式处理(推荐)

用Kotlin的kotlinx-csv库(轻量高效)逐行解析CSV,每解析一条就直接处理(比如写入数据库、计算统计指标),不存储到List里。这样内存里永远只有当前行的Product对象,内存占用极低。

首先添加依赖(Gradle为例):

implementation "org.jetbrains.kotlinx:kotlinx-csv-jvm:0.10.1"

然后修改你的代码:

import kotlinx.csv.CsvReader
import java.io.File

data class Product(
    val year: Int,
    val month: Int,
    val cod_product: Int,
    val unit: Int,
    val country: Int,
    val uf: String,
    val transport: Int,
    val revenue: Int,
    val quantity: Int,
    val weight: Int,
    val price: Int
)

fun main() {
    val csvFilePath = "你的大CSV文件路径.csv"
    
    // 用use()自动关闭流,避免资源泄漏
    File(csvFilePath).bufferedReader().use { fileReader ->
        CsvReader(fileReader).use { csvReader ->
            // 如果CSV有表头,先跳过一行
            csvReader.readNext()
            
            // 逐行解析并处理
            for (row in csvReader) {
                val product = parseRowToProduct(row)
                // 这里替换成你的实际业务逻辑:比如写入DB、统计等
                processSingleProduct(product)
            }
        }
    }
}

// 把CSV行解析为Product对象
private fun parseRowToProduct(row: List<String>): Product {
    return Product(
        year = row[0].toInt(),
        month = row[1].toInt(),
        cod_product = row[2].toInt(),
        unit = row[3].toInt(),
        country = row[4].toInt(),
        uf = row[5],
        transport = row[6].toInt(),
        revenue = row[7].toInt(),
        quantity = row[8].toInt(),
        weight = row[9].toInt(),
        price = row[10].toInt()
    )
}

// 处理单个Product的业务逻辑
private fun processSingleProduct(product: Product) {
    // 示例:打印或者写入数据库
    // println("处理产品:${product.cod_product}")
    // productDao.insert(product)
}

方案2:分批处理(如果需要批量操作)

如果你的业务必须批量处理(比如批量插入数据库),可以每次读取固定数量的记录,处理完就清空List,再读取下一批:

fun main() {
    val csvFilePath = "你的大CSV文件路径.csv"
    val batchSize = 10000 // 每批处理1万条,可根据内存情况调整
    
    File(csvFilePath).bufferedReader().use { fileReader ->
        CsvReader(fileReader).use { csvReader ->
            csvReader.readNext() // 跳表头
            
            val batch = mutableListOf<Product>()
            for (row in csvReader) {
                val product = parseRowToProduct(row)
                batch.add(product)
                
                // 达到批大小就处理,然后清空
                if (batch.size >= batchSize) {
                    processProductBatch(batch)
                    batch.clear()
                }
            }
            // 处理最后一批不足batchSize的数据
            if (batch.isNotEmpty()) {
                processProductBatch(batch)
            }
        }
    }
}

// 批量处理Product的业务逻辑
private fun processProductBatch(batch: List<Product>) {
    // 示例:批量插入数据库
    // productDao.insertBatch(batch)
    println("处理了${batch.size}条记录")
}

额外小贴士

  • 避免使用ArrayList存储全量数据:除非你的内存真的能装下所有数据(但大文件场景下不推荐)。
  • 尽量用原始类型:比如Int而不是Integer(Kotlin默认就是原始类型的包装,但解析时注意避免不必要的装箱)。
  • 关闭不必要的调试工具:比如IntelliJ的内存快照、调试器,这些会额外占用内存。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 20:42:44