Giriş akışından verimli bir şekilde Android Okuma


152

Yaptığım bir android uygulaması için bir web sitesine bir HTTP alma isteği yapıyorum.

Bir DefaultHttpClient ve isteği vermek için HttpGet kullanıyorum. Varlık yanıtı almak ve bu sayfanın html almak için bir InputStream nesnesi elde.

Daha sonra aşağıdaki gibi cevap verme döngüsü:

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

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

Ancak bu çok yavaş.

Bu verimsiz mi? Büyük bir web sayfası yüklemiyorum - www.cokezone.co.uk, böylece dosya boyutu büyük değil. Bunu yapmanın daha iyi bir yolu var mı?

Teşekkürler

Andy


Gerçekten satırları ayrıştırmıyorsanız, satır satır okumak çok mantıklı değildir. Ben char tarafından sabit boyutlu arabelleklerle char okumayı tercih ederim: gist.github.com/fkirc/a231c817d582e114e791b77bb33e30e9
Mike76

Yanıtlar:


355

Kodunuzdaki sorun, çok sayıda ağır Stringnesne oluşturması , içeriklerini kopyalaması ve üzerlerinde işlemler gerçekleştirmesidir. Bunun yerine, her ekte StringBuilderyeni Stringnesneler oluşturmaktan ve karakter dizilerini kopyalamaktan kaçınmak için kullanmalısınız. Davanız için uygulama şu şekilde olacaktır:

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

Artık totaldönüştürmeden kullanabilirsiniz String, ancak sonuca a olarak ihtiyacınız varsa şunları Stringeklemeniz yeterlidir:

Dize sonucu = total.toString ();

Daha iyi açıklamaya çalışacağım ...

  • a += b(veya a = a + b), nerede ave bDizeleri, kopyalar içeriği olan hem a ve b (siz de kopyalama o notta Yeni bir nesneye aiçeren, birikmiş String ) ve her tekrarında bu kopyaları yapıyoruz.
  • a.append(b), nerede abir StringBuilder, biçeriği doğrudan ekler a, böylece her yinelemede biriken dizeyi kopyalamazsınız.

23
Bonus puanlar için, StringBuilder StringBuilder total = new StringBuilder(inputStream.available());
dolarken

10
Bu yeni satır karakterlerini kesmiyor mu?
Nathan Schwermann

5
denemeyi / yakalamayı şu şekilde tamamlamayı unutmayın: try {while ((line = r.readLine ())! = null) {total.append (line); }} catch (IOException e) {Log.i (tag, "inputStreamToString işlevindeki readline ile ilgili sorun"); }
botbot

4
@botbot: Bir istisnanın günlüğe kaydedilmesi ve göz ardı edilmesi, istisnayı görmezden gelmekten daha iyi değildir ...
Matti Virkkunen

50
Android'in yerleşik bir dize-dize dönüştürmesine sahip olmaması şaşırtıcı. Web üzerindeki her kod snippet'ini ve gezegendeki uygulamayı bir readlinedöngüyü yeniden uygulamak çok saçma. Bu model 70'lerde bezelye yeşili ile ölmüş olmalıydı.
Edward Brey

35

Bir akışı bir dizeye dönüştürmek için yerleşik yöntemi denediniz mi? Apache Commons kütüphanesinin (org.apache.commons.io.IOUtils) bir parçasıdır.

O zaman kodunuz şu tek satır olur:

String total = IOUtils.toString(inputStream);

Bununla ilgili belgeleri burada bulabilirsiniz: http://commons.apache.org/io/api-1.4/org/apache/commons/io/IOUtils.html#toString%28java.io.InputStream%29

Apache Commons IO kütüphanesi buradan indirilebilir: http://commons.apache.org/io/download_io.cgi


Bunun geç bir yanıt olduğunu anlıyorum, ancak şimdi bir Google aramasıyla bununla karşılaştık.
Makotosan

61
Android API, IOUtils
Charles Ma

2
Evet, bu yüzden ona sahip olan harici kütüphaneden bahsettim. Kütüphaneyi Android projeme ekledim ve akışlardan okumayı kolaylaştırdı.
Makotosan

bunu nereden indirebilirim ve bunu android projenize nasıl aktardınız?
safari

3
Eğer indirmek zorunda, ben "yerleşik" olarak adlandırmaz; Yine de, sadece indirdim ve bir şans vereceğim.
B. Clay Shannon

15

Guava ile başka bir olasılık:

bağımlılık: compile 'com.google.guava:guava:11.0.2'

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

String total = new String(ByteStreams.toByteArray(inputStream ));

9

Bu yeterince verimli olduğuna inanıyorum ... Bir InputStream bir dize almak için aşağıdaki yöntemi çağırır:

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();
}

Her zaman UTF-8 kullanıyorum. Elbette, InputStream öğesinin yanında karakter kümesini bağımsız değişken olarak ayarlayabilirsiniz.


6

Peki buna ne dersin. Daha iyi performans veriyor gibi görünüyor.

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));
}

Edit: Aslında bu tür hem çelikbytes hem de Maurice Perry's kapsar


Sorun - ben başlamadan önce im okuma şey boyutunu bilmiyorum - yani dizi de büyüyen bir tür gerekebilir. Inless almak bir şey bayt dizisinin boyutunu optimize etmek için ne kadar büyük olduğunu bulmak için bir InputStream veya URL sorgulayabilirsiniz. Ben asıl sorun olan bir mobil cihazda olduğu gibi verimli olmak zorunda! Ancak bu fikir için teşekkürler - Bu gece bir şans verecek ve performans kazancı açısından nasıl işlediğini size bildirir!
RenegadeAndy

Gelen akışın büyüklüğünün o kadar önemli olduğunu düşünmüyorum. Yukarıdaki kod bir kerede 1000 bayt okur, ancak bu boyutu artırabilir / azaltabilirsiniz. Testlerimle 1000/10000 bayt kullandığım o kadar fazla hava yaratmadı. Bu sadece basit bir Java uygulamasıydı. Bir mobil cihazda daha önemli olabilir.
Adrian

4
Sonrasında iki okumaya bölünmüş bir Unicode varlığıyla sonuçlanabilir. BufferedReader'ın yaptığı gibi \ n gibi bir çeşit sınır karaktere kadar okumak daha iyidir.
Jacob Nordfalk

4

Muhtemelen Jaime Soriano'nun cevabından biraz daha hızlı ve Adrian'ın cevabının çok baytlı kodlama problemleri olmadan şunları öneririm:

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();
}

Neden daha hızlı olacağını açıklayabilir misiniz?
Akhil Dad

Girişi satırsonu karakterleri için taramaz, sadece 1024 baytlık parçaları okur. Bunun pratik bir fark yaratacağını iddia etmiyorum.
heiner

@Ronald cevabı hakkında herhangi bir yorum var mı? Aynı şeyi yapıyor ancak inputStream boyutuna eşit daha büyük bir yığın için. Ayrıca Nikola cevap olarak bayt dizisi yerine char dizisi tararsam ne kadar farklıdır? Aslında hangi yaklaşımın hangi durumda en iyi olduğunu bilmek istedim? Ayrıca readLine \ n ve \ r kaldırır ama ben bile google io app kodu readline kullandıklarını gördüm
Akhil Baba

3

Belki daha sonra 'bir seferde bir satır' okuyun ve dizelere katılın, satır sonu taramasından kaçınmak ve dize birleştirmelerinden kaçınmak için 'mevcut olanları oku' seçeneğini deneyin.

yani InputStream.available()veInputStream.read(byte[] b), int offset, int length)


Hmm. yani şöyle olur: int offset = 5000; Bayt [] bArr = yeni Bayt [100]; Bayt [] toplam = Bayt [5000]; while (InputStream.available) {offset = InputStream.read (bArr, ofset, 100); (int i = 0; i <ofset; i ++) {toplam [i] = bArr [i]; } bArr = yeni Bayt [100]; } Bu gerçekten daha verimli mi - yoksa kötü yazdım mı? Lütfen bir örnek verin!
RenegadeAndy

2
hayır hayır hayır hayır, basitçe {toplam bayt [] = yeni [instrm.available ()]; instrm.read (toplam, 0, total.length); } ve daha sonra bir String olarak gerekiyorsa, {String asString = String (total, 0, total.length, "utf-8"); // utf8 varsayalım :-)}
SteelBytes

2

Bir kerede bir metin satırı okumak ve adı geçen satırı bir dizeye ayrı ayrı eklemek, her satırı ayıklamak ve çok sayıda yöntem çağrısının ek yükü için zaman alıcıdır.

Akış verilerini tutmak için düzgün boyutlu bir bayt dizisi ayırarak ve gerektiğinde yinelemeli olarak daha büyük bir dizi ile değiştirilerek ve dizi tutabildiği kadar okumaya çalışarak daha iyi performans elde etmeyi başardım.

Bazı nedenlerden dolayı, kod HTTPUrlConnection tarafından döndürülen InputStream'i kullandığında Android tekrar tekrar tüm dosyayı indiremedi, bu yüzden tüm dosyayı alacağımı veya iptal edeceğimden emin olmak için hem BufferedReader hem de elle yuvarlanan bir zaman aşımı mekanizması kullanmak zorunda kaldım transfer.

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: İçeriğin yeniden kodlanması gerekmiyorsa (yani içeriği OLDUĞUNU istiyorsanız) Reader alt sınıflarından hiçbirini kullanmamanız gerektiği ortaya çıkıyor. Uygun Stream alt sınıfını kullanmanız yeterlidir.

2 ila 3 kat fazla hızlandırmak için önceki yöntemin başlangıcını aşağıdaki satırlarla değiştirin .

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];

Bu yukarıdakilerden çok daha hızlı ve kabul edilen cevaplar. Android'de "Reader" ve "Stream" yazılımlarını nasıl kullanıyorsunuz?
SteveGSD

1

Dosya uzunsa, her satır için bir String birleştirme kullanmak yerine bir StringBuilder ekleyerek kodunuzu optimize edebilirsiniz.


Dürüst olmak gerekirse bu uzun değil - onun web sitesi www.cokezone.co.uk sayfa kaynağı - gerçekten büyük değil. Kesinlikle 100kb'den az.
RenegadeAndy

Bunun nasıl daha verimli hale getirilebileceği konusunda başka fikirleri olan var mı - yoksa bu verimsiz olsa bile !? İkincisi doğruysa - neden bu kadar uzun sürüyor? Bağlantının suçlanmak olduğuna inanmıyorum.
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 öğesini String'e dönüştürmek için BufferedReader.readLine () yöntemini kullanırız. BufferedReader null değerine dönene kadar yineliyoruz , yani okunacak başka veri yok. Her satır bir StringBuilder öğesine eklenir ve String olarak döndürülür.

 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();
    }
}`

Ve son olarak dönüştürmek istediğiniz herhangi bir sınıftan işlevi çağırın

String dataString = Utils.convertStreamToString(in);

tamamlayınız


-1

Tüm verileri okumak için kullanıyorum:

// inputStream is one instance InputStream
byte[] data = new byte[inputStream.available()];
inputStream.read(data);
String dataString = new String(data);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.