Android有效地从输入流中读取

Posted

技术标签:

【中文标题】Android有效地从输入流中读取【英文标题】:Android Reading from an Input stream efficiently 【发布时间】:2011-01-30 07:53:15 【问题描述】:

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

我正在使用 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 【参考方案1】:

您的代码中的问题是,它创建了大量的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 是字符串,将both a b 的内容复制到新的对象(请注意,您还复制了a,其中包含累积 String),并且您在每次迭代中都进行这些复制。 a.append(b),其中aStringBuilder,直接将b 的内容附加到a,这样就不会在每次迭代时复制累积的字符串。

【讨论】:

对于奖励积分,提供初始容量以避免在 StringBuilder 填满时重新分配:StringBuilder total = new StringBuilder(inputStream.available()); 这不是删掉换行符吗? 不要忘记将 while 包裹在 try / catch 中,如下所示:try while ((line = r.readLine()) != null) total.append(line); catch (IOException e) Log.i(tag, "problem with readline in inputStreamToString function"); @botbot:记录和忽略异常并不比仅仅忽略异常好多少...... 令人惊奇的是,Android 没有内置的流到字符串的转换。让网络上的每个代码 sn-p 和地球上的应用程序重新实现 readline 循环是荒谬的。这种模式应该在 70 年代随着豌豆绿消失。【参考方案2】:

您是否尝试过将流转换为字符串的内置方法?它是 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

【讨论】:

我意识到这是一个迟到的回应,但刚刚通过谷歌搜索偶然发现了这个。 android API 不包含 IOUtils 对,这就是我提到拥有它的外部库的原因。我将该库添加到我的 Android 项目中,它可以轻松地从流中读取。 我在哪里可以下载这个,你是如何将它导入到你的android项目中的? 如果你必须下载它,我不会称它为“内置”;不过,我只是下载了它,并会试一试。【参考方案3】:

番石榴的另一种可能性:

依赖:compile 'com.google.guava:guava:11.0.2'

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

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

【讨论】:

【参考方案4】:

我相信这已经足够高效了...要从 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 设置为参数。

【讨论】:

【参考方案5】:

这个呢。似乎提供了更好的性能。

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 的

【讨论】:

问题是 - 在我开始之前我不知道我正在阅读的内容的大小 - 所以可能还需要某种形式的数组增长。除非您可以通过 http 查询 InputStream 或 URL,以了解我检索的内容有多大,以优化字节数组的大小。我必须在移动设备上保持高效,这是主要问题!不过感谢您的想法 - 今晚会试一试,让您知道它在性能提升方面的处理方式! 我认为传入流的大小并不那么重要。上面的代码一次读取 1000 个字节,但您可以增加/减少该大小。通过我的测试,我使用 1000/10000 字节的天气并没有太大的不同。不过,那只是一个简单的 Java 应用程序。它在移动设备上可能更重要。 您最终可能会得到一个 Unicode 实体,该实体被分成两个后续读取。更好地阅读直到某种边界字符,例如 \n,这正是 BufferedReader 所做的。【参考方案6】:

可能比 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();

【讨论】:

你能解释一下为什么它会更快吗? 它不会扫描输入中的换行符,而只是读取 1024 字节的块。我并不是说这会产生任何实际影响。 @Ronald 答案的任何 cmets 吗?他也在做同样的事情,但更大的块等于 inputStream 大小。另外,如果我扫描 char 数组而不是 byte 数组作为 Nikola 的回答,这又有什么不同?其实我只是想知道在哪种情况下哪种方法最好? readLine 也删除了 \n 和 \r 但我什至看到了他们正在使用 readline 的 google io 应用程序代码【参考方案7】:

也许与其读取“一次一行”并加入字符串,不如尝试“读取所有可用”以避免扫描行尾,并避免字符串连接。

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 不不不不,我的意思是简单的 byte total[] = new [instrm.available()]; instrm.read(total,0,total.length); 如果您随后需要它作为字符串,请使用 String asString = String(total,0,total.length,"utf-8"); // 假设 utf8 :-) 【参考方案8】:

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

我能够通过分配一个大小合适的字节数组来保存流数据,并在需要时将其迭代地替换为更大的数组,并尝试读取数组所能容纳的尽可能多的数据,从而获得更好的性能。

由于某种原因,当代码使用 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上使用“Reader”和“Stream”?【参考方案9】:

如果文件很长,您可以通过附加到 StringBuilder 来优化您的代码,而不是对每一行使用字符串连接。

【讨论】:

说实话没那么久——它是网站 www.cokezone.co.uk 的页面来源——所以真的没那么大。绝对小于 100kb。 是否有人对如何提高效率有任何其他想法 - 或者如果这甚至是低效的!?如果后者是真的 - 为什么需要这么长时间?我不认为这种联系是罪魁祸首。【参考方案10】:
    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);

【讨论】:

【参考方案11】:

要将 InputStream 转换为 String,我们使用 BufferedReader.readLine() 方法。我们迭代直到 BufferedReader 返回 null,这意味着没有更多数据要读取。每行都将附加到 StringBuilder 并作为字符串返回。

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

完成

【讨论】:

【参考方案12】:

我是用来读取完整数据的:

// inputStream is one instance InputStream
byte[] data = new byte[inputStream.available()];
inputStream.read(data);
String dataString = new String(data);

请注意,这适用于存储在磁盘上的文件,而不适用于没有默认大小的流。

【讨论】:

以上是关于Android有效地从输入流中读取的主要内容,如果未能解决你的问题,请参考以下文章

从外部附件输入流中读取并推送到 s3 的最有效方法?

有效地从压缩的、分块的 HTTP 流中读取行,因为它们到达

是否可以从输入流中“丢弃”读取值?

Io流中的其他流

从输入流中读取格式化文本[重复]

字符输入流读取文本文件ReaderFileReaderBufferedReader