StringBuilder 到 Streams 和 Lambda 表达式

问题描述

我有一个方法

public String tpeResponse(InputStream is) {
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder out = new StringBuilder();
        String line;

        while ((line = reader.readLine()) != null) {
            out.append(line);
        }
        reader.close();
        is.close();
        return out.toString();
}

在线:out.append(line); 我有一个错误

java.lang.OutOfMemoryError:Java 堆空间

请帮助我使用 Streams 和 lambda 表达式重建这个方法

解决方法

无论您从(传递给您的方法的那个 is 变量)读取数据是

  • [A] 无穷无尽(例如,如果它是 System.in,它是一个 InputStream,代表您的流程标准,并且您使用 java -jar yourapp.jar </dev/null 启动应用程序,那么它实际上是无止境。

  • 或者,[B] 不是无穷无尽的,而是 LOT 的数据。例如,相同的情况,但您执行 java -jar yourapp.jar </my/20gigabyte4kCopyOfTheEntireLordOfTheRingsTrilogy.mp4

a StringBuilder 只是将所有数据存储在有限的进程内存中。限制到什么程度?好吧,当然不会超过您计算机中的 RAM 总量,但通常会少于此数量,这取决于您如何启动 JVM。但是,如果您达到了该限制,答案是无论如何都不要使用 StringBuilder。

有几个解决方案。哪一个是正确的?从您在问题中提供的有限细节无法判断:

  • 意识到它是无限输入,因此根本不可能尝试存储所有输入。
  • 采用流式模型,而不是将所有输入转换为一个大字符串然后处理该字符串,而是获取足够的输入,您可以对其进行操作,对其进行操作,然后继续进行。

例如,假设您有一个应用程序,它计算输入流中“嘿”一词出现的次数。

与其像你那样写,不如你可以这样做:

public int countHey(InputStream is) {
    BufferedReader reader = new BufferedReader(new InputStreamReader(is));
    String line;

    int total = 0;
    while ((line = reader.readLine()) != null) {
        total += countHeyInLine(line);
    }
    reader.close();
    is.close(); // WARNING! SEE BELOW!
    return total;
}

即使你把收集的莎士比亚作品提供给它,这段代码也能工作,因为这段代码读取一行,处理那一行,把这个处理的结果减少到一个单一的值,然后继续:Java 的垃圾收集器可以简单地收集您处理过的所有行,因此此代码将愉快地挖掘您扔给它的所有数百万行,而不会耗尽内存。

注意:无论打开资源还是关闭它,或者需要非常清楚地标记,它将关闭资源的责任转移给调用它的人。您做了相反的操作:此方法不是创建该输入流的代码,但您正在关闭它。这是一个非常糟糕的主意,会导致资源泄漏。您不应该关闭 is 并在文档中明确表示您的代码没有。或者,如果您认为此方法最好关闭 is,它应该在 try/finally 结构中这样做,以便调用者可以放心,调用此方法将最终以某种方式关闭该流。