字节流

字节流可以处理任何类型的数据,如图片,视频等,字节流以字节(8bit)为单位

InputStream

在Java8 中,InputStream 被定义为一个抽象类,相应的,该类的read()方法也是一个抽象方法,这就意味着必须要右一个类继承InputStream并且去实现这个read方法

InputStream的read()虽然也返回int,但由于此类是面向字节流的,一个字节占8个位,所以返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。因此对于不能用0-255来表示的值就得用字符流来读取!比如说汉字.
图片.png

使用实例

  1. InputStream inputstream = new FileInputStream("c:\\data\\input-text.txt");
  2. int data = inputstream.read();
  3. while(data != -1) {
  4. //do something with data...
  5. doSomethingWithData(data);
  6. data = inputstream.read();
  7. }
  8. inputstream.close();

Java7开始,你可以用try-with-resources去确保InputStream在使用后可以关闭。具体的用法可以参考上面Java异常处理的文章,但是这里有一个例子:

  1. try( InputStream inputstream = new FileInputStream("file.txt") ) {
  2. //使用了try-resource 之后就不需要手动close这个流
  3. int data = inputstream.read();
  4. while(data != -1){
  5. System.out.print((char) data);
  6. data = inputstream.read();
  7. }
  8. }

只要线程已经执行出try代码块,inputstream就会被关闭。

read() 方法

InputStream的read()方法返回一个int类型的值,这个值是每次读取的字节的值。下面是一个相关的例子

  1. int data = inputstream.read();

你也可以把int值强转成为一个char类型的:

  1. char aChar = (char) data;

InputStream的子类有可能扩展了read()方法。例如,DataInputStream允许读取java基本类型,像int、long、float、double、boolean等等。当然这些有相应的方法,readBoolean(),readDouble()等等。

read(byte[])

还有其他两种read重载方法,int read(byte[]) 和 int read(byte[],int offset,int length)
每次读取一个字节数组显然要比每次读取一个字节要快的多,所以只要有可能,你可以使用这类的方法来替代read()方法。
read(byte[])方法会读取所有数据到数组中。它会返回一个int值,来告诉实际读取的字节数。万一实际读取的字节数要比提供的字节少,那么字节数组的其余部分将包含与读取开始之前所做的相同的数据。要记住去检查返回的int值,看实际取得的字节数有多少。
read(byte[], int offset, int length)方法也是读数组到数组中,但是可以根据参数来规定开始的偏移量和一共读取多少长度。返回值和read(byte[])方法的含义相同。
两个方法相同的地方都是如果返回值是 -1 ,代表读取结束。
示例

  1. InputStream inputstream = new FileInputStream("c:\\data\\input-text.txt");
  2. byte[] data = new byte[1024];
  3. int bytesRead = inputstream.read(data);
  4. while(bytesRead != -1) {
  5. doSomethingWithData(data, bytesRead);
  6. bytesRead = inputstream.read(data);
  7. }
  8. inputstream.close();

mark()和reset()

InputStream类中有两个方法mark()和reset(),但是它的子类不一定有支持(或者说子类有没有重写此方法)。
如果一个InputStream的子类支持这两个方法,子类会重写markSupported()方法并返回 true。相反的,如果返回false,则子类不支持这两个方法。
mark()方法在InputStream设置一个内部的标记,标记在流中哪个数据到目前为止已经被读取。使用InputStream的代码,可以根据这个标记位来继续读取数据。
在读取流的过程汇总,如果想退回到标记的那点上,可以调用reset()方法。然后InputStream退回到标记位上,开始从这个位置开始返回数据。这当然会导致多次返回一些数据
当实现一个解析器时,会经常用到这两个方法。解析器读取一个InputStream时会提前读,如果解析器没有找到它想要的,他可能需要回退并且将已读数据和其他的数据进行匹配。

OutputStream

OutputStream 许多特性与InputStream 相同,这里我们以FileInputStream 为例。

FileOutputStream

将若干字节写入文件流

  1. public void writeFile() throws IOException {
  2. OutputStream output = new FileOutputStream("out/readme.txt");
  3. output.write(72); // H
  4. output.write(101); // e
  5. output.write(108); // l
  6. output.write(108); // l
  7. output.write(111); // o
  8. output.close();
  9. }

每次写入一个字节非常麻烦,更常见的方法是一次写入若干字节。可以使用void write(byte[])来实现:

  1. public void writeFile() throws IOException {
  2. OutputStream output = new FileOutputStream("out/readme.txt");
  3. output.write("Hello".getBytes("UTF-8")); // Hello
  4. output.close();
  5. }

上述代码没有考虑发生异常情况下如何正确地关闭资源。所以我们可以通过try(resource)来保证OutputStream在无论是否发生IO错误的时候都能够正确地关闭

  1. public void writeFile() throws IOException {
  2. try (OutputStream output = new FileOutputStream("out/readme.txt")) {
  3. output.write("Hello".getBytes("UTF-8")); // Hello
  4. } // 编译器在此自动为我们写入finally并调用close()
  5. }

阻塞

和InputStream一样,OutputStream的write()方法也是阻塞的。

装饰者模式

Java I/O 使用了装饰者模式来实现。以InputStream为例,

  • InputStream是抽象组件;
  • FileInputStream 是InputStream的子类,属于具体组件,提供了字节流的输入操作;
  • FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能。例如,BufferedInputStream为FIleInputStream提供缓存的功能。

实例化一个具有缓存功能的字节流对象时,只需要在FileInputStream 对象上再套一层BufferedInputStream 对象即可

  1. FileInputStream fileInputStream = new FileInputStream(filePath);
  2. BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);