Что такое эффективный способ заставить потоки Java8 создавать форматированную строку

1

Контекст: данный каталог, я хотел бы перечислить все файлы там, которые содержат шаблон в их имени, упорядоченный по lastModified временной 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% больше времени, когда я ожидал (очень маленькую) сбережения.

Я не чувствую, что новая версия легче читать или поддерживать, поэтому я сохраню старую.

Спасибо всем.

  • 0
    вы наверное имели ввиду return name == null ? false , т.е. исключить нулевые имена. Хотя я не уверен, в какой ситуации это могло произойти ...
  • 0
    В лямбдах вы можете удалить {возврат и закрытие}
Показать ещё 2 комментария
Теги:
optimization
java-8
java-stream
string-formatting

1 ответ

8
Лучший ответ

Форматирование строк - это тривиальная часть производительности вашего приложения, которую почти никогда не стоит оптимизировать; только подумайте об этом, если профилирование показывает реальную горячую точку. Фактически, большинство приложений используют отражающие JSON-карты, а их узкие места находятся в другом месте (обычно I/O). Используемый вами метод StringBuilder - это самый эффективный способ сделать это на Java без ручного скрещивания массивов символов, и он даже идет дальше, чем я сам (я бы использовал String#format()).

Вместо этого напишите свой код для ясности. Текущая версия в порядке.

  • 3
    +1, единственное, что можно улучшить, это использовать конструктор StringBuilder(int) указывающий более высокую начальную емкость, поскольку результирующая длина, как и ожидалось, больше емкости по умолчанию в 16 символов. Но правильно, ввод / вывод перевесит все это.
  • 0
    Вы должны быть правы. В любом случае, просто из (научного) любопытства, и поскольку это может относиться к другим областям, где IO не является узким местом, я попытаюсь реализовать его по-другому, и я дам вам знать =)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню