Ефективне читання 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, тому розмір файлу не великий. Чи є кращий спосіб зробити це?

Дякую

Енді


Якщо ви насправді не розбираєте рядки, читати рядки за рядком не має сенсу. Я б швидше читав char по char через буфери фіксованого розміру: gist.github.com/fkirc/a231c817d582e114e791b77bb33e30e9
Mike76

Відповіді:


355

Проблема у вашому коді полягає в тому, що він створює багато важких Stringоб'єктів, копіює їх вміст і виконує операції над ними. Натомість вам слід використовувати, StringBuilderщоб не створювати нових Stringоб’єктів на кожному додатку та уникати копіювання масивів char. Реалізація для вашого випадку буде приблизно такою:

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, просто додайте:

Результат рядка = total.toString ();

Спробую пояснити це краще ...

  • a += b(Або a = a + b), де aі bє рядки, копіює вміст як a і b до нового об'єкту (зверніть увагу , що ви також копіювання a, який містить накопичене String ), і ви робите ці копії на кожній ітерації.
  • a.append(b), де aa StringBuilder, безпосередньо додає bвміст a, щоб ви не копіювали накопичений рядок при кожній ітерації.

23
Для отримання бонусних балів надайте початкову спроможність уникнути перерозподілу, оскільки StringBuilder заповнює: StringBuilder total = new StringBuilder(inputStream.available());
dokkaebi

10
Це не вирізає нових символів рядка?
Натан Шверман

5
не забудьте завершити час у спробі / ловити так: спробуйте {while ((рядок = r.readLine ())! = null) {total.append (рядок); }} лов (IOException e) {Log.i (тег, "проблема з лінією читання у функції inputStreamToString"); }
ботбот

4
@botbot: Реєстрація та ігнорування винятку не набагато краща, ніж просто ігнорування винятку ...
Матті Вірккунен

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

Бібліотеку AOche Commons IO можна завантажити тут: http://commons.apache.org/io/download_io.cgi


Я усвідомлюю, що це пізня відповідь, але якраз зараз натрапив на це через пошук в Google.
Макотосан

61
API для Android не включає IOUtils
Чарльз Ма

2
Правильно, саме тому я згадав про зовнішню бібліотеку, яка має її. Я додав бібліотеку до свого проекту Android, і це полегшило читання з потоків.
Макотосан

куди я можу це завантажити та як ти імпортував це у свій проект Android?
сафарі

3
Якщо вам доведеться завантажити його, я б не називав його "вбудованим"; тим не менше, я просто завантажив його і дам йому піти.
Б. Клей Шеннон

15

Ще одна можливість з Guava:

залежність: 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. Звичайно, ви можете встановити діаграму як аргумент, окрім 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));
}

Редагувати: насправді цей тип охоплює як сталебайти, так і Моріса Перрі


Проблема полягає в тому, що я не знаю розмір речі, яку я читаю, перш ніж почати, - тому може знадобитися і деяка форма масиву. Якщо ви не можете запитати InputStream або URL за допомогою http, щоб дізнатися, наскільки велика річ у пошуку, що стосується оптимізації розміру байтового масиву. Я повинен бути ефективним, як його на мобільному пристрої, що є основною проблемою! Однак спасибі за цю ідею - сьогодні ввечері продемонструю і дам вам знати, як вона справляється з точки зору підвищення продуктивності!
RenegadeAndy

Я не думаю, що розмір вхідного потоку є таким важливим. Наведений вище код одночасно читає 1000 байт, але ви можете збільшити / зменшити цей розмір. З моїм тестуванням не змінилося погоди, я використав 1000/10000 байт. Це був просто простий додаток Java. Це може бути важливішим на мобільному пристрої.
Адріан

4
Ви можете створити об'єкт Unicode, який розрізаний на два наступні читання. Краще читати до тих пір, поки якийсь граничний символ, як-то \ n, саме це і робить BufferedReader.
Jacob Nordfalk

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 байтів. Я не стверджую, що це матиме якусь практичну зміну.
heiner

будь-які коментарі щодо відповіді @Ronald? Він робить те саме, але для більшої частини, що дорівнює розміру inputStream. Також наскільки це відрізняється, якщо я сканую масив char, а не байтовий масив, як Ніколас відповість? Насправді я просто хотів знати, який підхід найкращий у такому випадку? Також readLine видаляє \ n і \ r, але я бачив навіть код програми для google io, в якому вони використовуються readline
Akhil Dad

3

Можливо, тоді читайте "по черзі один раз" і приєднуйтесь до рядків, спробуйте "прочитати все доступне", щоб уникнути сканування кінця рядка, а також уникнути з'єднання рядків.

тобто, InputStream.available()іInputStream.read(byte[] b), int offset, int length)


Хм. тож було б так: int offset = 5000; Байт [] bArr = новий байт [100]; Байт [] всього = Байт [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
ні ні ні ні, я маю на увазі просто {байт total [] = new [instrm.available ()]; instrm.read (всього, 0, загальна довжина); } і якщо вам тоді він був потрібний як 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: Виявляється, якщо вам не потрібно повторно кодувати вміст (тобто ви хочете, щоб вміст ЯК Є ), ви не повинні використовувати жоден з підкласів 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];

Це набагато швидше, ніж наведені вище та прийняті відповіді. Як ви використовуєте "Reader" та "Stream" на android?
SteveGSD

1

Якщо файл довгий, ви можете оптимізувати свій код, додавши до StringBuilder замість того, щоб використовувати рядок String для кожного рядка.


Чесно кажучи, його джерело на веб-сайті 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);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.