循环遍历中等大小的数据集时,Kotlin 出现内存不足错误

Out of memory error in Kotlin when looping through a medium size data set

我是 运行 Kotlin 中的以下循环并抛出内存不足错误。我是 运行 这个,用于读取 csv 文件中的行。 "records" 的大小是 6422。

fun readCSVFile(filePath: String): List<String> {
    val reader = FileReader(filePath)
    val records = CSVFormat.DEFAULT.parse(reader)
    val rows = mutableListOf<String>()

    var output = ""
    records.forEach() {
        val size = it.size()
        for (i in 0 until it.size()-1) {
            output = output + it.get(i) + ","
        }
        output.dropLast(1)
        rows.add(output)
    }
    return rows
}

下面是我得到的异常。

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at trivago.ti.tools.FileProcessor.readCSVFile(FileProcessor.kt:16)
at trivago.ti.tools.ComparatorMainKt.main(ComparatorMain.kt:25)

我在 Java 中执行了相同的逻辑,但它工作正常。以下是我在 Java.

中的内容
private static List<String> readCSVFile(String filePath) throws IOException {
    Reader in = new FileReader(filePath);
    Iterable<CSVRecord> records = CSVFormat.DEFAULT.parse(in);
    List<String> rows = new ArrayList<>();
    for (CSVRecord record : records) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < record.size(); i++)
            builder.append(record.get(i) + ",");
        builder.deleteCharAt(builder.length() - 1);
        rows.add(builder.toString());
    }
    return rows;
}

为什么 Kotlin 对此有问题?我在循环中做错了什么吗?任何帮助将不胜感激,因为我是 Kotlin 的新手。

我认为您的代码中存在错误

records.forEach() {
    output = "" // clear output ;)
    ...
}

将其与您的 java 代码进行比较

for (CSVRecord record : records) {
    StringBuilder builder = new StringBuilder(); // clear builder
    ...
}

在您的 kotlin 代码中也使用 StringBuilder。您正在堆中创建 String 个对象的日志。字符串是不可变的,此代码:

var output = ""
output = output + ","

正在堆中创建两个对象,尽管您只引用了其中一个。所以另一个有资格被 GC 删除。 GC 在你的情况下 "working" 太难了,这就是你得到 java.lang.OutOfMemoryError: GC overhead limit exceeded.

的原因
fun readCSVFile(filePath: String): List<String> {
    val reader = FileReader(filePath)
    val records = CSVFormat.DEFAULT.parse(reader)
    val rows = mutableListOf<String>()

    var output = StringBuilder("")
    records.forEach() {
        output = StringBuilder("")
        val size = it.size()
        for (i in 0 until it.size()-1) {
            output = output.append(it.get(i) + ",")
        }
        output.deleteCharAt(output.length - 1)
        rows.add(output.toString())
    }
    return rows
}

您的代码也会 运行 快很多,因为创建新对象的成本非常高。

您的 Kotlin 代码有两个问题:

  1. 您正在使用字符串和字符串连接 - 这是一项开销很大的操作。您也应该使用 StringBuilder。
  2. 您在 foreach 循环之外设置 output = "" - 对于每次迭代,您在输出
  3. 中包含所有先前的行