У меня есть хобби: я собираю различные решения типовых задач в Java, которые нахожу в инете, и пытаюсь выбрать наиболее оптимальное по размеру/производительности/элегантности. В первую очередь по производительности. Давайте рассмотрим такую типовую задач, которые часто встречаются в программировании на Java как "преобразование InputStream в строку" и разные варианты её решения.
Посмотрим какие ограничения есть у каждого (требования подключения определенной библиотеки/определенной версии, корректная работа с unicode и т.д.). Английскую версию этой статьи можно найти в моем ответе на stackoverflow. Тесты в моем проекте на github.
Очень часто встречающая задача, давайте рассмотрим какими способами можно это сделать (их будет 11):
Используя IOUtils.toString из библиотеки Apache Commons. Один из самых коротких однострочников.
String result = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
Используя CharStreams из библиотеки guava. Тоже довольно короткий код.
try(InputStreamReader reader = new InputStreamReader(inputStream, Charsets.UTF_8)) {
String result = CharStreams.toString(reader);
}
Используя Scanner (JDK). Решение короткое, хитрое, с помощью чистого JDK, но это скорее хак, который вынесет мозг тем кто о таком фокусе не знает.
try(Scanner s = new Scanner(inputStream).useDelimiter("\A")) {
String result = s.hasNext() ? s.next() : "";
}
Используя Stream Api с помощью Java 8. Предупреждение: Оно заменяет разные переносы строки (такие как rn) на n, иногда это может быть критично.
try(BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) {
String result = br..lines().collect(Collectors.joining("n"));
}
Используя parallel Stream Api (Java 8). Предупреждение: Как и 4 решение, оно заменяет разные переносы строки (такие как rn) на n.
try(BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) {
String result = br.lines().parallel().collect(Collectors.joining("n"));
}
Используя InputStreamReader и StringBuilder из обычного JDK
final int bufferSize = 1024;
final char[] buffer = new char[bufferSize];
final StringBuilder out = new StringBuilder();
try(Reader in = new InputStreamReader(inputStream, "UTF-8")) {
for (; ; ) {
int rsz = in.read(buffer, 0, buffer.length);
if (rsz < 0)
break;
out.append(buffer, 0, rsz);
}
return out.toString();
}
Используя StringWriter и IOUtils.copy из Apache Commons
Используя ByteArrayOutputStream и inputStream.read из JDK
try(ByteArrayOutputStream result = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
result.write(buffer, 0, length);
}
return result.toString("UTF-8");
}
Используя BufferedReader из JDK. Предупреждение: Это решение заменяет разные переносы строк (такие как nr) на line.separator system property (например, в Windows на "rn").
String newLine = System.getProperty("line.separator");
try(BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
StringBuilder result = new StringBuilder();
String line; boolean flag = false;
while ((line = reader.readLine()) != null) {
result.append(flag? newLine: "").append(line);
flag = true;
}
return result.toString();
}
Используя BufferedInputStream и ByteArrayOutputStream из JDK
try(BufferedInputStream bis = new BufferedInputStream(inputStream); ByteArrayOutputStream buf = new ByteArrayOutputStream()) {
int result = bis.read();
while(result != -1) {
buf.write((byte) result);
result = bis.read();
}
return buf.toString();
}
Используя inputStream.read() и StringBuilder (JDK). Предупреждение: Это решение не работает с Unicode, например с русским текстом
int ch;
StringBuilder sb = new StringBuilder();
while((ch = inputStream.read()) != -1)
sb.append((char)ch);
reset();
return sb.toString();
Итак о использовании:
Решения 4, 5 и 9 преобразую разные переносы строки в одну.
Решения 11 не работает с Unicode текстом
Решение 1, 7 требует использование библиотеки Apache Commons, 2 требует библиотеку Guava, 4 и 5 требуют Java 8 и выше,
Замеры производительности
Предупреждение: замеры производительности всегда сильно зависят от системы, условий замера и т.п. Я измерял на двух разных компьютерах, один Windows 8.1, Intel i7-4790 CPU 3.60GHz2, 16Gb, второй — Linux Mint 17.2, Celeron Dual-Core T3500 2.10Ghz2, 6Gb, однако не могу гарантировать что результаты являются абсолютной истиной, вы всегда можете повторить тесты (test1 и test2) на вашей системе.
Замеры производительности для небольших строк (длина = 175), тесты можно найти на github (режим = среднее время выполнения (AverageTime), система = Linux Mint 17.2, Celeron Dual-Core T3500 2.10Ghz*2, 6Gb, чем значение ниже тем лучше, 1,343 — наилучшее):
Замеры производительности для больших строк (длина = 50100), тесты можно найти на github (режим = среднее время выполнения (AverageTime), система = Linux Mint 17.2, Celeron Dual-Core T3500 2.10Ghz*2, 6Gb, чем значение ниже тем лучше, 200,715 — наилучшее):
Самым быстрым решением во всех случаях и всех системах оказался 8 тест: Используя ByteArrayOutputStream и inputStream.read из JDK
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
result.write(buffer, 0, length);
}
return result.toString("UTF-8");
Коротким и весьма быстрым решением будет использование IOUtils.toString из Apache Commons
String result = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
Stream Api из Java 8 показывает среднее время, а использование параллельных стримов имеет смысл только при довольно большой строки, иначе он работает очень долго (что в общем-то было ожидаемо)
Решение 11 лучше не использовать в принципе, так как он работает медленнее всех и не работает с Unicode,
P.S.
Английскую версию этой статьи можно найти в моем ответе на stackoverflow. Тесты в моем проекте на github. Если эта статья вам понравилась и вы поставите плюс на stackoverflow буду вам благодарен.
Буду очень благодарен за любые замечания, исправления, указания на ошибки или другие способы преобразования InputStream в строку