本文共 6384 字,大约阅读时间需要 21 分钟。
public class Temp { public static void main(String[] args) throws IOException { String fileName = "data.txt"; File file = new File(fileName); FileInputStream fileInputStream = new FileInputStream(file); // 缓冲输入流 BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream); // do something // ... // close stream bufferedInputStream.close(); }}
通过 FileInputStream 示例对象,来构造一个 BufferedInputStream 对象,从而为 fileInputStream 对象提供额外的缓冲功能。其中 fileInputStream 为原始的字节流,它原生实现了 InputStream 接口,而 bufferedInputStream 是基于 fileInputStream ,通过覆盖它的方法从而添加额外的功能,而底层的流操作还是委托给了 fileInputStream 。这里有个问题,即 这里的变量 fileInputStream 和 bufferedInputStream,其中 bufferedInputStream 内部保存了对 fileInputStream 的引用,它们本质都是操作同一个流对象,只是 bufferedInputStream 提供了额外的功能,如果在程序中混合使用这两个对象,则可能造成混乱(特别是分别通过这两个对象进行读取操作的情况)。
示例:import java.io.BufferedInputStream;import java.io.ByteArrayInputStream;import java.io.IOException;public class Temp { public static void main(String[] args) throws IOException { byte[] bytes = { 'A', 'B'}; ByteArrayInputStream is = new ByteArrayInputStream(bytes); BufferedInputStream bis = new BufferedInputStream(is); int a = is.read(); // 1. 通过 is 读取 int b = bis.read(); // 2. 通过 bis 读取 System.out.println("" + (char)a + (char)b); // 两次读取操作的集合组成完整的数据 }}
程序中,两次读取操作的集合才能组成完整的原始数据。
如果不需要使用底层的或中间的流对象提供的特定操作,则可以将多个流对象的创建嵌套在一起,使得最终只产生一个对末端流对象的引用,这样便可以避免混乱。修改代码如下:import java.io.BufferedInputStream;import java.io.ByteArrayInputStream;import java.io.IOException;public class Temp { public static void main(String[] args) throws IOException { byte[] bytes = { 'A', 'B'}; BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(bytes)); System.out.println("" + (char)bis.read() + (char)bis.read()); }}
本文开头的示例程序,也可以修改为:
import java.io.BufferedInputStream;import java.io.File;import java.io.FileInputStream;import java.io.IOException;public class Temp { public static void main(String[] args) throws IOException { String fileName = "data.txt"; // 缓冲输入流 BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(new File(fileName))); // do something // ... // close stream bufferedInputStream.close(); }}
import java.io.BufferedInputStream;import java.io.ByteArrayInputStream;import java.io.IOException;public class Temp { public static void main(String[] args) throws IOException { // 生产字节,用于读取 int bytes_num = 10; byte[] bytes = new byte[bytes_num]; for(int i = 0;i < bytes_num;i++) { bytes[i] = (byte)(i + 1); } // 指定缓冲区大小为 5 BufferedInputStream is = new BufferedInputStream(new ByteArrayInputStream(bytes), 5); // 在 pos 为 0 的位置 mark,且 marklimit = 5 = 缓冲区长度 is.mark(5); // 连续读取 5 次,最后 pos = count = 5 = 缓冲区长度 for(int i = 0;i < 5;i++) { is.read(); } // 当再次读取时,前面标记的 mark 位置,将被失效 is.read(); // 读取了数值 6 // 想要回到起始位置,再次进行读取时,reset 方法抛出异常 try { is.reset(); } catch (IOException e) { e.printStackTrace(); } System.out.println(is.read()); // 打印结果为:7。“回不去了” }}
运行结果(reset 方法抛出异常):
java.io.IOException: Resetting to invalid mark at java.io.BufferedInputStream.reset(BufferedInputStream.java:448) at com.willhonor.test.network.Temp.main(Temp.java:28)7 // 打印语句输出。“回不去了”
当然,上面的示例是我有意构造的,但是在实际使用中并不是不可能出现,示例中,在第一次读取之前先 mark 当前位置,并且 mark 长度 等于 内部缓存数组的长度(其实这个需求也很简单:在之后的任何时间点都可能需要回到起始位置再次进行读取,所以在读取之前先 mark 当前位置),随后连续读取一个字节,直到读取第 6 个字节后,想要再次回到起始位置,此时调用 reset 方法报告异常(注意,此时底层流中是有数据的,只是 BufferedInputStream 提供的缓存机制报告异常),BufferedInputStream 并没有着急扩大缓存区,而是将 mark 标记失效掉了,将 pos 和 count 重置为 0,导致丢弃当前所有已缓存数据(代码中给出的注释是“buffer got too big, so invalidate mark and drop buffer contents”)。
可以通过调整 mark 的参数,来避免上述的缓存数据丢失,而且直接导致 BufferedInputStream 扩大缓存区,示例代码如下(仅仅修改了 mark 函数的参数值):import java.io.BufferedInputStream;import java.io.ByteArrayInputStream;import java.io.IOException;public class Temp { public static void main(String[] args) throws IOException { // 生产字节,用于读取 int bytes_num = 10; byte[] bytes = new byte[bytes_num]; for(int i = 0;i < bytes_num;i++) { bytes[i] = (byte)(i + 1); } // 指定缓冲区大小为 5 int buf_size = 5; BufferedInputStream is = new BufferedInputStream(new ByteArrayInputStream(bytes), buf_size); // 在 pos 为 0 的位置 mark,且 marklimit = (1.5 * 缓冲区长度) is.mark(Math.round(buf_size * 1.5f)); // 连续读取 5 次 for(int i = 0;i < buf_size;i++) { is.read(); } // is.read(); // 回到起始位置再次读取 try { is.reset(); } catch (IOException e) { e.printStackTrace(); } System.out.println(is.read()); // 打印结果为:1。“回到过去,试着让故事继续...” }}
运行结果为:
1 // 打印语句输出。“回到过去,试着让故事继续...”
将 mark 的参数设置为 当前数组长度的 1.5 倍,也会导致随后缓存区数组长度扩大为原来的 1.5 倍(注意数组的最大扩大倍数为 2),且已标记的缓存数据并不会被丢失。当数组长度被扩大一次后,如果一直需要达到上述的 mark 效果,必须在执行读取操作之前再次调用 mark, 参数计算方式不变,但是其中数组的长度必须更新为当前扩大后的数组长度,可是 BufferedInputStream 并没有提供获取内部缓存区数组长度的方法,所以这一点必须自己来做,那就将数组的长度记录下来呗。
刚刚在看 Buffer 类,其中的思想与 BufferedInputStream 类一致!Buffer 类的 API 文档介绍如下:
转载地址:http://kjlsi.baihongyu.com/