coding……
但行好事 莫问前程

Java编程拾遗『字节流』

上篇文章中简单介绍了文件和Java IO的概念,我们了解到Java中文件是作为一种特殊的IO设备处理的,并且Java中处理IO是通过流来操作的,流又可以细分为字节流和字符流。本篇文章就重点介绍一下Java IO中的一个重要模块——字节流。字节流的继承体系如下图所示:

本文重点介绍如下几种字节流:

  • InputStream/OutputStream: 基类,抽象类。
  • ByteArrayInputStream/ByteArrayOutputStream: 输入源和输出目标是字节数组的流。
  • FileInputStream/FileOutputStream: 输入源和输出目标是文件的流。
  • FilterInputStream/FilterOutputStream,所有包装流的父类,一些“特殊”功能的流,比如DataInputStream/DataOutputStream、BufferedInputStream/BufferedOutputStream都继承了改类。
  • ObjectInputStream/ObjectOuputStream:输入源和输出目标是对象的流,用于实现Java序列化。

1. InputStream/OutputStream

InputStream和OutputStream是抽象类,是所有字节流的基类。

1.1 InputStream

S.N.
方法说明
1public abstract int read() throws IOException从字节输入流中读取下一个字节,返回值为读取字节的int值
2public int read(byte b[]) throws IOException从字节输入流中读取多个字节,放入字节数组b中,返回值为实际读入的字节个数
3public int read(byte b[], int off, int len) throws IOException从字节输入流中读取len个字节放入字节数组b index off开始的位置, 返回值为实际读入的字节个数
4public void close() throws IOException关闭字节输入流,释放资源
5public long skip(long n) throws IOException跳过字节输入流中n个字节,返回值为实际跳过的字节个数
6public int available() throws IOException返回下一次不需要阻塞就能读取到的字节个数,使用较少
7public synchronized void mark(int readlimit)标记能够从字节输入流中往后读取的字节个数readlimit
8public boolean markSupported()判断当前字节输入流是否支持mark/reset操作
9public synchronized void reset() throws IOException重新从标记位置读取字节输入流
  • read()

read()方法会从字节输入流中读取下一个字节,返回类型为int(一个字节8位,范围0~255,所以int可以表示),当读到流结尾的时候,返回值为-1。如果字节输入流中没有数据,read方法会阻塞直到数据到来、流关闭、或异常出现。异常出现时,read方法抛出异常,类型为IOException,这是一个受检异常,调用者必须进行处理。read是一个抽象方法,具体子类必须实现。

  • read(byte b[])

read(byte b[])方法将从字节输入流读入的字节放入字节数组b中,第一个字节存入b[0],第二个存入b[1],以此类推,一次最多读入的字节个数为数组b的长度,但实际读入的个数可能小于数组长度,返回值为实际读入的字节个数。如果刚开始读取时已到流结尾,则返回-1,否则,只要数组长度大于0,该方法都会尽力至少读取一个字节,如果流中一个字节都没有,它也会阻塞直到数据到来、流关闭、或异常出现。该方法不是抽象方法,InputStream有一个默认实现,通过调用读一个字节的read方法实现,但子类中一般会提供更为高效的实现。

  • read(byte b[], int off, int len)

read(byte b[], int off, int len)方法会将读入的第一个字节放入b[off],最多读取len个字节。read(byte b[])方法就是调用这个方法实现的,如下:

public int read(byte b[]) throws IOException {
    return read(b, 0, b.length);
}
  • close()

close()方法作用是字节输入流读取结束后,关闭流,释放相关资源。不管read方法是否抛出了异常,都应该调用close方法,所以close通常应该放在finally语句内。该方法不是抽象方法,InputStream中的实现是一个空函数,子类中会提供自己的实现。

  • skip()

skip()方法会跳过字节输入流中n个字节,因为输入流中剩余的字节个数可能不到n,所以返回值为实际略过的字节个数。InputStream的默认实现就是调用readd()方法,尽力读取n个字节,实现skip效果。子类往往会提供更为高效的实现,比如在FileInputStream中会调用本地方法。在处理数据时,对于不感兴趣的部分,skip往往比读取然后扔掉的效率要高。

  • available()

available()方法返回下一次不需要阻塞就能读取到的字节个数。InputStream的默认实现是返回0,子类会根据具体情况返回适当的值。在文件读写中,这个方法一般没什么用,但在从网络读取数据时,可以根据该方法的返回值在网络有足够数据时才读,以避免阻塞。

  • mark(int readlimit)

mark(int readlimit)方法用于标记当前字节输入流读取的位置,当字节输入流已经读取过该位置后,可以通过调用reset()方法,重新从标记位置读取。readLimit,表示在设置了标记后,能够继续往后读的最多字节数,如果超过了,标记会无效。因为之所以能够重读,是因为流能够将从标记位置开始的字节保存起来,而保存消耗的内存不能无限大,流只保证不会小于readLimit。在InputStream中该方法是个空方法,具体子类要根据自身情况覆盖该方法。

  • markSupported()

markSupported()方法用于获取当前字节输入流是否支持mark/reset操作,返回true表示当前字节输入流支持mark/reset操作。InpuStream类中的默认实现是不支持,子类根据自身情况覆盖该方法。

  • reset()

reset()方法用于重新从mark标记位置读取字节输入流。

1.2 OutputStream

S.N.方法说明
1public abstract void write(int b) throws IOException向字节输出流中写入一个字节
2public void write(byte b[]) throws IOException将字节数组b中所有的字节写入字节输出流
3public void write(byte b[], int off, int len) throws IOException将字节数组b中从index off开始,长度为len的字节写入字节输出流
4public void flush() throws IOException将缓冲而未实际写的数据进行实际写入
5public void close() throws IOException关闭字节输出流,释放资源
  • write(int b)

write(int b)方法向流中写入一个字节,参数类型虽然是int,但其实只会用到最低的8位(一个字节)。该方法在OutputStream中是个抽象方法,子类需要根据自身情况实现该方法。

  • write(byte b[], int off, int len)

write(byte b[], int off, int len)方法会将字节数组中index从off开始的len个字节写入到字节输出流中,第一个写入的字节是b[off],写入个数为len,最后一个是b[off+len-1]。OutputStream的默认实现是循环调用单字节的write方法,子类往往有更为高效的实现。

  • write(byte b[])

write(byte b[])方法会将字节数组中的全部字节写入到字节输出流中,在OutputStream中的实现是write(b, 0, b.length)。

  • flush()

flush()方法会将缓冲而未实际写的数据进行实际写入。

  • close()

close()方法会关闭字节输出流,释放系统资源。

2. ByteArrayInputStream/ByteArrayOutputStream

2.1 ByteArrayInputStream

ByteArrayInputStream的作用是将byte数组包装为一个输入流(
适配器模式 )。ByteArrayInputStream可以通过如下两种方式构建:

public ByteArrayInputStream(byte buf[]);
public ByteArrayInputStream(byte buf[], int offset, int length);

第一个构造函数是将字节数组buf中的全部字节包装到ByteArrayInputStream(效果是buf数组中的全部字节都体现在ByteArrayInputStream中)。第二个构造函数是将字节数组buf中index从offset开始长度为length的所有字节包装到ByteArrayInputStream(效果是buf数组中offset到offset + len -1的字节体现在ByteArrayInputStream中,read操作也只能读取到offset及之后的字节)。ByteArrayInputStream的所有数据都在内存,支持mark/reset重复读取。

2.2 ByteArrayOutputStream

ByteArrayOutputStream的作用是将内存中的数据输出到一个byte数组中,充当这个输出操作的“管道”。ByteArrayOutputStream可以通过如下两种方式构建:

public ByteArrayOutputStream();
public ByteArrayOutputStream(int size);

第二个构造函数中的size是用来存储内存中数据的字节数组的大小。第一个构造函数的实现是通过调用第二个构造函数实现的,size默认为32。在调用write方法向ByteArrayOutputStream流中写值的过程中,如果字节数组长度不够,会进行动态扩容,扩展后的容量是扩容前容量的两倍。

S.N.方法说明
1public synchronized byte[] toByteArray()将ByteArrayOutputStream流中的内容输出到字节数组
2public synchronized String toString()以系统默认编码,将ByteArrayOutputStream流中的内容(write操作写进来字节数组)输出为字符串
3public synchronized String toString(String charsetName)使用特定编码,将ByteArrayOutputStream流中的内容(write操作写进来字节数组)输出为字符串
4public synchronized void writeTo(OutputStream out) throws IOException将ByteArrayOutputStream中的数据写到另一个OutputStream中
5public synchronized int size()返回当前已经写入的字节个数
6public synchronized void reset()重置ByteArrayOutputStream中的内容,之前写入的内容都会无效

2.3 使用示例

@SneakyThrows
public static void main(String[] args) {
    byte[] bytes = Bytes.toArray(Lists.newArrayList(65, 66, 67, 68));

    try (InputStream inputStream = new ByteArrayInputStream(bytes);
         ByteArrayOutputStream output = new ByteArrayOutputStream()) {
        writeStream(inputStream, output);
    }

    try (InputStream inputStream = new ByteArrayInputStream(bytes, 1, 2);
         ByteArrayOutputStream output = new ByteArrayOutputStream()) {
        writeStream(inputStream, output);
    }
}

private static void writeStream(InputStream inputStream, ByteArrayOutputStream output) throws IOException {
    byte[] buf = new byte[16];
    int bytesRead;
    while ((bytesRead = inputStream.read(buf)) != -1) {
        output.write(buf, 0, bytesRead);
    }
    String data = output.toString("UTF-8");
    System.out.println(data);
}

输出结果:

ABCD
BC

关于上面这段代码,简单讲述一下:

  • Bytes.toArray()方法是google Guava工具包提供的方法,代码中的作用是将List<Integer>转化为一个byte数组,详见之前的一篇文章Guava元语工具
  • try () {}这种格式是Java8提供的语法糖try-with-resource,可以在try代码块执行结束,自动关闭打开的资源
  • 两个try代码块,分别测试了ByteArrayInputStream的两种构造函数,根据运行结果,可以发现是符合预期的,第一个ByteArrayInputStream对象通过整个byte数组构造,第二个ByteArrayInputStream对象通过byte数组index 1开始到index 2结束的字节构建
  • output.write操作将从ByteArrayInputStream中读取的字节数组写入到ByteArrayOutputStream对象output中
  • output.toString(“UTF-8”)使用UTF-8编码将ByteArrayOutputStream中的内容输出为字符串

3. FileInputStream/FileOutputStream

3.1 FileInputStream

赞(0) 打赏
Zhuoli's Blog » Java编程拾遗『字节流』
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

zhuoli's blog

联系我关于我

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏