Android从输入流中高效读取


152

我正在向正在制作的android应用程序的网站发出HTTP get请求。

我正在使用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,因此文件大小不大。有一个更好的方法吗?

谢谢

安迪


除非您实际上是在解析行,否则逐行读取并没有多大意义。我宁愿通过固定大小的缓冲区按字符读取字符:gist.github.com/fkirc/a231c817d582e114e791b77bb33e30e9
Mike76 '19

Answers:


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),其中ab都是字符串,副本的内容 a b一个新的对象(注意,你还复制a,它包含累积 String),和你正在做的每一次迭代的拷贝。
  • a.append(b),其中aStringBuilder,直接将b内容附加到a,因此您不必在每次迭代时都复制累积的字符串。

23
对于奖励积分,请在StringBuilder填满时提供初始容量来避免重新分配:StringBuilder total = new StringBuilder(inputStream.available());
dokkaebi,2012年

10
这不是剪出换行符吗?
内森·史威文2012年

5
不要忘了像这样在try / catch中包装一会儿:try {while((line = r.readLine())!= null){total.append(line); }} catch(IOException e){Log.i(tag,“ inputStreamToString函数中的读取行问题”); }
botbot

4
@botbot:记录和忽略异常并不比忽略异常好多少……
Matti Virkkunen 2012年

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


我意识到这是一个较晚的响应,但就在偶然之间,通过Google搜索偶然发现了这个响应。
Makotosan

61
android API不包括IOUtils
Charles Ma,2010年

2
是的,这就是为什么我提到了拥有它的外部库。我将该库添加到了我的Android项目中,它使从流中读取变得很容易。
Makotosan

我在哪里可以下载此文件,以及如何将其导入到Android项目中?
苹果浏览器

3
如果您必须下载它,我不会将其称为“内置”。不过,我只是下载了它,然后继续尝试。
B. Clay Shannon 2014年

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。当然,除了InputStream之外,您还可以将charset设置为参数。


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,也包含了Maurice Perry's


问题是-我不知道在开始之前我正在阅读的东西的大小-因此可能还需要某种形式的数组增长。除非如此,否则您可以通过http查询InputStream或URL,以了解正在检索的内容对于优化字节数组的大小有多大。我必须在移动设备上保持高效,这是主要问题!但是,感谢您提出这个想法-今晚会试一试,让您知道它如何处理性能提升!
RenegadeAndy

我认为传入流的大小没有那么重要。上面的代码一次读取1000个字节,但是您可以增加/减小该大小。通过我的测试,我使用1000/10000字节的天气并没有太大的不同。那只是一个简单的Java应用程序。在移动设备上可能更重要。
阿德里安

4
您可能最终得到一个Unicode实体,该实体被分成两个后续的读取。最好读取直到某种边界字符(例如\ n),这正是BufferedReader所做的。
2011年

4

可能比Jaime Soriano的回答要快一些,并且没有Adrian回答的多字节编码问题,我建议:

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

您能解释一下为什么会更快吗?
Akhil Dad

它不会在输入中扫描换行符,而只会读取1024字节的数据块。我不认为这会带来任何实际的改变。
heiner

@Ronald答案有任何评论吗?他正在做相同的事情,但是要使用更大的块(等于inputStream的大小)。如果我扫描字符数组而不是字节数组作为Nikola答案,还有什么不同?其实我只是想知道哪种方法在哪种情况下最好?readLine也删除了\ n和\ r,但是我什至看到Google io应用程序代码也都在使用readline
Akhil Dad

3

也许而不是一次读取“一行”并连接字符串,尝试“读取所有可用内容”,以避免扫描行尾,也避免了字符串连接。

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


嗯 就像这样:int offset = 5000; Byte [] bArr =新的Byte [100];Byte []总计= 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
不,不,不,我的意思是{字节总数[] =新[instrm.available()];instrm.read(total,0,total.length); },如果您随后需要将其用作字符串,请使用{String asString = String(total,0,total.length,“ utf-8”); //假定为utf8 :-)}
SteelBytes,2010年

2

一次读取一行文本,并将该行单独添加到字符串上,这在提取每一行和如此多的方法调用的开销方面都非常耗时。

我可以通过分配一个大小合适的字节数组来保存流数据来获得更好的性能,并在需要时用较大的数组迭代替换它,并尝试读取尽可能多的数组。

由于某种原因,当代码使用HTTPUrlConnection返回的InputStream时,Android多次无法下载整个文件,因此我不得不求助于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();
    }
}

编辑:事实证明,如果您不需要重新编码内容(即,您希望内容按原样),则不应使用任何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];

这比上述和公认的答案要快得多。您如何在Android上使用“阅读器”和“流”?
SteveGSD 2014年

1

如果文件很长,可以通过附加到StringBuilder而不是为每行使用String串联来优化代码。


老实说,它的时间并不长-它是网站www.cokezone.co.uk的页面来源-真的不那么大。绝对小于100kb。
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返回null,这意味着没有更多数据可读取。每行将追加到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);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.