【问题标题】:What is a performant way to make Java8 streams produce a formatted string什么是使 Java8 流生成格式化字符串的高效方法
【发布时间】:2014-08-06 16:27:59
【问题描述】:

上下文:给定一个目录,我想列出其中名称中包含模式的所有文件,按lastModified 时间戳排序,并将此列表格式化为我将获得名称的 Json 字符串每个文件的时间戳:

[{"name": "somefile.txt", "timestamp": 123456},
{"name": "otherfile.txt", "timestamp": 456789}]

我有以下代码:

private StringBuilder jsonFileTimestamp(File file) {
    return new StringBuilder("{\"name\":\"")
            .append(file.getName())
            .append("\", \"timestamp\":")
            .append(file.lastModified())
            .append("}");
}

public String getJsonString(String path, String pattern, int skip, int limit) throws IOException {

    return Files.list(Paths.get(path))
            .map(Path::toFile)
            .filter(file -> {
                return file.getName().contains(pattern);
            })
            .sorted((f1, f2) -> {
                return Long.compare(f2.lastModified(), f1.lastModified());
            })
            .skip(skip)
            .limit(limit)
            .map(f -> jsonFileTimestamp(f))
            .collect(Collectors.joining(",", "[", "]"));
}

这运作良好。我只关心 StringBuilder 实例化(或字符串连接)的性能。只要文件数量保持小就可以(这是我的情况,所以我很好),但我很好奇:你会建议什么作为优化?我觉得我应该将reduce 与正确的累加器和组合器一起使用,但我无法理解它。

谢谢。


更新

我最终采用了以下“优化”:

private StringBuilder jsonFileTimestampRefactored(StringBuilder res, File file) {
    return res.append(res.length() == 0 ? "" : ",")
            .append("{\"name\":\"")
            .append(file.getName())
            .append("\", \"timestamp\":")
            .append(file.lastModified())
            .append("}");
}

public String getJsonStringRefactored(String path, String pattern, int skip, int limit) throws IOException {
    StringBuilder sb = Files.list(Paths.get(path))
            .map(Path::toFile)
            .filter(file -> file.getName().contains(pattern))
            .sorted((f1, f2) -> Long.compare(f2.lastModified(), f1.lastModified()))
            .skip(skip)
            .limit(limit)
            .reduce(new StringBuilder(),
                    (StringBuilder res, File file) -> jsonFileTimestampRefactored(res, file),
                    (StringBuilder a, StringBuilder b) -> a.append(a.length() == 0 || b.length() == 0 ? "" : ",").append(b))
            ;
    return new StringBuilder("[").append(sb). append("]").toString();
}

此版本仅创建 2 个 StringBuilder 实例,而旧版本实例化的实例数量与目录中的文件一样多。

在我的工作站上,第一个实现需要 1289 毫秒来完成超过 3379 个文件,而第二个实现需要 1306 毫秒。当我期望(非常少)节省时,第二个实施使我多花费了 1% 的时间。

我不觉得新版本更容易阅读或维护,所以我会保留旧版本。

谢谢大家。

【问题讨论】:

  • 您的意思可能是return name == null ? false,即排除空名称。虽然我不确定在什么情况下会发生这种情况......
  • 在 lambdas 中,您可以删除 { return 和关闭 }
  • 您可以将.sorted((f1, f2) -> { return Long.compare(f2.lastModified(), f1.lastModified()); })替换为.sorted(Comparator.comparingLong(File::lastModified))
  • 感谢 cmets。我已经删除了 name 上的测试,因为它是我的生产代码的遗留问题,我忘记为这个问题清理了。
  • 您应该使用像 Jackson 或 Gson 这样的适当库来创建 JSON。如果文件名中包含无效字符,您将获得无效的 JSON。使用名称和时间戳字段创建 POJO,映射到该对象,收集到列表,然后将列表转换为带有实际 JSON 库的 JSON 数组。

标签: java optimization java-8 string-formatting java-stream


【解决方案1】:

字符串格式化是应用程序性能中如此微不足道的一部分,几乎不值得优化;仅在分析显示实际热点时才考虑它。事实上,大多数应用程序都使用反射 JSON 映射器,它们的瓶颈在其他地方(通常是 I/O)。您正在使用的 StringBuilder 方法是您在 Java 中无需手动旋转字符数组即可执行此操作的最有效方法,而且它甚至比我自己走得更远(我会使用 String#format())。

为了清楚起见,编写您的代码。当前版本很好。

【讨论】:

  • +1,唯一可以改进的是使用StringBuilder(int)构造函数指定更高的初始容量,因为结果长度可以预见地高于16个字符的默认容量。但没错,I/O 将胜过一切。
  • 你一定是对的。无论如何,只是出于(科学)好奇心,并且因为这可能适用于 IO 不是瓶颈的其他领域,所以我将尝试以另一种方式实现它,我会让你知道 =)
猜你喜欢
  • 2023-03-20
  • 1970-01-01
  • 1970-01-01
  • 2010-10-25
  • 2012-07-22
  • 1970-01-01
  • 1970-01-01
  • 2020-12-01
  • 1970-01-01
相关资源
最近更新 更多