Контекст: данный каталог, я хотел бы перечислить все файлы там, которые содержат шаблон в их имени, упорядоченный по 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% больше времени, когда я ожидал (очень маленькую) сбережения.
Я не чувствую, что новая версия легче читать или поддерживать, поэтому я сохраню старую.
Спасибо всем.
Форматирование строк - это тривиальная часть производительности вашего приложения, которую почти никогда не стоит оптимизировать; только подумайте об этом, если профилирование показывает реальную горячую точку. Фактически, большинство приложений используют отражающие JSON-карты, а их узкие места находятся в другом месте (обычно I/O). Используемый вами метод StringBuilder
- это самый эффективный способ сделать это на Java без ручного скрещивания массивов символов, и он даже идет дальше, чем я сам (я бы использовал String#format()
).
Вместо этого напишите свой код для ясности. Текущая версия в порядке.
StringBuilder(int)
указывающий более высокую начальную емкость, поскольку результирующая длина, как и ожидалось, больше емкости по умолчанию в 16 символов. Но правильно, ввод / вывод перевесит все это.
return name == null ? false
, т.е. исключить нулевые имена. Хотя я не уверен, в какой ситуации это могло произойти ...