new InputStream().available()方法的讲解

本文探讨了InputStream的available()方法在不同场景下的表现,特别是网络传输与本地文件读取时的区别。指出了在网络不稳定情况下使用该方法可能导致的死循环问题,并提供了改进方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

案例一:

先看看下面这段代码(有删节)

public static String send(String sendurl, String sendText) throws Exception {
URL url = null;
URLConnection uc = null;

//建立连接、输出数据等;

String strReceive = "";
try {
InputStream is = uc.getInputStream();

DataInputStream in = new DataInputStream(is);
int blockLen = in.available();
byte block[] = new byte[blockLen];
for (int readLen = -9999; readLen != -1;) {
readLen = in.read(block);

if (readLen != -1)
strReceive = strReceive + new String(block);
}
is.close();
in.close();
} catch (IOException e) {
logger.info("httpl接收错误1:" + e.getMessage());
}
return strReceive;
}

注意红色字体那几行。blockLen被用来创建一个字节数组block,block作为数据缓冲来读取inputstream里的数据。然后循环从inputstream中读取数据,写入block中。

考虑一种情况。如果网络阻塞了,inputstream已经打开,但是数据却还没有传输过来,会发生什么?

inputstream.available()方法返回的值是该inputstream在不被阻塞的情况下一次可以读取到的数据长度。如果数据还没有传输过来,那么这个inputstream势必会被阻塞,从而导致inputstream.available返回0。而对inputstream.read(byte[] b)而言,如果b的长度等于0,该方法将返回0。

回头看看这个循环体的结束条件,是readLen == -1时跳出。显然,上面提到的网络阻塞情况发生之后,代码将陷入这个死循环当中。

这是我们在工程应用中遇到的一个问题。由外包商提供的工具jar包中的这段代码,直接将我们的服务器拉进了死循环。

我们的解决方法,是将整个接收与发送的方法进行改写,使用了下面的这段代码:

HttpClient client = new HttpClient();
PostMethod method = new PostMethod(prpUrl);
method.setRequestBody(sendstr);
method.getParams().setParameter(
HttpMethodParams.HTTP_CONTENT_CHARSET, "GBK");
client.executeMethod(method);
// rtnXml = method.getResponseBodyAsString();
InputStream txtis = method.getResponseBodyAsStream();
BufferedReader br = new BufferedReader(new InputStreamReader(txtis));
String tempbf;
StringBuffer html = new StringBuffer(100);
while ((tempbf = br.readLine()) != null) {
html.append(tempbf);
}
rtnXml = html.toString();

method.releaseConnection();

确确实实的,解决了问题。

如果仍然要采用原方法中手动打开输入流、建立缓冲区、循环读取数据的方法,那么不应该用available这个字段来作为缓冲区的初始长度。可以考虑手工设定一个固定值;或者读取http报文头的content-length属性值。最后的这种方式没有尝试过。

=====================================================================================

案例二

如果用inputStream对象的available()方法获取流中可读取的数据大小,通常我们调用这个函数是在下载文件或者对文件进行其他处理时获取文件的总大小。

以前在我们初学File和inputStream和outputStream时,有需要将文件从一个文件夹复制到另一个文件夹中,这时候我们用的就是inputStream.available()来获取文件的总大小,而且屡试不爽。

但是当我们要从网络URL中下载一个文件时,我们发现得到的数值并不是需要下载的文件的总大小。

好吧。我们看看JDK文档中怎么解释。

available

public int available() throws IOException
返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数。下一个调用可能是同一个线程,也可能是另一个线程。一次读取或跳过此估计数个字节不会受阻塞,但读取或跳过的字节数可能小于该数。

注意,有些 InputStream 的实现将返回流中的字节总数,但也有很多实现不会这样做。试图使用此方法的返回值分配缓冲区,以保存此流所有数据的做法是不正确的。

如果已经调用 close() 方法关闭了此输入流,那么此方法的子类实现可以选择抛出IOException

InputStreamavailable 方法总是返回 0

此方法应该由子类重写。

返回:
可以不受阻塞地从此输入流读取(或跳过)的估计字节数;如果到达输入流末尾,则返回 0
抛出:
IOException - 如果发生 I/O 错误。

inputStream 源代码

/**
* Returns the number of bytes that are available before this stream will
* block. This implementation always returns 0. Subclasses should override
* and indicate the correct number of bytes available.
*
* @return the number of bytes available before blocking.
* @throws IOException
* if an error occurs in this stream.
* @since Android 1.0
*/
<span style="color:#ff0000;"> public int available() throws IOException {
return 0;

}</span>

这里返回的是 0 值。

所以说要从网络中下载文件时,我们知道网络是不稳定的,也就是说网络下载时,read()方法是阻塞的,说明这时我们用

inputStream.available()获取不到文件的总大小。

但是从本地拷贝文件时,我们用的是FileInputStream.available(),难道它是将先将硬盘中的数据先全部读入流中?

然后才根据此方法得到文件的总大小?

好吧,我们来看看FileInputStream源代码吧

/**
* Returns the number of bytes that are available before this stream will
* block. This method always returns the size of the file minus the current
* position.
*
* @return the number of bytes available before blocking.
* @throws IOException
* if an error occurs in this stream.
* @since Android 1.0
*/
@Override
public int available() throws IOException {
openCheck();

// BEGIN android-added

// Android always uses the ioctl() method of determining bytes
// available. See the long discussion in
// org_apache_harmony_luni_platform_OSFileSystem.cpp about its
// use.

<span style="color:#ff0000;">return fileSystem.ioctlAvailable(fd.descriptor);</span>
// END android-added

// BEGIN android-deleted
// synchronized (repositioningLock) {
// // stdin requires special handling
// if (fd == FileDescriptor.in) {
// return (int) fileSystem.ttyAvailable();
// }
//
// long currentPosition = fileSystem.seek(fd.descriptor, 0L,
// IFileSystem.SEEK_CUR);
// long endOfFilePosition = fileSystem.seek(fd.descriptor, 0L,
// IFileSystem.SEEK_END);
// fileSystem.seek(fd.descriptor, currentPosition,
// IFileSystem.SEEK_SET);
// return (int) (endOfFilePosition - currentPosition);
// }
// END android-deleted
}

这里重写了inputStream中的available()方法

关键是:fileSystem.ioctlAvailable(fd.descriptor);

调用了FileSystem这是java没有公开的一个类,JavaDoc API没有。
其中

fileSystem 是一个IFileSystem对象,IFileSySTEM是java没有公开的一个类,JavaDoc API中没有;

fd是一个FileDescriptor对象,即文件描述符

说明这句代码应该是通过文件描述符获取文件的总大小,而并不是事先将磁盘上的文件数据全部读入流中,再获取文件总大小

搞清楚了这些,但是我们的主要问题没有解决,怎么获得网络文件的总大小?

我想原理应该都差不多,应该也是通过一个类似文件描述符的东西来获取。

网络下载获取文件总大小的代码:

<span style="color:#ff0000;">HttpURLConnection httpconn = (HttpURLConnection)url.openConnection();
httpconn.getContentLength();</span>

我们再来看看httpconn.getContentLength();

/**
* Gets the content length in bytes specified by the response header field
* {@code content-length} or {@code -1} if this field is not set.
*
* @return the value of the response header field {@code content-length}.
* @since Android 1.0
*/
public int getContentLength() {
<span style="color:#ff0000;">return getHeaderFieldInt("Content-Length", -1);</span> //$NON-NLS-1$
}

关键:getHeaderFieldInt("Content-Length", -1);

意思是从http预解析头中获取“Content-length”字段的值

其实也是类似从文件描述符中获取文件的总大小

===========================================================================

我的简练的总结:

FileInputStream 的 available()就是返回的的,实际可读字节数,也就是总大小

FileInputStream的available() 方法返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取(或跳过)的估计剩余字节数。

在某些情况下,非阻塞的读取(或跳过)操作在执行很慢时看起来受阻塞,例如,在网速缓慢的网络上读取大文件时。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值