Android Чтение из входного потока эффективно

152

Я делаю HTTP-запрос на получение приложения для Android, которое я делаю.

Я использую DefaultHttpClient и использую HttpGet для выдачи запроса. Я получаю ответ сущности и получаю объект InputStream для получения html страницы.

Затем я перебираю ответ, выполняя следующее:

BufferedReader r = new BufferedReader(new InputStreamReader(inputStream));
String x = "";
x = r.readLine();
String total = "";

while(x!= null){
total += x;
x = r.readLine();
}

Однако это ужасно медленно.

Это неэффективно? Я не загружаю большую веб-страницу - www.cokezone.co.uk, поэтому размер файла не большой. Есть лучший способ сделать это?

Спасибо

Энди

RenegadeAndy
источник
Если вы на самом деле не разбираете строки, нет смысла читать построчно. Я предпочел бы читать символ за символом
Mike76

Ответы:

355

Проблема в вашем коде заключается в том, что он создает множество тяжелых Stringобъектов, копирует их содержимое и выполняет над ними операции. Вместо этого вы должны использовать, StringBuilderчтобы избежать создания новых Stringобъектов в каждом добавлении и чтобы избежать копирования массивов символов. Реализация для вашего случая будет примерно такой:

BufferedReader r = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder total = new StringBuilder();
for (String line; (line = r.readLine()) != null; ) {
    total.append(line).append('\n');
}

Теперь вы можете использовать его totalбез преобразования String, но если вам нужен результат как String, просто добавьте:

String result = total.toString ();

Я постараюсь объяснить это лучше ...

  • a += b(или a = a + b), где aи bявляются строками, копирует содержимое обоих a и b в новый объект (обратите внимание, что вы также копируете a, который содержит накопленный String ), и вы делаете эти копии на каждой итерации.
  • a.append(b), где aa StringBuilder, напрямую добавляет bсодержимое a, чтобы вы не копировали накопленную строку на каждой итерации.
Хайме Сориано
источник
23
Для получения бонусных баллов предоставьте начальную емкость, чтобы избежать перераспределения при заполнении StringBuilder total = new StringBuilder(inputStream.available());
StringBuilder
10
Разве это не вырезает символы новой строки?
Натан Шверманн
5
не забудьте обернуть время в try / catch следующим образом: try {while ((line = r.readLine ())! = null) {total.append (line); }} catch (IOException e) {Log.i (tag, "проблема с readline в функции inputStreamToString"); }
ботбот
4
@botbot: регистрация и игнорирование исключения не намного лучше, чем просто игнорирование исключения ...
Matti Virkkunen
50
Удивительно, что в Android нет встроенного преобразования потока в строку. Любой фрагмент кода в сети и приложение на планете для повторной реализации readlineцикла просто смешно. Эта модель должна была умереть с зеленым горошком в 70-х годах.
Эдвард Брей,
35

Вы пробовали встроенный метод для преобразования потока в строку? Это часть библиотеки Apache Commons (org.apache.commons.io.IOUtils).

Тогда ваш код будет такой одной строкой:

String total = IOUtils.toString(inputStream);

Документация для этого может быть найдена здесь: http://commons.apache.org/io/api-1.4/org/apache/commons/io/IOUtils.html#toString%28java.io.InputStream%29

Библиотеку Apache Commons IO можно скачать здесь: http://commons.apache.org/io/download_io.cgi

Makotosan
источник
Я понимаю, что это запоздалый ответ, но только что случайно наткнулся на это через поиск в Google.
Макотосан
61
Android API не включает IOUtils
Чарльз Ма
2
Да, именно поэтому я упомянул внешнюю библиотеку, в которой она есть. Я добавил библиотеку в свой проект Android, и теперь ее легко читать из потоков.
Макотосан
Где я могу скачать это, и как вы импортировали это в свой проект Android?
сафари
3
Если вам нужно скачать его, я бы не назвал его «встроенным»; тем не менее, я только что загрузил его, и попробую.
Б. Клэй Шеннон
15

Еще одна возможность с гуавой:

зависимость: compile 'com.google.guava:guava:11.0.2'

import com.google.common.io.ByteStreams;
...

String total = new String(ByteStreams.toByteArray(inputStream ));
Андрей
источник
9

Я считаю, что это достаточно эффективно ... Чтобы получить строку из InputStream, я бы вызвал следующий метод:

public static String getStringFromInputStream(InputStream stream) throws IOException
{
    int n = 0;
    char[] buffer = new char[1024 * 4];
    InputStreamReader reader = new InputStreamReader(stream, "UTF8");
    StringWriter writer = new StringWriter();
    while (-1 != (n = reader.read(buffer))) writer.write(buffer, 0, n);
    return writer.toString();
}

Я всегда использую UTF-8. Конечно, вы могли бы установить charset в качестве аргумента, кроме InputStream.

Будимир Гром
источник
6

Как насчет этого. Кажется, чтобы дать лучшую производительность.

byte[] bytes = new byte[1000];

StringBuilder x = new StringBuilder();

int numRead = 0;
while ((numRead = is.read(bytes)) >= 0) {
    x.append(new String(bytes, 0, numRead));
}

Изменить: На самом деле этот вид охватывает как Steelbytes и Мориса Перри

Адриан
источник
Проблема в том, что я не знаю размера того, что я читаю до того, как я начну, так что, возможно, потребуется еще и некоторая форма увеличения массива. Если вы не можете запросить InputStream или URL через http, чтобы выяснить, насколько велика вещь, которую я извлекаю, для оптимизации размера байтового массива. Я должен быть эффективным, как на мобильном устройстве, что является главной проблемой! Однако, спасибо за эту идею - попробуем сегодня вечером и расскажем, как она справляется с ростом производительности!
RenegadeAndy
Я не думаю, что размер входящего потока так важен. Приведенный выше код читает 1000 байтов за раз, но вы можете увеличить / уменьшить этот размер. С моим тестированием это не имело особого значения, я использовал 1000/10000 байтов. Это было просто простое Java-приложение. Это может быть важнее на мобильном устройстве.
Адриан
4
Вы можете получить юникодную сущность, которая будет разделена на две последующие операции чтения. Лучше читать до какого-нибудь граничного символа, такого как \ n, что и делает BufferedReader.
Джейкоб Нордфальк
4

Возможно, несколько быстрее, чем ответ Хайме Сориано, и без проблем многобайтового кодирования ответа Адриана, я предлагаю:

File file = new File("/tmp/myfile");
try {
    FileInputStream stream = new FileInputStream(file);

    int count;
    byte[] buffer = new byte[1024];
    ByteArrayOutputStream byteStream =
        new ByteArrayOutputStream(stream.available());

    while (true) {
        count = stream.read(buffer);
        if (count <= 0)
            break;
        byteStream.write(buffer, 0, count);
    }

    String string = byteStream.toString();
    System.out.format("%d bytes: \"%s\"%n", string.length(), string);
} catch (IOException e) {
    e.printStackTrace();
}
Хайнер
источник
Можете ли вы объяснить, почему это будет быстрее?
Ахил пап
Он не сканирует ввод на наличие символов новой строки, а просто читает фрагменты размером 1024 байта. Я не утверждаю, что это будет иметь какое-либо практическое значение.
Хейнер
какие-нибудь комментарии по поводу ответа @Ronald? Он делает то же самое, но для большего чанка, равного размеру inputStream. Кроме того, насколько отличается, если я отвечу Николаю, я сканирую массив символов, а не байтовый массив? На самом деле, я просто хотел узнать, какой подход лучше в каком случае? Также readLine удаляет \ n и \ r, но я видел даже код приложения Google io, который они используют readline
Akhil Dad
3

Возможно, вместо этого читайте «по одной строке за раз» и соединяйте строки, попробуйте «прочитать все доступные», чтобы избежать сканирования конца строки, а также избежать объединения строк.

то есть InputStream.available()иInputStream.read(byte[] b), int offset, int length)

SteelBytes
источник
Хм. так было бы так: int offset = 5000; Байт [] bArr = новый байт [100]; Byte [] total = Byte [5000]; while (InputStream.available) {offset = InputStream.read (bArr, offset, 100); for (int i = 0; i <offset; i ++) {total [i] = bArr [i]; } bArr = новый байт [100]; } Это действительно более эффективно - или я написал это плохо! Пожалуйста, приведите пример!
RenegadeAndy
2
нет нет нет нет, я имею в виду просто {byte total [] = new [instrm.available ()]; instrm.read (всего, 0, total.length); } и если вам нужно было это как String, используйте {String asString = String (total, 0, total.length, "utf-8"); // предположим utf8 :-)}
SteelBytes
2

Чтение одной строки текста за раз и добавление указанной строки к строке по отдельности отнимает много времени как при извлечении каждой строки, так и при обработке большого количества вызовов метода.

Я смог добиться лучшей производительности, выделив байтовый массив приличного размера для хранения потоковых данных, который при необходимости итеративно заменяется большим массивом, и пытался прочитать столько, сколько мог вместить массив.

По какой-то причине Android неоднократно не загружал весь файл, когда код использовал InputStream, возвращаемый HTTPUrlConnection, поэтому мне пришлось прибегнуть к использованию как BufferedReader, так и механизма тайм-аута, созданного вручную, чтобы убедиться, что я получу весь файл или откажусь перевод.

private static  final   int         kBufferExpansionSize        = 32 * 1024;
private static  final   int         kBufferInitialSize          = kBufferExpansionSize;
private static  final   int         kMillisecondsFactor         = 1000;
private static  final   int         kNetworkActionPeriod        = 12 * kMillisecondsFactor;

private String loadContentsOfReader(Reader aReader)
{
    BufferedReader  br = null;
    char[]          array = new char[kBufferInitialSize];
    int             bytesRead;
    int             totalLength = 0;
    String          resourceContent = "";
    long            stopTime;
    long            nowTime;

    try
    {
        br = new BufferedReader(aReader);

        nowTime = System.nanoTime();
        stopTime = nowTime + ((long)kNetworkActionPeriod * kMillisecondsFactor * kMillisecondsFactor);
        while(((bytesRead = br.read(array, totalLength, array.length - totalLength)) != -1)
        && (nowTime < stopTime))
        {
            totalLength += bytesRead;
            if(totalLength == array.length)
                array = Arrays.copyOf(array, array.length + kBufferExpansionSize);
            nowTime = System.nanoTime();
        }

        if(bytesRead == -1)
            resourceContent = new String(array, 0, totalLength);
    }
    catch(Exception e)
    {
        e.printStackTrace();
    }

    try
    {
        if(br != null)
            br.close();
    }
    catch(IOException e)
    {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

EDIT: Получается, что если вам не нужно иметь содержание повторно кодированным (то есть, вы хотите содержание AS IS ) , вы не должны использовать любого из подклассов Reader. Просто используйте соответствующий подкласс Stream.

Замените начало предыдущего метода соответствующими строками следующего, чтобы ускорить его еще от 2 до 3 раз .

String  loadContentsFromStream(Stream aStream)
{
    BufferedInputStream br = null;
    byte[]              array;
    int                 bytesRead;
    int                 totalLength = 0;
    String              resourceContent;
    long                stopTime;
    long                nowTime;

    resourceContent = "";
    try
    {
        br = new BufferedInputStream(aStream);
        array = new byte[kBufferInitialSize];
Huperniketes
источник
Это намного быстрее, чем приведенные выше и принятые ответы. Как вы используете «Reader» и «Stream» на Android?
SteveGSD
1

Если файл длинный, вы можете оптимизировать свой код, добавив StringBuilder вместо использования конкатенации строк для каждой строки.

Морис Перри
источник
Это не так уж долго, если честно - это источник страницы сайта www.cokezone.co.uk - так что на самом деле не так уж и много. Определенно меньше, чем 100 КБ.
RenegadeAndy
У кого-нибудь есть какие-либо идеи о том, как это можно сделать более эффективным - или если это даже неэффективно !? Если последнее верно - почему так долго? Я не верю, что связь виновата.
RenegadeAndy
1
    byte[] buffer = new byte[1024];  // buffer store for the stream
    int bytes; // bytes returned from read()

    // Keep listening to the InputStream until an exception occurs
    while (true) {
        try {
            // Read from the InputStream
            bytes = mmInStream.read(buffer);

            String TOKEN_ = new String(buffer, "UTF-8");

            String xx = TOKEN_.substring(0, bytes);
Хосе Араужо
источник
1

Для преобразования InputStream в String мы используем метод BufferedReader.readLine () . Мы выполняем итерацию до тех пор, пока BufferedReader не возвратит ноль, что означает, что больше нет данных для чтения. Каждая строка добавляется в StringBuilder и возвращается как String.

 public static String convertStreamToString(InputStream is) {

        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();

        String line = null;
        try {
            while ((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return sb.toString();
    }
}`

И, наконец, из любого класса, в который вы хотите конвертировать вызов функции

String dataString = Utils.convertStreamToString(in);

полный

Юбарадж Пудель
источник
-1

Я использую, чтобы прочитать полные данные:

// inputStream is one instance InputStream
byte[] data = new byte[inputStream.available()];
inputStream.read(data);
String dataString = new String(data);
Рональд
источник