五、03【Java IO模型】之字节流

InputStream 介绍

public abstract class InputStream implements Closeable {}

这个抽象类是表示字节输入流所有类的超类。

定义了 InputStream 子类的应用程序必须始终提供返回输入下一个字节的方法。

从上面的体系结构也就只管的知道它的子类了。

说白了 inputStream 就是一个字节输入流,以字节的形式读取数据。

InputStream 是一个抽象类,只提供方法声明,具体的方法实现由子类来完成。

属性&方法

// MAX_SKIP_BUFFER_SIZE 用于确定跳过时使用的最大缓冲区大小
private static final int MAX_SKIP_BUFFER_SIZE = 2048;

// 从输入流中读取下一个字节的数据。值字节以整数形式返回,范围为0到255。
// 如果已到达流的结尾或没有字节可用,则返回值-1。
// 此方法将会阻塞,直到输入数据可用、检测到流的结尾或引发异常。
public abstract int read() throws IOException;

// 关闭此输入流并释放任何相关的系统资源与流
public void close() throws IOException {}

其主要的方法还是 read() 和 close(),读资源和关闭释放输入流。

InputStream 的子类实现体系结构图

 OutputStream 介绍

public abstract class OutputStream implements Closeable, Flushable {}

这个抽象类是表示字节输出流所有类的超类。输出流接受输出字节并将它们发送到某个接收器。

需要定义 OutputStream 子类的应用程序必须始终至少提供一个写入一个字节输出的方法。

属性&方法

// 将指定的字节写入此输出流。写入的一般约定是将一个字节写入输出流。
// 要写入的字节是参数 b 的8个低位。 b的24个高位被忽略。
// 子类必须实现次方法
public abstract void write(int b) throws IOException;

// 刷新此输出流并强制写出任何缓冲的输出字节。
// flush的一般约定是,调用它表示,如果先前写入的任何字节已被输出流的实现缓冲,
// 则应立即将这些字节写入其预期目的地。
// 如果该流的预期目的地是底层操作系统提供的抽象,例如文件,
// 则刷新该流仅保证先前写入该流的字节被传递到操作系统以进行写入;
// 它不能保证它们确实被写入到物理设备(如磁盘驱动器)中。
// OutputStream的 flush() 方法不起任何作用。
public void flush() throws IOException {}

// 关闭此输出流并释放与此流关联的任何系统资源。
// 关闭的一般约定是关闭输出流。关闭的流无法执行输出操作,也无法重新打开。
public void close() throws IOException {}

主要的方法 write() 写出数据, flush() 强制刷新缓存里的数据, close() 关闭并释放输出流

InputStream 的子类实现体系结构图

 InputStream&OutputStream 子类

ByteArrayInputStream & ByteArrayOutputStream

ByteArrayInputStream 字节数组输入流。是 InputStream 的子类,就是把字节转换成流来操作。

内部有一个缓冲区,可以从流中读取的字节。内部计数器跟踪 read() 方法,提供的下一个字节。

属性&构造函数:

// 由流的创建者提供的字节数组。
// 元素buf[0]到buf[count-1]是唯一可以从流中读取的字节;
// 元素buf[pos]是下一个要读的字节。
protected byte buf[];

// 下一个要从输入流缓冲区中读取的字符的索引。
// 此值应始终为且不大于count的值。
// 下一个从输入流缓冲区读取的字节将是buf[pos]。
protected int pos;

// 输入流缓冲区中比最后一个有效字符大1的索引。
// 这个值应该总是非负的并且不大于buf的长度。
// 它比buf中可以从输入流缓冲区读取的最后一个字节的位置大1。
protected int count;

// 流中当前标记的位置。
// 对象在构造时默认标记在位置0。它们可以被mark()方法标记在缓冲区的另一个位置。
// 当前缓冲区位置由reset()方法设置为该点。
// 如果没有设置标记,那么mark的值就是传递给构造函数的偏移量(如果没有提供偏移量,则为0)。
protected int mark = 0;

// 创建一个ByteArrayInputStream,使用buf作为其缓冲数组。
// 缓冲区数组不会被复制
// pos的初始值为0,count的初始值为buf的长度。
public ByteArrayInputStream(byte buf[]) {
    this.buf = buf;
    this.pos = 0;
    this.count = buf.length;
}

// 创建使用buf作为缓冲数组的ByteArrayInputStream。
// pos的初始值是offset, count的初始值是offset+length和buf.length的最小值。
// 缓冲区数组不会被复制。
// 缓冲区的 mark 被设置为指定的偏移量。
public ByteArrayInputStream(byte buf[], int offset, int length) {
    this.buf = buf;
    this.pos = offset;
    this.count = Math.min(offset + length, buf.length);
    this.mark = offset;
}

ByteArrayOutputStream 字节数组输出流。是 OutputStream 的子类,数据被写入字节数组。当数据写入缓冲区时,缓冲区会自动增长。

可以使用toByteArray()和toString()检索数据。

属性&构造函数:

// 存储数据的缓冲区。
protected byte buf[];

// 缓冲区中有效的字节数
protected int count;

// 创建一个新的字节数组输出流。
// 缓冲区容量最初是32字节,但如果需要,它的大小会增加。
public ByteArrayOutputStream() {
    this(32);
}

// 创建新的字节数组输出流,其缓冲区容量为指定大小(以字节为单位)。
public ByteArrayOutputStream(int size) {
    if (size < 0) {
        throw new IllegalArgumentException("Negative initial size: " + size);
    }
    buf = new byte[size];
}

// 必要时增加容量,以确保至少能容纳最小容量参数指定的元素数量。
private void ensureCapacity(int minCapacity) {
    // overflow-conscious code
    if (minCapacity - buf.length > 0)
        grow(minCapacity);
}

// 增加容量,以确保至少可以保存由最小容量参数指定的元素数量。
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = buf.length;
    int newCapacity = oldCapacity << 1; // 成倍扩容
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    buf = Arrays.copyOf(buf, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

代码示例:

private static void byteArrayIOStreamTest() throws IOException {
    ByteArrayInputStream bis = null;
    ByteArrayOutputStream bos = null;
    try {
        String ss = "Hello World";
        bis = new ByteArrayInputStream(ss.getBytes());
        bos = new ByteArrayOutputStream();

        int read = bis.read();
        while (read != -1) {
            char c = (char) read;
            System.out.print(" " + c);

            bos.write(read);
            read = bis.read();
        }

        System.out.println();

        System.out.println(bos.toString());
    } finally {
        if(null != bos){
            bos.close();
        }
        if(null != bis){
            bis.close();
        }
    }
}

FileInputStream & FileOutputStream

FIleInputStream 文件输入流。继承InputStream。从文件系统中的文件中获取输入字节。哪些文件可用取决于主机环境。用于读取原始字节流,比如图像数据。

属性&构造函数:

// 文件描述符-打开文件的句柄
private final FileDescriptor fd;

// 引用文件的路径(如果流是用文件描述符创建的,则为空)
private final String path;

// 通过打开与实际文件的连接来创建FileInputStream,
// 文件系统中以路径名命名的文件。
// 将创建一个新的FileDescriptor对象来表示此文件连接。
// 首先,如果有一个安全管理器,则调用它的checkRead方法,并使用name参数作为其参数。
// 如果指定的文件不存在,是一个目录而不是一个常规文件,
// 或者由于其他原因无法打开进行读取,则抛出FileNotFoundException。
// name 是依赖于系统的文件名。
public FileInputStream(String name) throws FileNotFoundException {
    this(name != null ? new File(name) : null);
}

// 通过打开与实际文件的连接来创建FileInputStream,
// 文件系统中由文件对象文件命名的文件。
// file 是要打开以供读取的文件
public FileInputStream(File file) throws FileNotFoundException {
    String name = (file != null ? file.getPath() : null);
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkRead(name);
    }
    if (name == null) {
        throw new NullPointerException();
    }
    if (file.isInvalid()) {
        throw new FileNotFoundException("Invalid file path");
    }
    fd = new FileDescriptor();
    fd.attach(this);
    path = name;
    open(name);
}

FileOutputStream 文件输出流。继承OutputStream。用于将数据写入文件的输出流。文件是否可用或是否可以创建取决于底层平台。特别是,一些平台允许一次只通过一个FileOutputStream(或其他文件写入对象)打开文件进行写入。在这种情况下,如果涉及的文件已经打开,则该类中的构造函数将失败。用于写入原始字节流,如图像数据。

属性&构造函数:

// 依赖于系统的文件描述符。
private final FileDescriptor fd;

// 如果文件被打开用于追加,则为True。
private final boolean append;

// 关联的通道,延迟初始化。
private FileChannel channel;

// 引用文件的路径(如果流是用文件描述符创建的,则为空)
private final String path;

// 创建一个文件输出流,以写入具有指定名称的文件。
// 将创建一个新的FileDescriptor对象来表示此文件连接。
// 首先,如果有一个安全管理器,它的checkWrite方法将以name作为参数来调用。
// 如果文件存在但不是常规文件,而是目录,不存在但无法创建,
// 或者由于任何其他原因无法打开,则抛出FileNotFoundException。
// name 是依赖于系统的文件名。
public FileOutputStream(String name) throws FileNotFoundException {
    this(name != null ? new File(name) : null, false);
}

// 创建要写入具有指定名称的文件的文件输出流。
// 如果第二个参数为true,则字节数将被写入文件的末尾,而不是开头。
// name 是依赖于系统的文件名。append 如果true,则将写入字节
public FileOutputStream(String name, boolean append)
    throws FileNotFoundException{
    this(name != null ? new File(name) : null, append);
}

// 创建要写入具有指定名称的文件的文件输出流。
// file 是要打开以供读取的文件
public FileOutputStream(File file) throws FileNotFoundException {
    this(file, false);
}
// 创建要写入具有指定名称的文件的文件输出流。
// 如果第二个参数为true,则字节数将被写入文件的末尾,而不是开头。
// file 是要打开以供读取的文件。append 如果true,则将写入字节
public FileOutputStream(File file, boolean append)
    throws FileNotFoundException {
    String name = (file != null ? file.getPath() : null);
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkWrite(name);
    }
    if (name == null) {
        throw new NullPointerException();
    }
    if (file.isInvalid()) {
        throw new FileNotFoundException("Invalid file path");
    }
    this.fd = new FileDescriptor();
    fd.attach(this);
    this.append = append;
    this.path = name;
    open(name, append);
}

代码示例:

private static void fileIOStreamTest() throws IOException {
    FileInputStream fis = null;
    FileOutputStream fos = null;
    try {
        // 读取a.txt文件内容
        fis = new FileInputStream("/a.txt");
        // 写到b.txt文件
        fos = new FileOutputStream("/b.txt");

        // 创建一个缓冲字节数组,可减少IO操作,提高读写效率,一般用于大文件操作
        byte[] byt = new byte[1024];

        // 读取缓冲字节数组长度的字节
//            int read = fis.read(byt);
        int read = fis.read();
        while(read != -1){
//                fos.write(byt, 0, read);
            fos.write(read);
            read = fis.read();
        }
        fos.flush();
    } catch (FileNotFoundException e) {
        System.out.println("文件不存在");
    } catch (IOException e) {
        System.out.println("系统异常");
    } finally {
        if(null != fos){
            fos.close();
        }
        if(null != fis){
            fis.close();
        }
    }
}

应用场景:

1)文件读取

2)文件上传下载

在 FileInputStream 下还有 BufferedInputStream、DataInputStream、LineNumberInputStream 和 PushBackInputStream 几个子类实现;

在 FilterOutputStream 下还有 BufferedOutputStream、DataOutputStream、PrintStream 几个子类的实现;

BufferedInputStream & BufferedOutputStream

BufferedInputStream继承于FilterInputStream,提供缓冲输入流功能。

缓冲输入流相对于普通输入流的优势是,它提供了一个缓冲数组,每次调用read方法的时候,它首先尝试从缓冲区里读取数据,若读取失败(缓冲区无可读数据),则选择从物理数据源(例如文件)读取新数据(这里会尝试尽可能读取多的字节)放入到缓冲区中,最后再将缓冲区中的内容部分或全部返回。从缓冲区里读取数据远比直接从物理数据源(例如文件)读取速度快。

BufferedInputStream为另一个输入流添加了功能,即缓冲输入以及支持标记和重置方法的能力。创建BufferedInputStream时,将创建一个内部缓冲区数组。当读取或跳过流中的字节时,根据需要从包含的输入流中重新填充内部缓冲区,每次填充许多字节。mark操作记住输入流中的一个点,reset操作导致在从包含的输入流中获取新字节之前,重新读取自最近的mark操作以来读取的所有字节。

属性&构造函数:

// 默认缓冲区大小 8M
private static int DEFAULT_BUFFER_SIZE = 8192;
// 最大缓冲区大小
private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
// 缓冲区字节数组
protected volatile byte buf[];
// ...

// 创建一个 BufferedInputStream 并保存其参数,即输入流,以备以后使用。 
// 一个内部 buffer array 被创建并存储在buf
public BufferedInputStream(InputStream in) {
    this(in, DEFAULT_BUFFER_SIZE);
}
// 创建一个 BufferedInputStream 并保存其参数,即输入流,以备以后使用。 
// 可以指定缓存区大小,小于等于0则抛出异常
public BufferedInputStream(InputStream in, int size) {
    super(in);
    if (size <= 0) {
        throw new IllegalArgumentException("Buffer size <= 0");
    }
    buf = new byte[size];
}

// 除了上面列出来的,还有很多属性和方法来提供功能,是比较核心的

BufferedOutputStream 实现了一个缓冲输出流。通过设置这样的输出流,应用程序可以将字节写入底层输出流,而不必为写入的每个字节调用底层系统。

属性&构造函数:

// 存储数据的内部缓冲区。
protected byte buf[];
// 缓冲区中的有效字节数。
protected int count;
// ...

// 创建一个新的缓冲输出流以将数据写入指定底层输出流。
public BufferedOutputStream(OutputStream out) {
    this(out, 8192);
}
// 使用指定的缓冲区底层输出流大小创建一个新的缓冲输出流以将数据写入指定底层输出流。
// 小于等于0 将抛出异常
public BufferedOutputStream(OutputStream out, int size) {
    super(out);
    if (size <= 0) {
        throw new IllegalArgumentException("Buffer size <= 0");
    }
    buf = new byte[size];
}

// 同样除了上面列出来的,还有一些属性和方法来提供功能,比较核心

代码示例:

private static void bufferedIOStreamTest() {
    try {
        // 声明一个缓冲数组
        byte[] buffer = new byte[1024];
        // 声明文件输入输出流
        FileInputStream fileInputStream = new FileInputStream("/aaa.txt");
        FileOutputStream fileOutputStream = new FileOutputStream("/bbb.txt");
        // 声明缓冲输入输出流
        BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
        // 从文件中按字节读取内容,到文件尾部时read方法将返回-1
        int bytesRead = 0;
        while ((bytesRead = bufferedInputStream.read(buffer)) != -1) {
            bufferedOutputStream.write(buffer, 0, bytesRead);
        }
        // 关闭流
        bufferedOutputStream.close();
        bufferedInputStream.close();
        fileOutputStream.close();
        fileInputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

DataInputStream&DataOutputStream

DataInputStream 允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。

DataInputStream 继承于 FileInputStream。专门用来读取 Java 中的一些基本数据类型(比如:int char double);

对于多线程访问,DataInputStream不一定是安全的。线程安全是可选的,由此类方法的用户负责。

DataOutputStream 允许应用程序以可移植的方式将原始Java数据类型写入输出流。然后,应用程序可以使用DataInputStream将数据读回。

代码示例:

private static void dataIOStream(){
    try {
        // 文件输出流
        FileOutputStream fos = new FileOutputStream("/aaa.txt");
        // 数据输出流
        DataOutputStream dos = new DataOutputStream(fos);
        dos.writeUTF("&");          // 写出char类型数据
        dos.writeInt(1234567);       // 写出int类型数据
        dos.writeBoolean(true);      // 写出boolean类型数据
        dos.writeShort((short)123);     // 写出short类型数据
        dos.writeLong((long)456);       // 写出long类型数据
        dos.writeDouble(99.98);      // 写出double类型数据
        dos.writeFloat((float)110.11);  // 写出float类型数据
        // 把刚才写出到那个文件的数据在读出来
        // 文件输出流
        FileInputStream fis = new FileInputStream("/aaa.txt");
        // 数据输入流
        DataInputStream dis = new DataInputStream(fis);
        System.out.println(dis.readUTF());      // 读取char类型数据
        System.out.println(dis.readInt());      // 读取int类型数据
        System.out.println(dis.readBoolean());  // 读取boolean类型数据
        System.out.println(dis.readShort());    // 读取short类型数据
        System.out.println(dis.readLong());     // 读取long类型数据
        System.out.println(dis.readDouble());   // 读取double类型数据
        System.out.println(dis.readFloat());    // 读取float类型数据
        // 关闭流
        dis.close();
        fis.close();
        dos.close();
        fos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

LineNumberInputStream

这个类是一个输入流过滤器,它提供了跟踪当前行号的附加功能。行是以回车符('\r')、换行符('\n')或紧跟换行符的回车符结尾的字节序列。

在这三种情况下,行尾字符都作为一个换行符返回。行号从0开始,当读取返回换行符时,行号将递增1。

此类错误地假设字节充分代表字符。从JDK 1.1开始,对字符流进行操作的首选方式是通过新的字符流类,其中包括一个用于计算行号的类。

已经被弃用了。

PushbackInputStream

PushbackInputStream为另一个输入流添加了功能,即“向后推”或“未读”一个字节的能力。这在代码片段方便读取由特定字节值分隔的不确定数量的数据字节的情况下非常有用;读取终止字节后,代码片段可以“未读取”它,这样输入流上的下一个读取操作将重新读取被推回的字节。例如,表示构成标识符的字符的字节可能会被表示运算符字符的字节终止;一种只读取标识符的方法可以一直读取,直到看到运算符,然后将运算符推回重新读取。

也可以叫它回退流。流本身不支持回退功能,如果想要能够pushBack,必须能够缓存数据。

PushBackInputStream内部维护了一个字节数组,是为了保存pushBack的字节。

我们可以这么理解 PushbackInputStream 在从 InputStream 解析数据时使用,需要先读取几个字节查看将要发生的事情,然后才能确定如何解释当前字节,它是将读取的字节让程序看完之后在推回到流中,这样就像流没有被动过,下次调用 read() 时,将再次重新读取流的内容。

代码示例:

PrintStream

PrintStream为另一个输出流(fileOutputStream)添加了功能,即能够方便地打印各种数据值的表示。还提供了另外两个功能。与其他输出流不同,PrintStream从不抛出IOException;相反,异常情况只是设置了一个内部标志,可以通过checkError方法进行测试。可选地,可以创建打印流以便自动刷新;这意味着在写入字节数组、调用某个println方法或写入换行符或字节('\n')后,会自动调用flush方法。

打印流打印的所有字符都使用平台的默认字符编码转换为字节。PrintWriter类应用于需要写入字符而不是字节的情况。

代码示例:

ObjectInputStream&ObjectOutputStream

ObjectInputStream反序列化以前使用ObjectOutputStream编写的基本数据和对象。

ObjectOutputStream和ObjectInputStream分别与FileOutputStream和FileInputStream一起使用时,可以为应用程序提供对象图形的持久存储。ObjectInputStream用于恢复之前序列化的对象。其他用途包括使用套接字流在主机之间传递对象,或在远程通信系统中封送和解封送参数和参数。

ObjectInputStream确保从流创建的图形中的所有对象的类型与Java虚拟机中存在的类匹配。使用标准机制根据需要加载类。

仅支持java.io.Serializable 或 java.io.Externalizable 接口可以从流中读取。readObject方法用于从流中读取对象。应该使用Java的安全强制转换来获得所需的类型。在Java中,字符串和数组是对象,在序列化过程中被视为对象。读取时,需要将其转换为预期类型。可以使用DataInput上的适当方法从流中读取基本数据类型。

对象的默认反序列化机制将每个字段的内容恢复为写入时的值和类型。反序列化过程会忽略声明为瞬态或静态的字段。对其他对象的引用会根据需要从流中读取这些对象。使用引用共享机制正确还原对象的图形。反序列化时始终会分配新对象,从而防止覆盖现有对象。

读取对象类似于运行新对象的构造函数。内存分配给对象并初始化为零(NULL)。不为非序列化类调用参数构造函数,然后从最接近java的可序列化类开始的流中恢复可序列化类的字段。然后以对象最具体的类结束。

例如,从ObjectOutputStream中的示例编写的流中读取:

FileInputStream fis = new FileInputStream("t.tmp");
ObjectInputStream ois = new ObjectInputStream(fis);

int i = ois.readInt();
String today = (String) ois.readObject();
Date date = (Date) ois.readObject();

ois.close();

类通过实现两个接口之一来控制它们的序列化方式 java.io.Serializable or java.io.Externalizable

实现Serializable接口允许对象序列化来保存和恢复对象的整个状态,并允许类在流写入和读取之间演化。它自动遍历对象之间的引用,保存和恢复整个图形。

在序列化和反序列化过程中需要特殊处理的可序列化类应实现以下方法:

private void writeObject(java.io.ObjectOutputStream stream) throws IOException;
private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException;
private void readObjectNoData() throws ObjectStreamException;

readObject方法负责使用相应的writeObject方法写入流的数据,读取并恢复其特定类的对象状态。该方法不需要关心属于其超类或子类的状态。通过从ObjectInputStream中读取各个字段的数据,并对对象的相应字段进行赋值,可以恢复状态。DataInput支持读取基本数据类型。

任何读取对象数据的尝试,如果超出了由相应writeObject方法写入的自定义数据的边界,将导致抛出eof字段值为true的OptionalDataException。超过分配数据结尾的非对象读取将以与指示流结尾相同的方式反映数据结尾:字节读取将返回-1作为字节读取或读取的字节数,原语读取将抛出EOFEException。如果没有相应的writeObject方法,则默认序列化数据的结束标记分配数据的结束。

从readExternal方法中发出的基元和对象读取调用的行为方式相同——如果流已经位于相应writeExternal方法写入的数据的末尾,则对象读取将抛出OptionalDataExceptions,eof设置为true,字节读取将返回-1,基元读取将抛出EOFEExceptions。

如果序列化流没有将给定类列为反序列化对象的超类,ReadObjectNodeData方法负责初始化其特定类的对象状态。如果接收方使用的反序列化实例类的版本与发送方不同,并且接收方的版本扩展了发送方版本未扩展的类,则可能会发生这种情况。如果序列化流被篡改,也可能发生这种情况;因此,ReadObjectNodeData对于正确初始化反序列化对象非常有用,尽管源流“恶意”或不完整。

序列化不会读取未实现 java.io.Serializable 接口的任何对象的字段或为其赋值。不可序列化的对象的子类可以序列化。在这种情况下,不可序列化类必须有一个无参数构造函数,以允许初始化其字段。在这种情况下,子类负责保存和恢复不可序列化类的状态。通常情况下,该类的字段是可访问的(public、package或protected),或者可以使用get和set方法来恢复状态。

反序列化对象时发生的任何异常都将被ObjectInputStream捕获并中止读取过程。

实现Externalizable接口允许对象完全控制对象序列化表单的内容和格式。外部化接口writeExternal和readExternal的方法被调用以保存和恢复对象状态。当由类实现时,它们可以使用ObjectOutput和ObjectInput的所有方法编写和读取自己的状态。对象负责处理发生的任何版本控制。

枚举常量的反序列化方式不同于普通的可序列化或可外部化对象。枚举常量的序列化形式仅由其名称组成;常数的字段值不会被传输。要反序列化枚举常量,ObjectInputStream从流中读取常量名称;然后通过调用静态方法Enum获得反序列化常量 .valueOf(类,字符串),枚举常量的基类型和接收到的常量名称作为参数。与其他可序列化或可外部化的对象一样,枚举常量可以作为后续出现在序列化流中的反向引用的目标。无法自定义枚举常量反序列化的过程:反序列化期间,将忽略枚举类型定义的任何特定于类的readObject、ReadObjectNodeData和readResolve方法。类似地,也会忽略任何serialPersistentFields或serialVersionUID字段声明——所有枚举类型都有一个固定的serialVersionUID为0L。

ObjectOutputStream将Java对象的基本数据类型和图形写入OutputStream。可以使用ObjectInputStream读取(重构)对象。对象的持久存储可以通过使用流的文件来实现。如果流是网络套接字流,则可以在另一台主机上或在另一个进程中重建对象。

只支持实现 java.io.Serializable 接口的对象才能写入流。对每个可序列化对象的类进行编码,包括类的类名和签名、对象的字段和数组的值,以及从初始对象引用的任何其他对象的闭包。writeObject方法用于将对象写入流。任何对象,包括字符串和数组,都是用writeObject编写的。可以将多个对象或原语写入流。对象必须从相应的ObjectInputstream中以相同的类型和写入顺序读回。

原始数据类型也可以使用DataOutput中的适当方法写入流。也可以使用writeUTF方法写入字符串。

对象的默认序列化机制写入对象的类、类签名以及所有非瞬态和非静态字段的值。对其他对象(瞬态或静态字段中除外)的引用也会导致写入这些对象。使用引用共享机制对单个对象的多个引用进行编码,以便可以将对象的图形恢复为与原始对象写入时相同的形状。

例如,要在ObjectInputStream中编写示例可以读取的对象:

FileOutputStream fos = new FileOutputStream("t.tmp");
ObjectOutputStream oos = new ObjectOutputStream(fos);

oos.writeInt(12345);
oos.writeObject("Today");
oos.writeObject(new Date());

oos.close();

在序列化和反序列化过程中需要特殊处理的类必须实现具有以下确切签名的特殊方法:

private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException;
private void writeObject(java.io.ObjectOutputStream stream) throws IOException
private void readObjectNoData() throws ObjectStreamException;

writeObject方法负责为其特定类写入对象的状态,以便相应的readObject方法可以恢复它。该方法不需要关心属于对象的超类或子类的状态。通过使用writeObject方法或使用DataOutput支持的基本数据类型的方法将各个字段写入 ObjectOutputStream 可以保存状态。

序列化不会写出任何未实现 java.io.Serializable 接口的对象的字段。不可序列化的对象的子类可以序列化。在这种情况下,不可序列化类必须有一个无参数构造函数,以允许初始化其字段。在这种情况下,子类负责保存和恢复不可序列化类的状态。通常情况下,该类的字段是可访问的(public、package或protected),或者可以使用get和set方法来恢复状态。通过实现引发NotSerializableException的writeObject和readObject方法,可以防止对象序列化。ObjectOutputStream将捕获异常并中止序列化过程。

实现Externalizable接口允许对象完全控制对象序列化表单的内容和格式。外部化接口writeExternal和readExternal的方法被调用以保存和恢复对象状态。当由类实现时,它们可以使用ObjectOutput和ObjectInput的所有方法编写和读取自己的状态。对象负责处理发生的任何版本控制。

枚举常量的序列化方式不同于普通的可序列化或可外部化对象。枚举常量的序列化形式仅由其名称组成;常数的字段值不会被传输。要序列化枚举常量,ObjectOutputStream会写入常量的name方法返回的字符串。与其他可序列化或可外部化的对象一样,枚举常量可以作为后续出现在序列化流中的反向引用的目标。无法自定义枚举常量序列化的过程;在序列化过程中,将忽略枚举类型定义的任何特定于类的writeObject和writeReplace方法。类似地,也会忽略任何serialPersistentFields或serialVersionUID字段声明——所有枚举类型都有一个固定的serialVersionUID为0L。

原始数据(不包括可序列化字段和可外部化数据)以块数据记录的形式写入ObjectOutputStream。块数据记录由头和数据组成。块数据头由一个标记和头后面的字节数组成。连续的原语数据写入合并到一个块数据记录中。块数据记录使用的阻塞因子为1024字节。每个块数据记录将被填充至1024字节,或在块数据模式终止时写入。对ObjectOutputStream方法writeObject、defaultWriteObject和writeFields的调用最初会终止任何现有的块数据记录。

ObjectInputStream 和 ObjectOutputStream 是java原生的序列化以及反序列化类;

PipedInputStream&PipedOutputStream

管道输入流PipedInputStream 与 管道输出流PipedOutputStream 实现了类似管道的功能,用于不同线程之间的相互通信(跨线程的字节数据传输)。

PipedInputStream(管道输入流)应连接到PipedOutputStream(管道输出流);然后,管道输入流提供写入管道输出流的任何数据字节。通常,一个线程从PipedInputStream对象读取数据,另一个线程将数据写入相应的PipedOutStream。不建议尝试从单个线程同时使用这两个对象,因为这可能会使线程死锁。管道输入流包含一个缓冲区,在一定范围内将读操作与写操作分离。如果向连接的管道输出流提供数据字节的线程不再活动,则称管道已断开。

PipedOutputStream(管道输出流)可以连接到PipedInputStream(管道输入流)以创建通信管道。管道输出流是管道的发送端。通常,数据由一个线程写入PipedOutStream对象,数据由另一个线程从连接的PipedInputStream读取。不建议尝试从单个线程同时使用这两个对象,因为这可能会使线程死锁。如果从连接的管道输入流中读取数据字节的线程不再活动,则称管道已断开。

private static void PipedIOStream(){
    // 第一种连接方式
    PipedInputStream pipedInputStream = new PipedInputStream();
    PipedOutputStream pipedOutputStream = new PipedOutputStream();
    try {
        // 2选1
        pipedInputStream.connect(pipedOutputStream);
        pipedOutputStream.connect(pipedInputStream);
    } catch (IOException e) {
        e.printStackTrace();
    }

    // 第二种连接方式,也是2选1
    try {
        PipedInputStream pipedInputStream2 = new PipedInputStream();
        PipedOutputStream pipedOutputStream2 = new PipedOutputStream(pipedInputStream2);

        PipedOutputStream pipedOutputStream21 = new PipedOutputStream();
        PipedInputStream pipedInputStream21 = new PipedInputStream(pipedOutputStream21);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

// 上面方式就把两个输入输出管道连接起来了。

SequenceInputStream

SequenceInputStream表示其他输入流的逻辑连接。它从输入流的有序集合开始,从第一个流开始读取,直到到达文件末尾,然后从第二个流开始读取,依此类推,直到最后一个包含的输入流到达文件末尾。

说白了SequenceInputStream可以看做是多个InputStream对象的有序集合。当一个InputStream对象的数据读取完后,它会自动取出下一个InputStream对象进行读取,直到所有的InputStream对象都读取完为止。

try {
    FileInputStream fa = new FileInputStream("/a.log");
    FileInputStream fb = new FileInputStream("/b.log");
    FileInputStream fc = new FileInputStream("/c.log");
    Vector<InputStream> vector = new Vector<>();
    vector.addElement(fa);
    vector.addElement(fb);
    vector.addElement(fc);
    SequenceInputStream sis = new SequenceInputStream(vector.elements());

    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("all.log"));

    byte[] buf = new byte[1024];
    int len = buf.length;
    while((len = sis.read(buf, 0, len)) != -1) {
        bos.write(buf, 0, len);
    }

    bos.close();
    sis.close();
    fc.close();
    fb.close();
    fa.close();
} catch (IOException e) {
    e.printStackTrace();
}

StringBufferInputStream

这个类允许应用程序创建一个输入流,其中读取的字节由字符串的内容提供。应用程序还可以使用ByteArrayInputStream从字节数组中读取字节。

该类只使用字符串中每个字符的低位8位。

这个类不能正确地将字符转换为字节。作为在jdk1.1中创建流的首选方法是string是通过StringReader类。

现在已经被弃用了;

总结

这么多输入流输出流其中大部分都是成对出现的。是在某一定程度上来解决一些问题的;

这些流之间是可以相互之间进行转化的。

使用的场景呢就是对文件的读取写入(读写)。文件可以是任何(比如文本文件,图片,音频等);


CXJY

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值