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
),其中a
和b
是字符串,将both a
和 b
的内容复制到新的对象(请注意,您还复制了a
,其中包含累积 String
),并且您在每次迭代中都进行这些复制。
a.append(b)
,其中a
是StringBuilder
,直接将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一次读取一行文本,并将所述行单独附加到一个字符串中,在提取每一行和如此多的方法调用的开销方面都非常耗时。
我能够通过分配一个大小合适的字节数组来保存流数据,并在需要时将其迭代地替换为更大的数组,并尝试读取数组所能容纳的尽可能多的数据,从而获得更好的性能。
由于某种原因,当代码使用 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有效地从输入流中读取的主要内容,如果未能解决你的问题,请参考以下文章