InputStream.available()使用大坑

在实现文件下载功能时,遇到文件内容不完整的问题。经过排查,问题源于使用了InputStream.available()方法,该方法在处理网络数据传输时可能导致长时间暂停,影响文件传输的完整性。因此,应当避免在涉及网络数据流时使用此方法。

问题场景:文件下载功能,需要对原来的文件进行读取再输出到文件或浏览器中,会常用到InputStream.available()方法。经历过一次系统功能变更升级,需要将文件先下载到本地再响应出去,发现下载的文件不全

百思不得其解,搜索好久资料,查找得知,以备忘记

这个方法可以在读写操作前先得知数据流里有多少个字节可以读取。
   需要注意的是,如果这个方法用在从本地文件读取数据时,一般不会遇到问题,
   但如果是用于网络操作,就经常会遇到一些麻烦。比如,Socket通讯时,
   对方明明发来了1000个字节,但是自己的程序调用available()方法却只得到900,
   或者100,甚至是0,感觉有点莫名其妙,怎么也找不到原因。
   其实,这是因为网络通讯往往是间断性的,一串字节往往分几批进行发送。
   本地程序调用available()方法有时得到0,这可能是对方还没有响应,也可能是对方已经响应了,
   但是数据还没有送达本地。对方发送了1000个字节给你,也许分成3批到达,
   这你就要调用3次available()方法才能将数据总数全部得到。

所以在进行网络数据传输时候,不能使用InputStream.available(),这个方法导致系统出现长时间暂停状态

<think> 我们正在讨论Java中的InputStream.available()方法。根据引用[2]中的内容,用户提到了使用available()方法来确定要读取的字节数。让我们总结一下这个方法的作用、用法和注意事项。 作用: available()方法用于返回可以从此输入中读取(或跳过)的剩余字节数的估计值,而不会被阻塞。在文件输入中,这通常是文件的总大小减去已读取的字节数。但在网络中,它可能只返回当前可读取的字节数(缓冲区中的数据),而不一定是整个的剩余字节数。 用法: 通常,我们可以使用available()来获取一个估计值,然后根据这个值来创建缓冲区。例如: InputStream in = ...; int availableBytes = in.available(); if (availableBytes > 0) { byte[] buffer = new byte[availableBytes]; in.read(buffer); } 注意事项: 1. 这个方法返回的是一个估计值,不能依赖它来获取的全部剩余字节数,特别是对于网络和某些类型的,因为数据可能还在传输中。 2. 对于文件available()通常返回整个文件的剩余字节数(因为文件大小已知)。 3. 对于网络,它只返回当前已到达并缓冲的字节数,因此如果网络延迟,这个值可能比实际要传输的剩余字节少。 4. 不能使用available()来预先分配一个缓冲区然后一次性读取整个,因为可能数据还没有全部到达(网络)或者这个值可能返回0(例如某些可能没有实现available(),或者实现不准确)。 5. 如果需要读取整个,通常还是使用循环读取直到返回-1。 引用[2]中提到,用户最初使用固定大小的缓冲区(如1024字节)进行读取,然后担心如果超过这个大小怎么办。实际上,使用循环读取的方式(即使缓冲区大小固定)也能读取任意长度的,因为read方法会一直读取直到结束(返回-1)。因此,我们通常不需要使用available()来设置缓冲区大小,除非我们想要一次性读取当前可用的所有数据(比如非阻塞IO场景)。 另外,引用[3]提到了BufferedInputStream,它内部有一个缓冲区(默认8KB),可以提升读取效率。当我们使用BufferedInputStream时,available()方法返回的是该缓冲区内可读的字节数,而不是底层的全部可用字节数。 因此,总结如下: - 在文件读写中,available()可以用于获取文件的剩余字节数(但要注意,如果文件很大,可能超出int的范围,因此Java中还有一个long available()的方法?注意:实际上InputStream中的available()返回int,而FileInputStream重写了此方法,返回的是long类型?不对,实际上InputStreamavailable()返回int,FileInputStream也返回int,所以最大只能表示2GB左右的剩余字节数。如果要处理更大的文件,应该使用其他方式)。 - 在网络编程中,避免使用available()来获取整个的大小,因为它不可靠。 示例代码: 下面展示一个错误用法和一个正确用法。 错误用法(依赖available()读取整个): ```java // 错误:因为available()可能只返回0(尽管还有数据)或者返回的值小于实际剩余字节数 InputStream in = ...; byte[] data = new byte[in.available()]; // 这里可能会分配一个0长度的数组,或者不完整的长度 in.read(data); // 可能不会读取全部数据 ``` 正确用法(循环读取直到结束): ```java InputStream in = ...; ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; // 固定大小的缓冲区 int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } byte[] data = out.toByteArray(); ``` 另外,在引用[4]中,我们看到使用ByteArrayResource封装字节数组,然后获取其输入。对于这种情况,available()方法会返回整个字节数组的长度,因为所有的数据都在内存中。 因此,使用available()的注意事项: - 不要依赖它来获取整个的大小(尤其是网络),除非确定该类型支持且数据已经全部可用。 - 在非阻塞IO中,available()可能返回0,即使后面还会有数据。 生成相关问题: 1. 如何正确读取InputStream直到结束? 2. 在Java中如何获取文件的总字节数? 3. BufferedInputStreamavailable()方法有什么特别之处? 4. 为什么在读取网络available()方法返回的值不可靠? </think> ### Java中InputStream.available()方法详解 #### 一、作用与定义 `InputStream.available()`用于获取**无需阻塞即可读取的字节数**的估计值: 1. **非阻塞检测**:返回当前可立即读取的字节数,避免线程阻塞[^1] 2. **资源预判**:辅助判断输入剩余数据量,常用于: - 缓冲区大小动态分配 - 预估文件传输进度 - 网络可用性检测 3. **方法签名**:`public int available() throws IOException` #### 二、典型用法示例 ```java try (InputStream in = new FileInputStream("data.rar")) { // 获取可读字节数 int available = in.available(); if (available > 0) { // 动态创建缓冲区 byte[] buffer = new byte[available]; // 一次性读取所有可用字节 int bytesRead = in.read(buffer); System.out.printf("读取 %d/%d 字节%n", bytesRead, available); } else { System.out.println("无可用数据"); } } catch (IOException e) { e.printStackTrace(); } ``` #### 三、关键注意事项 1. **返回值性质**: - 仅为**估计值**,不保证精确性[^2] - 总是返回 ≤ 实际字节数 - 结束时返回 `0` 2. **类型差异**: | 类型 | available()行为 | |--------------------|------------------------------------------| | `FileInputStream` | 返回文件剩余字节数 | | `ByteArrayInputStream` | 返回整个字节数组长度[^4] | | `SocketInputStream`| 返回网络缓冲区当前可读字节数 | | `BufferedInputStream`| 返回内部缓冲区有效数据量[^3] | 3. **使用禁区**: ```java // 错误用法:不能依赖其读取完整文件 byte[] data = new byte[in.available()]; // 可能丢失数据! in.read(data); ``` - ❌ 不可用于大文件读取(超过`Integer.MAX_VALUE`) - ❌ 网络传输中不可靠(可能返回0但后续有数据) 4. **替代方案**: ```java // 可靠读取方案:循环+固定缓冲区 byte[] buf = new byte[8192]; // 推荐8KB缓冲区[^3] int bytesRead; while ((bytesRead = in.read(buf)) != -1) { // 处理数据 } ``` #### 四、底层原理 1. **默认实现**: - `InputStream`基类返回`0` - 具体子类需覆写实现 2. **缓冲区机制**: - `BufferedInputStream`优先返回缓冲区数据量 - 公式:`available = min(缓冲区有效字节, 底层可用字节)[^3]` $$ \text{available} = \min(\text{buf\_count} - \text{pos}, \text{in.available()}) $$ #### 五、适用场景对比 | 场景 | 推荐使用 | 注意事项 | |---------------------|----------|------------------------------| | 内存(ByteArray) | ✅ | 返回值准确[^4] | | 本地文件 | ⚠️ | 文件>2GB时返回值截断 | | 网络 | ❌ | 返回值可能为0(需轮询) | | ZIP/RAR解压 | ⚠️ | 需配合`CheckedInputStream`验证 | > 最佳实践:仅用于**优化缓冲区分配**,不可替代结束判断(`read() == -1`)[^2]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DazedMen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值