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

FileInputStream是文件和内存之间交互的“水管”,是一种将文件读入到内存中的工具流。可以通过如下两种方式构造:

public FileInputStream(String name) throws FileNotFoundException
public FileInputStream(File file) throws FileNotFoundException

其中第一个构造函数的参数是文件路径,第二个构造函数的参数是文件对象。其中文件路径和文件必须是一个存在的文件,并且不能是目录,否则会抛FileNotFoundException。下面通过一个简单的例子看一下FileInputStream的用法:

@SneakyThrows
public static void main(String[] args) {
    try (InputStream input = new FileInputStream("d:/hello.txt")) {
        byte[] buf = new byte[1024];
        int b;
        int bytesRead = 0;
        while ((b = input.read()) != -1) {
            buf[bytesRead++] = (byte) b;
        }
        String data = new String(buf, 0, bytesRead, StandardCharsets.UTF_8);
        System.out.println(data);
    }

    try (InputStream input = new FileInputStream(new File("d:/hello.txt"))) {
        byte[] buf = new byte[1024];
        int bytesRead = input.read(buf);
        String data = new String(buf, 0, bytesRead, StandardCharsets.UTF_8);
        System.out.println(data);
    }

    try (InputStream input = new FileInputStream(new File("d:/hello.txt"));
         ByteArrayOutputStream output = new ByteArrayOutputStream()) {
        byte[] buf = new byte[1024];
        int bytesRead;
        while ((bytesRead = input.read(buf)) != -1) {
            output.write(buf, 0, bytesRead);
        }
        String data = output.toString("UTF-8");
        System.out.println(data);
    }
}

代码逻辑很简单,作用就是通过FileInputStream读取文件,然后将文件内容作为String打印输出。上述三个try代码块分别表示三种实现方式。

  • 第一种方式通过逐个字节读取的方式,将FileInputStream流中的内容读取到字节数组buf中,这种方式没有缓存,逐个字节读取,效率较差,且建立在文件内容小于1024个字节的前提下。最后通过String的构造函数,将buf数组转化为String。
  • 第二种方式通过FileInputStream的一次read操作,将FileInputStream流中的内容读取到字节数组buf中,这种方式较第一种方式效率上会好一些,但是依然建立在文件内容小于1024个字节的前提下。最后通过String的构造函数,将buf数组转化为String。
  • 第三种方式借助ByteArrayOutputStream,每次从FileInputSteam流中读取1024个字节到字节数组buf中,然后再将字节数组内容写入到ByteArrayOutputStream中,由于ByteArrayOutputStream支持动态扩容,不用像之前两种方式那样,依赖于文件内容的大小,所以这种方式是一种比较合理的方式。

3.2 FileOutputStream

FileOutputStream的作用是充当内存到文件之间的“管道”,可以方便地将内存中的数据写入到文件中,可以通过如下几种方式构造:

public FileOutputStream(File file) throws FileNotFoundException
public FileOutputStream(File file, boolean append) throws FileNotFoundException
public FileOutputStream(String name) throws FileNotFoundException
public FileOutputStream(String name, boolean append) throws FileNotFoundException

可以通过两种方式构造FileOutPutStream对象,一种是File对象(必须是文件,不是文件路径),另一种是文件路径(路径可以是绝对路径,也可以是相对路径)。如果文件已存在,参数append指定是追加还是覆盖,true表示追加,false表示覆盖(没有append参数地构造函数,append默认为false,表示覆盖文件)。下面通过一个简单地例子来看一下FileOutPutStream地用法:

@SneakyThrows
public static void main(String[] args) {
    try (OutputStream output = new FileOutputStream(new File("d:/hello1.txt"))) {
        String str = "卓立123";
        byte[] bytes = str.getBytes(Charset.forName("UTF-8"));
        output.write(bytes);
    }
}

首先通过getBytes方法将内存中的字符串转化为byte数组,然后调用FileOutPutStream对象的write方法,将byte数组的内容写入到文件中,比较简单,这里就不多讲了。

3. FilterInputStream/FilterOutputStream

上面讲的流,基本的流按字节读写,没有缓冲区,这不方便使用,Java解决这个问题的方法是使用装饰器设计模式。FilterInputStream/FilterOutputStream是所有装饰流的父类,通过名称也能大概猜出FilterInputStream/FilterOutputStream是干嘛的,就是过滤的意思,类似于自来水管道,流入的是水,流出的也是水,功能不变,或者只是增加功能,它有很多子类:

  • DataInputStream/DataOutputStream:按八种基本类型和字符串对流进行读写
  • BufferedInputStream/BufferedOutputStream:对基础流进行缓冲读写

3.1 DataInputStream/DataOutputStream

3.1.1 DataInputStream

DataInputStream是装饰类FilterInputStream的子类,可以方便地对被包装流中的数据通过基础数据类型读取。由于DataInputStream的作用是对其它字节输入流做包装,所以构造函数中肯定事要传入被包装流的对象的,JDK实现也是这样,如下:

public DataInputStream(InputStream in)

除了基础的read方法外,DataInputStream中也提供了针对基本数据类型的读取方法,如下:

S.N.方法说明
1public final int read(byte b[]) throws IOException从被包装字节输入流中读取多个字节,放入字节数组b中,返回值为实际读入的字节个数
2public final int read(byte b[], int off, int len) throws IOException 从被包裝字节输入流中读取len个字节放入字节数组b index off开始的位置, 返回值为实际读入的字节个数
3public final boolean readBoolean() throws IOException从被包裝字节输入流中读取一个字节,并转化为boolean输出
4public final byte readByte() throws IOException从被包裝字节输入流中读取一个字节输出
5public final char readChar() throws IOException从被包裝字节输入流中读取两个字节,并转化为char输出
6public final double readDouble() throws IOException从被包裝字节输入流中读取八个字节,并转化为double输出
7public final float readFloat() throws IOException 从被包裝字节输入流中读取四个字节,并转化为float输出
8public final int readInt() throws IOException 从被包裝字节输入流中读取四个字节,并转化为int输出
9public final long readLong() throws IOException从被包裝字节输入流中读取八个字节,并转化为long输出
10public final short readShort() throws IOException从被包裝字节输入流中读取两个字节,并转化为short输出
11public final int readUnsignedByte() throws IOException从被包裝字节输入流中读取一个字节,以无符号byte输出
12public final int readUnsignedShort() throws IOException从被包裝字节输入流中读取两个字节,并转化为无符号short输出
13public final String readUTF() throws IOException 读取在已使用UTF-8修改版格式编码的字符串(就是使用DataOutputStream的writeUTF写入的)

3.1.2 DataOutputStream

DataOutputStream是装饰类FilterOutputStream的子类,可以方便地将内存中的数据通过基础数据类型写入到被包装流的输出目标中。比如被包装流是FileOutputStream对象,那么调用dataOutputSream的writeInt操作,就可以将一个int值写入到FileOutputStream对象的目标文件中。DataOutputStream可以通过如下方式构建:

public DataOutputStream(OutputStream out)

除了构造函数,DataOutputStream提供如下写操作:

S.N.方法说明
1public synchronized void write(int b) throws IOException写入一个字节
2public synchronized void write(byte b[], int off, int len)
3public final void writeBoolean(boolean v) throws IOException写入一个字节,如果值为true,则写入1,否则0
4public final void writeByte(int v) throws IOException
5public final void writeBytes(String s) throws IOException
6public final void writeChar(int v) throws IOException
7public final void writeChars(String s) throws IOException
8public final void writeDouble(double v) throws IOException
9public final void writeFloat(float v) throws IOException
10public final void writeInt(int v) throws IOException写入四个字节,最高位字节先写入,最低位最后写入
11public final void writeLong(long v) throws IOException
12public final void writeShort(int v) throws IOException
13public final void writeUTF(String str) throws IOException将字符串的UTF-8编码字节写入,这个编码格式与标准的UTF-8编码略有不同

3.1.3 示例

定义一个简单实体类Student:

@Getter
@Setter
@AllArgsConstructor
@ToString
public class Student {
    private String name;
    private int age;
    private float grade;
}

通过DataInputStream和DataOutputStream进行读写如下:

@SneakyThrows
public static void main(String[] args) {

    try (OutputStream fileOutputStream = new FileOutputStream("d:/hello.txt");
         DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream)
    ) {
        List<Student> listStudent = new ArrayList<>();

        listStudent.add(new Student("Alice", 23, 80.5f));
        listStudent.add(new Student("Brian", 22, 95.0f));
        listStudent.add(new Student("Carol", 21, 79.8f));

        for (Student student : listStudent) {
            dataOutputStream.writeUTF(student.getName());
            dataOutputStream.writeInt(student.getAge());
            dataOutputStream.writeFloat(student.getGrade());
        }
    }


    try (InputStream fileInputStream = new FileInputStream("d:/hello.txt");
         DataInputStream dataInputStream = new DataInputStream(fileInputStream)
    ) {
        List<Student> listStudent = new ArrayList<>();
        try {
            while (true) {
                String name = dataInputStream.readUTF();
                int age = dataInputStream.readInt();
                float grade = dataInputStream.readFloat();

                Student student = new Student(name, age, grade);
                listStudent.add(student);
            }
        } catch (EOFException ex) {
            //流读取完毕
        }

        System.out.println(listStudent);
    }
}

运行结果:

[Student(name=Alice, age=23, grade=80.5), Student(name=Brian, age=22, grade=95.0), Student(name=Carol, age=21, grade=79.8)]

使用DataInputStream/DataOutputStream读写对象,非常灵活,但比较麻烦,所以Java提供了序列化机制ObjectInputSream/ObjectOutputStream。

3.2 BuffedInputStream/BuffedOutputStream

3.2.1 BuffedInputStream

BuffedInputStream是装饰类FilterInputStream的子类,提供普通流读取的缓冲功能,举个例子,如果普通的流是个水管,那么BuffedInputStream就是一个蓄水池。BufferedInputStream内部有个字节数组作为缓冲区,读取时,先从这个缓冲区读,缓冲区读完了再调用包装的流读,可以通过如下两种方式构造:

public BufferedInputStream(InputStream in)
public BufferedInputStream(InputStream in, int size)

InputStream表示被包装流,第二个构造函数中的size表示用来缓冲的数组的大小。如果没指定size,那么缓冲数组长度默认为8192。

除了构造方法之外,BuffedInputStream重写了InputStream的read方法,没有提供其他自定义方法。

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

3.2.1 BuffedOutputStream

BufferedOutputStream是装饰类FilterOutputStream的子类,提供普通流读取的缓冲功能,同BufferedInputStream类似,BufferedOutputStream也可以通过如下两种构建:

public BufferedOutputStream(OutputStream out)
public BufferedOutputStream(OutputStream out, int size)
S.N.方法说明
1public synchronized void write(int b) throws IOException向BufferedOutputStream中写入一个字节
3public synchronized void write(byte b[], int off, int len) throws IOException将字节数组b中从index off开始,长度为len的字节写入BufferedOutputStream
4public void flush() throws IOException将缓冲而未实际写的数据进行实际写入

3.2.3 示例

@SneakyThrows
public static void main(String[] args) {
    try (OutputStream fileOutputStream = new FileOutputStream("d:/hello.txt");
         BufferedOutputStream bufferedInputStream = new BufferedOutputStream(fileOutputStream);
         DataOutputStream dataOutputStream = new DataOutputStream(bufferedInputStream)
    ) {
        List<Student> listStudent = new ArrayList<>();

        listStudent.add(new Student("Alice", 23, 80.5f));
        listStudent.add(new Student("Brian", 22, 95.0f));
        listStudent.add(new Student("Carol", 21, 79.8f));

        for (Student student : listStudent) {
            dataOutputStream.writeUTF(student.getName());
            dataOutputStream.writeInt(student.getAge());
            dataOutputStream.writeFloat(student.getGrade());
        }
    }


    try (InputStream fileInputStream = new FileInputStream("d:/hello.txt");
         BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
         DataInputStream dataInputStream = new DataInputStream(bufferedInputStream)
    ) {
        List<Student> listStudent = new ArrayList<>();
        try {
            while (true) {
                String name = dataInputStream.readUTF();
                int age = dataInputStream.readInt();
                float grade = dataInputStream.readFloat();

                Student student = new Student(name, age, grade);
                listStudent.add(student);
            }
        } catch (EOFException ex) {
            //流读取完毕
        }

        System.out.println(listStudent);
    }
}

跟上节示例目标一致,通过DataOutputStream向文件中写入对象,然后通过DataInputStream从文件中读取对象。但是上节的示例中,读写文件都是直接读写的,比如readInt操作,就要一个字节一个字节的从FileInputStream中读取内容,效率时不高的。如果像本节示例中,通过BufferedInputStream将FileInputStream对象包装一下,readInt操作就可以从缓冲中读取内容了,BufferedOutputStream操作同样的道理。

4. ObjectInputStream/ObjectOuputStream

ObjectInputStream/ObjectOuputStream是java中用来实现序列化和反序列化默认机制的,从IO流的角度来讲,ObjectInputStream/ObjectOuputStream其实就是源和目标是对象的IO输入输出流。

4.1 ObjectInputStream

ObjectInputStream可以用来实现反序列化,可以通过如下方式构建:

public ObjectInputStream(InputStream in) throws IOException

之前讲过DataInputStream可以按照基本数据类型读取流中的内容,比较灵活,但是比较麻烦。相比于DataInputStream,ObjectInputSream也提供了基本数据类型的读取方法,除此之外,ObjectInputSream还提供了readObject方法,ObjectOutputStream通过writeObject方法写入流中的对象。

S.N.方法说明
1public int read() throws IOException从ObjectInputSream中读取一个字节
2public int read(byte b[], int off, int len) throws IOException 从ObjectInputSream中读取len个字节放入字节数组b index off开始的位置, 返回值为实际读入的字节个数
3public boolean readBoolean() throws IOException从ObjectInputSream中读取一个字节,并转化为boolean输出
4public byte readByte() throws IOException从ObjectInputSream中读取一个字节输出
5public char readChar() throws IOException从ObjectInputSream中读取两个字节,并转化为char输出
6public double readDouble() throws IOException从ObjectInputSream中读取八个字节,并转化为double输出
7public float readFloat() throws IOException 从ObjectInputSream中读取四个字节,并转化为float输出
8public int readInt() throws IOException 从ObjectInputSream中读取四个字节,并转化为int输出
9public long readLong() throws IOException从ObjectInputSream中读取八个字节,并转化为long输出
10public final Object readObject() throws IOException, ClassNotFoundException反序列化,从ObjectInputSream读取序列化的对象
11public short readShort() throws IOException从ObjectInputSream中读取两个字节,并转化为short输出
12public int readUnsignedByte() throws IOException从ObjectInputSream中读取一个字节,以无符号byte输出
13public int readUnsignedShort() throws IOException从ObjectInputSream中读取两个字节,并转化为无符号short输出
14public String readUTF() throws IOException 从ObjectInputSream中读取在已使用UTF-8修改版格式编码的字符串(就是使用ObjectOutputSream的writeUTF写入的)

4.2 ObjectOutputStream

ObjectOutputStream是源为对象的字节输出流,主要用来将对象序列化,可以通过如下方式构建:

public ObjectOutputStream(OutputStream out) throws IOException

与DataOutputStream类似,ObjectOutputStream也提供了基础类型数据写入字节流的方法,除此之外主要提供了用来实现对象序列化的writeObject方法

S.N.方法说明
1public void write(int val) throws IOException写入一个字节
2public void write(byte[] buf) throws IOException
2public void write(byte[] buf, int off, int len) throws IOException
private void writeArray(Object array, ObjectStreamClass desc, boolean unshared)
3public void writeBoolean(boolean val) throws IOException写入一个字节,如果值为true,则写入1,否则0
4public void writeByte(int val) throws IOException
5public void writeBytes(String str) throws IOException
6public void writeChar(int val) throws IOException
7public void writeChars(String str) throws IOException
8public void writeDouble(double val) throws IOException
9public void writeFloat(float val) throws IOException
10public void writeInt(int val) throws IOException写入四个字节,最高位字节先写入,最低位最后写入
11public void writeLong(long val) throws IOException
12public final void writeObject(Object obj) throws IOException
13public void writeShort(int val) throws IOException
14public void writeUTF(String str) throws IOException将字符串的UTF-8编码字节写入,这个编码格式与标准的UTF-8编码略有不同

4.3 示例

@SneakyThrows
public static void main(String[] args) {
    try (OutputStream fileOutputStream = new FileOutputStream("d:/student.dat");
         ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) {

        Student student = new Student("Michael", 23, 1);
        objectOutputStream.writeObject(student);
    }

    try (InputStream fileInputStream = new FileInputStream("d:/student.dat");
         ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) {
        Student student = (Student) objectInputStream.readObject();
        System.out.println(student);
    }
}

通过ObjectOutputStream像对象的序列化结果写入到文件中,然后通过ObjectInputStream反序列化,运行结果:

Student(name=Michael, age=23, grade=1.0)

按照之前讲的字符文件和二进制文件的定义,上述序列化得到的文件就是二进制文件了(可以记事本打开看一下,会乱码,因为除了对象内容外,还有很多控制信息)。对象序列化就是将对象按照某种规则转为另一种形式(比如字节流,字符串),以方便存储,传播。但序列化则是序列化的逆过程,按照约定的规则解析序列化结果,得到序列化前的对象。除了Java默认提供的ObjectInputStream/ObjectOuputStream可以实现序列化/反序列化之外,还有很多工具可以同样可以实现,并且使用起来时间效率和空间效率要比ObjectInputStream/ObjectOuputStream要好,比如Hession、FastJson、Gson、Protobuf等,特别是谷歌提供的Protobuf,在近年来应用很广泛,这里不继续介绍了,之后打算专门写一篇关于Protobuf的文章介绍一下,有兴趣的同学也可以去了解一下:Google Protocol Buffers

参考链接:

1. JDK源码

2. 《Java编程的逻辑 》

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

评论 抢沙发

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