Питання Читати / перетворити InputStream на рядок


Якщо у вас є java.io.InputStream об'єкт, як ви повинні обробляти цей об'єкт і виробляти a String?


Припустимо, у мене є InputStream який містить текстові дані, і я хочу перетворити його в a String, тому, наприклад, я можу записати це в файл журналу.

Який найпростіший спосіб взяти InputStream і перетворити його на a String?

public String convertStreamToString(InputStream is) { 
    // ???
}

3267
2017-11-21 16:47


походження


Хлопчик, я абсолютно закоханий в Java, але це питання виникає так часто, що ви думаєте, що вони просто з'ясували, що з'єднання потоків є дещо важким і або допомагає створювати різні комбінації або переосмислити все це. - Bill K
Відповіді на це питання працюють, лише якщо ви хочете прочитати вміст потоку повністю (поки не буде закрито). Оскільки це не завжди призначено (HTTP-запити при збереженні зв'язку не будуть закриті), цей метод викличе блок (не надаючи вам вміст). - f1sh
ви треба щоб знати і вказати кодування символів для потоку, або ви воля мати помилки кодування коду, оскільки ви будете використовувати випадково обрану кодування в залежності від того, яка машина / операційна система / платформа або її версія ваш код працює. Тобто, робіть ні використання методів, які залежать від стандартного кодування платформи. - Christoffer Hammarström
Просто, щоб розважитися своїм власним коментарем 9 років тому, сьогодні я використовую Groovy's "String s = new File" ("SomeFile.txt"). Text ", щоб прочитати весь файл відразу, і це чудово працює. Я щасливий, використовуючи groovy для мого невиробничого (скриптованого) коду, і - добре чесно змушуючи вас справлятися з кодуванням і надзвичайно довгими файлами, як це робить java, - це дійсно гарна ідея для коду виробництва, так що вона працює з ціллю, Groovy працює для швидких скриптів, ява не чудова - Просто використовуйте правильний інструмент для роботи, і це все працює. - Bill K
Просто спрощення: ByteArrayOutputStream outputBytes = new ByteArrayOutputStream();  for(byte[] b = new byte[512]; 0 < inputStream.read(b); outputBytes.write(b));  return new String(outputBytes.toByteArray(), StandardCharsets.UTF_8); - Felypp Oliveira


Відповіді:


Хороший спосіб зробити це - це використання Apache commons  IOUtils скопіювати InputStream в a StringWriter... щось на зразок

StringWriter writer = new StringWriter();
IOUtils.copy(inputStream, writer, encoding);
String theString = writer.toString();

або навіть

// NB: does not close inputStream, you'll have to use try-with-resources for that
String theString = IOUtils.toString(inputStream, encoding); 

Крім того, ви можете використовувати ByteArrayOutputStream якщо ви не хочете змішувати свої потоки та письменники


2048
2017-11-21 16:54



Я знайшов виявлений випадок, коли я намагаюся прочитати ім'я файлу з ім'ям "До_свидания" (російською мовою), я намагаюся скористатися FileInputstream, але це не кабель для читання цього імені файлу з sdcard. - Bhanu Sharma
Для developers forroid, здається, що Android не поставляється з IOUtils від Apache. Тому ви можете розглянути посилання на інші відповіді. - Chris.Zou
Я працюю в обмеженій середовищі середовища, тому рішення, яке використовує @PavelRepin нижче з використанням java io / util libs, має більше сенсу. - James
Це надзвичайно старе питання на цьому етапі (це було задано у 2008 році). Варто вашого часу, щоб прочитати більш сучасні відповіді. Деякі використовують місцеві дзвінки з бібліотеки Java 8. - Shadoninja
Ця відповідь сильно застаріла, і треба вміти позначати її як таку (на жаль, це неможливо atm). - codepleb


Ось спосіб використання лише стандартної бібліотеки Java (зауважте, що потік не закритий, YMMV).

static String convertStreamToString(java.io.InputStream is) {
    java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
    return s.hasNext() ? s.next() : "";
}

Я дізнався цей трюк "Дурний трюк сканера" стаття Причиною цього є те, що він працює Сканер повторює токени в потоці, і в цьому випадку ми відокремлюємо токени, використовуючи "початок кордону вводу" (\ A), тим самим даючи нам лише один токен для всього вмісту потоку.

Зауважте, якщо вам потрібно конкретизувати кодування потоку вхідного потоку, ви можете надати другий аргумент для Scanner конструктор, який вказує, який командний рядок використовувати (наприклад, "UTF-8").

Капелюх підійде і до Яків, Яків який колись вказав мені на цю статтю.

РЕДАКТИ: Завдяки пропозиції від Патрік, зробив цю функцію більш надійною при обробці порожнього потоку вхідних даних. Ще одне редагування: nixed try / catch, шлях Патріка більш лаконічний.


2093
2018-03-26 20:40



Спасибі, для моєї версії цього я додав, нарешті, блок, який закриває вхідний потік, тому користувачеві не потрібно, оскільки ви закінчили читання введення. Простота коду абонента значно.
@PavelRepin @Patrick у моєму випадку, порожній вхідний потік викликав NPE під час побудови сканера. Мені довелося додати if (is == null) return ""; прямо на початку методу; Я вважаю, що цю відповідь потрібно оновити, щоб краще справлятися з null inputstreams. - CFL_Jeff
Для Java 7 ви можете закрити в try-with: try(java.util.Scanner s = new java.util.Scanner(is)) { return s.useDelimiter("\\A").hasNext() ? s.next() : ""; } - earcam
На жаль, це рішення, ймовірно, йде, і втрачає винятки, випущені в моїй основній потоці реалізації. - Taig
FYI hasNext блоки на вхідних потоках консолі (див тут) (Просто потрапив у це питання прямо зараз.) Це рішення чудово інакше ... просто головою. - Ryan


Підбиваючи підсумки інших відповідей, я знайшов 11 основних способів зробити це (див. Нижче). І я написав кілька тестів продуктивності (див. Результати нижче):

Способи перетворення InputStream в рядок:

  1. Використовуючи IOUtils.toString (Apache Utils)

    String result = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
    
  2. Використовуючи CharStreams (Гуава)

    String result = CharStreams.toString(new InputStreamReader(
          inputStream, Charsets.UTF_8));
    
  3. Використовуючи Scanner (JDK)

    Scanner s = new Scanner(inputStream).useDelimiter("\\A");
    String result = s.hasNext() ? s.next() : "";
    
  4. Використовуючи API потоку (Java 8). УВАГА: Це рішення перетворює різні розриви рядків (наприклад, \r\n) до \n.

    String result = new BufferedReader(new InputStreamReader(inputStream))
      .lines().collect(Collectors.joining("\n"));
    
  5. Використовуючи паралельний Stream API (Java 8). УВАГА: Це рішення перетворює різні розриви рядків (наприклад, \r\n) до \n.

    String result = new BufferedReader(new InputStreamReader(inputStream)).lines()
       .parallel().collect(Collectors.joining("\n"));
    
  6. Використовуючи InputStreamReader і StringBuilder (JDK)

    final int bufferSize = 1024;
    final char[] buffer = new char[bufferSize];
    final StringBuilder out = new StringBuilder();
    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();
    
  7. Використовуючи StringWriter і IOUtils.copy (Apache Commons)

    StringWriter writer = new StringWriter();
    IOUtils.copy(inputStream, writer, "UTF-8");
    return writer.toString();
    
  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);
    }
    // StandardCharsets.UTF_8.name() > JDK 7
    return result.toString("UTF-8");
    
  9. Використовуючи BufferedReader (JDK). УВАГА: Цей розв'язок перетворює різні розриви рядків (наприклад, \n\r) до line.separator властивість системи (наприклад, у Windows для "\ r \ n").

    String newLine = System.getProperty("line.separator");
    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();
    
  10. Використовуючи BufferedInputStream і ByteArrayOutputStream (JDK)

    BufferedInputStream bis = new BufferedInputStream(inputStream);
    ByteArrayOutputStream buf = new ByteArrayOutputStream();
    int result = bis.read();
    while(result != -1) {
        buf.write((byte) result);
        result = bis.read();
    }
    // StandardCharsets.UTF_8.name() > JDK 7
    return buf.toString("UTF-8");
    
  11. Використовуючи inputStream.read() і StringBuilder (JDK). УВАГА: Це рішення має проблеми з Unicode, наприклад, з російським текстом (працює правильно лише з текстом, що не входить до Unicode)

    int ch;
    StringBuilder sb = new StringBuilder();
    while((ch = inputStream.read()) != -1)
        sb.append((char)ch);
    reset();
    return sb.toString();
    

УВАГА:

  1. Рішення 4, 5 та 9 перетворюють різні переривання рядків на один.

  2. Рішення 11 не може працювати правильно з текстом Unicode

Тестування продуктивності

Технічні характеристики для невеликих String (довжина = 175), URL-адреса в github (режим = середній час, система = Linux, оцінка 1 343 найкраща):

              Benchmark                         Mode  Cnt   Score   Error  Units
 8. ByteArrayOutputStream and read (JDK)        avgt   10   1,343 ± 0,028  us/op
 6. InputStreamReader and StringBuilder (JDK)   avgt   10   6,980 ± 0,404  us/op
10. BufferedInputStream, ByteArrayOutputStream  avgt   10   7,437 ± 0,735  us/op
11. InputStream.read() and StringBuilder (JDK)  avgt   10   8,977 ± 0,328  us/op
 7. StringWriter and IOUtils.copy (Apache)      avgt   10  10,613 ± 0,599  us/op
 1. IOUtils.toString (Apache Utils)             avgt   10  10,605 ± 0,527  us/op
 3. Scanner (JDK)                               avgt   10  12,083 ± 0,293  us/op
 2. CharStreams (guava)                         avgt   10  12,999 ± 0,514  us/op
 4. Stream Api (Java 8)                         avgt   10  15,811 ± 0,605  us/op
 9. BufferedReader (JDK)                        avgt   10  16,038 ± 0,711  us/op
 5. parallel Stream Api (Java 8)                avgt   10  21,544 ± 0,583  us/op

Тестування продуктивності для великих String (довжина = 50100), URL-адреса в github (режим = Середній час, система = Linux, оцінка 200 715 найкраща):

               Benchmark                        Mode  Cnt   Score        Error  Units
 8. ByteArrayOutputStream and read (JDK)        avgt   10   200,715 ±   18,103  us/op
 1. IOUtils.toString (Apache Utils)             avgt   10   300,019 ±    8,751  us/op
 6. InputStreamReader and StringBuilder (JDK)   avgt   10   347,616 ±  130,348  us/op
 7. StringWriter and IOUtils.copy (Apache)      avgt   10   352,791 ±  105,337  us/op
 2. CharStreams (guava)                         avgt   10   420,137 ±   59,877  us/op
 9. BufferedReader (JDK)                        avgt   10   632,028 ±   17,002  us/op
 5. parallel Stream Api (Java 8)                avgt   10   662,999 ±   46,199  us/op
 4. Stream Api (Java 8)                         avgt   10   701,269 ±   82,296  us/op
10. BufferedInputStream, ByteArrayOutputStream  avgt   10   740,837 ±    5,613  us/op
 3. Scanner (JDK)                               avgt   10   751,417 ±   62,026  us/op
11. InputStream.read() and StringBuilder (JDK)  avgt   10  2919,350 ± 1101,942  us/op

Графіки (тести продуктивності залежно від довжини вхідного потоку в системі Windows 7)
enter image description here

Тест продуктивності (середній час) залежно від довжини вхідного потоку в системі Windows 7:

 length  182    546     1092    3276    9828    29484   58968

 test8  0.38    0.938   1.868   4.448   13.412  36.459  72.708
 test4  2.362   3.609   5.573   12.769  40.74   81.415  159.864
 test5  3.881   5.075   6.904   14.123  50.258  129.937 166.162
 test9  2.237   3.493   5.422   11.977  45.98   89.336  177.39
 test6  1.261   2.12    4.38    10.698  31.821  86.106  186.636
 test7  1.601   2.391   3.646   8.367   38.196  110.221 211.016
 test1  1.529   2.381   3.527   8.411   40.551  105.16  212.573
 test3  3.035   3.934   8.606   20.858  61.571  118.744 235.428
 test2  3.136   6.238   10.508  33.48   43.532  118.044 239.481
 test10 1.593   4.736   7.527   20.557  59.856  162.907 323.147
 test11 3.913   11.506  23.26   68.644  207.591 600.444 1211.545

1656
2018-02-17 00:58



Коли ви пишете "підсумкову відповідь", слід зазначити, що деякі рішення автоматично перетворюють різні рядки рядків (наприклад, \r\n) до \n що може бути небажаним у деяких випадках. Також було б приємно побачити необхідну додаткову пам'ять або принаймні тиск розподілу (принаймні, ви можете запустити JMH з -prof gc) Для дійсно класного повідомлення було б здорово побачити графіки (залежно від довжини рядків у межах одного розміру вхідного матеріалу та в залежності від розміру вхідних даних у тій же довжині рядків). - Tagir Valeev
Прийнятий; Найцікавіше, що результати більш ніж очікувані: слід використовувати стандартний синтаксичний цукор JDK та / або Apache Commons. - mudasobwa
Дивовижний пост. Просто одне Java 8 попереджає проти використання паралельних потоків з ресурсів, що змусить вас блокувати і чекати (наприклад, цей вхідний потік), так що параметр паралельного потоку є досить громіздким і не вартий ні? - mangusbrother
Чи паралельний потік дійсно підтримує замовлення лінії? - Natix
Що reset() для прикладу 11? - Rob Stewart


Apache Commons дозволяє:

String myString = IOUtils.toString(myInputStream, "UTF-8");

Звичайно, ви можете вибрати інші кодування символів, окрім UTF-8.

Також див. (Документи)


794
2017-12-08 20:13



Крім того, існує метод, який використовує аргумент inputStream, якщо ви знайдете його за допомогою кодування за замовчуванням. - Guillaume Coté
@Guillum Coté Я думаю, повідомлення тут полягає в тому, що ви ніколи не повинні бути "чудово з кодуванням за умовчанням", оскільки ви не можете бути впевнені в тому, що це таке, залежно від платформи запускається код java. - Per Wiklander
@Per Wiklander Я не згоден з тобою. Код, який буде працювати на одному, може бути впевненим, що кодування за замовчуванням буде добре. Для коду, який відкриває лише локальний файл, це є розумним варіантом, щоб попросити їх кодувати в кодуванні стандартної платформи. - Guillaume Coté
Щоб зберегти кого-небудь клопоту Googling - <dependency> <groupId> org.apache.commons </ groupId> <artifactId> commons-io </ artifactId> <version> 1.3.2 </ version> </ dependency> - Chris
Також невеликим вдосконаленням буде використання константа apache io (або іншої) для кодування символів, замість використання простого рядка буквально - наприклад: IOUtils.toString (myInputStream, Charsets.UTF_8);


Враховуючи файл, треба спершу отримати a java.io.Reader екземпляр Це може бути прочитане і додане в a StringBuilder (нам не потрібно StringBuffer якщо ми не маємо доступу до нього в декількох потоках, а також StringBuilder швидше) Хитрість полягає в тому, що ми працюємо в блоках, і тому не потребуємо інших буферних потоків. Розмір блоку параметризується для оптимізації продуктивності під час виконання.

public static String slurp(final InputStream is, final int bufferSize) {
    final char[] buffer = new char[bufferSize];
    final StringBuilder out = new StringBuilder();
    try (Reader in = new InputStreamReader(is, "UTF-8")) {
        for (;;) {
            int rsz = in.read(buffer, 0, buffer.length);
            if (rsz < 0)
                break;
            out.append(buffer, 0, rsz);
        }
    }
    catch (UnsupportedEncodingException ex) {
        /* ... */
    }
    catch (IOException ex) {
        /* ... */
    }
    return out.toString();
}

263
2017-08-04 08:29



Це рішення використовує багатобайтові символи. У прикладі використовується кодування UTF-8, що дозволяє виразити повний діапазон юнікоду (включно з китайською). Заміна "UTF-8" на інше кодування дозволить використати кодування. - Paul de Vrieze
@ User1 - Мені подобається використовувати бібліотеки в моєму коді, щоб я міг зробити роботу швидше. Це здорово, коли ваші менеджери кажуть: "Уоу Джеймс! Як ви це зробили так швидко ?!". Але коли нам доводиться витрачати час на передивляння колеса лише тому, що у нас є недоречні ідеї щодо включення загальної, багаторазової, перевіреної та випробуваної утиліти, ми відмовляємось від часу, коли ми можемо витратити подальші цілі нашого проекту. Коли ми винайшли колесо, ми працюємо вдвічі важко, але до фінішу все частіше. Як тільки ми перебуваємо на фініші, нам ніхто не поздоровити. При будівництві будинку не будуйте молоток теж - jmort253
Вибачте, після повторного читання мого коментаря він злегка зарозумілий. Я просто думаю, що для запобігання бібліотекам важливо мати привід, і причина є дійсною, що може бути дуже добре :) - jmort253
@ jmort253 Ми помітили регрес продуктивності після оновлення декількох бібліотек у нашому продукті кілька разів. На щастя, ми будуємо та продаємо свій продукт, тому ми не маємо так званих термінів. На жаль, ми створюємо продукт, який доступний на багатьох JVM, базах даних та серверах додатків на багатьох операційних системах, тому ми повинні думати про користувачів, які використовують бідні машини ... І оптимізація операцій з ланцюжком може покращити ефективність на 30-40%. І виправити: In our product, I even replaced має бути "ми навіть замінили". - coolcfan
@ jmort253 Якщо б ви вже використовували apache commons, я б сказав, піти на це. У той же час, існують реальні витрати на використання бібліотек (як показує розповсюдження залежності в багатьох бібліотеках apache java). Якщо це було б єдиним використанням бібліотеки, було б надмірно використовувати библиотеку. З іншого боку, визначаючи власний розмір буферу, ви можете налаштувати баланс використання пам'яті / процесора. - Paul de Vrieze


Як про це?

InputStream in = / * Ваш InputStream * /;
StringBuilder sb=new StringBuilder();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String read;

while((read=br.readLine()) != null) {
    //System.out.println(read);
    sb.append(read);   
}

br.close();
return sb.toString();

226
2017-07-13 15:56



Справа в тому, що ви спочатку розбиваєтеся на лінії, а потім скасуєте це. Легше і швидше просто прочитати довільні буфери. - Paul de Vrieze
Крім того, readLine не розрізняє \ n і \ r, тому ви не можете повторно відтворити точний потік. - María Arias de Reyna Domínguez
@PauldeVrieze скільки рядків, і як швидко вам потрібно обробити їх !? Я б ризикував здогадатися, що будь-яка втрата продуктивності буде мала або може бути оброблена кожні один раз під час реєстрації їх у файл і знищення старого String obj. - Thufir
дуже неефективний, як readLine читати символ за символом, щоб шукати EOL. Крім того, якщо в потоці немає перерв у рядку, це не має сенсу. - njzk2
Це не найкраща відповідь, оскільки це не строгий байт у байтах. Читач вимикає нові рядки, тому вам потрібно бути обережним, щоб їх підтримувати. - Jeffrey Blattman


Якщо ви використовуєте Google-Collections / Guava, ви можете зробити наступне:

InputStream stream = ...
String content = CharStreams.toString(new InputStreamReader(stream, Charsets.UTF_8));
Closeables.closeQuietly(stream);

Зверніть увагу, що другий параметр (наприклад, Charsets.UTF_8) для InputStreamReader це не обов'язково, але, як правило, хороша ідея вказати кодування, якщо ви його знаєте (що вам слід!)


153
2018-05-08 20:24



@harschware: На питання було: "Якщо у вас є об'єкт java.io.InputStream, як ви повинні обробити цей об'єкт і створити рядок?" Я припустив, що потоки вже присутні в ситуації. - Sakuraba
Ви не дуже добре пояснили вашу відповідь і мали сторонні змінні; Користувач359996 говорив так само, як ви, але набагато чіткіше. - Uronym
+1 для гуави, -1, якщо не вказано кодування вхідного потоку. наприклад. новий InputStreamReader (потік, "UTF-8") - andras
@ Chris Noldus З іншого боку, у деяких людей у ​​проекті вже є гуава, як я, і думаю, що це рішення є більш елегантним, ніж версія sdk. - CorayThan
@Вадзим, що відповідь така ж, як і ця, - обидва використовують CharStreams.toString - Tom


Це моє чисте рішення Java & Android, добре працює ...

public String readFullyAsString(InputStream inputStream, String encoding)
        throws IOException {
    return readFully(inputStream).toString(encoding);
}    

public byte[] readFullyAsBytes(InputStream inputStream)
        throws IOException {
    return readFully(inputStream).toByteArray();
}    

private ByteArrayOutputStream readFully(InputStream inputStream)
        throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int length = 0;
    while ((length = inputStream.read(buffer)) != -1) {
        baos.write(buffer, 0, length);
    }
    return baos;
}

107
2018-06-10 21:07



Працює добре на Android, порівняно з іншими відповідями, які працюють тільки на корпоративній Java. - vorrtex
Збій у Android з помилкою OutOfMemory у рядку ".write", щоразу для коротких рядків. - Adam
Я додав кодування. як і бічна примітка, оригінальний метод readFully, який у моєму коді не повертає String, повертає байт [] для більш загального призначення. Впровадження нової String (...) з кодуванням відповідає за використання API! - TacB0sS
Швидка примітка: пам'ять пам'яті цього максимально 2*n, де n - розмір потоку, відповідно до ByteArrayInputStream система автоматичного вирощування. - njzk2
Необхідно подвоїти використання пам'яті, що дорогоцінне на мобільних пристроях. Вам краще використовувати InputStreamReader і додати до StringReader, перетворення байтів до char буде виконуватися на льоту, а не в основному в кінці. - Oliv


Як на рахунок:

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;    

public static String readInputStreamAsString(InputStream in) 
    throws IOException {

    BufferedInputStream bis = new BufferedInputStream(in);
    ByteArrayOutputStream buf = new ByteArrayOutputStream();
    int result = bis.read();
    while(result != -1) {
      byte b = (byte)result;
      buf.write(b);
      result = bis.read();
    }        
    return buf.toString();
}

56
2018-01-01 03:43



Це один повільний, тому що читає байт за байтом. - Daniel De León
@ DanielDeLeón Ні, це не так. Це BufferedInputStream. Нижче читається 8192 байтів одночасно. - user207421
@ EJP Я виявив, що це буде повільніше, ніж використовувати BufferedInputStream  і читання в буфер масиву байтів замість одного байта одночасно. Приклад: 200 мс проти 60 мс при читанні файлу 4,56 МБ. - jk7