JDK_Packages_java_io

若逢新雪初霁 满月当空 下面流转着亮银 而你带笑地想我走来 月色和雪色之间 你是第三种绝色 —余光中《绝色》

https://docs.oracle.com/javase/8/docs/api/java/io/package-summary.html

官方介绍

Provides for system input and output through data streams, serialization and the file system.

通过数据流、序列化和文件系统提供系统输入和输出。

前言

​ 创建一个好的I/O系统不是一件容易的事,难度似乎来自于需要覆盖所有的可能性。

因为不仅存在各种I/O源端(文件、控制台、网络连接等),还需要以多种不同的方式与这些I/O源端(顺序、回退、随机、缓冲、二进制、按字符、按行、按字)进行通信。Java类库的设计者通过创建大量的类来解决这个问题。

从下面三个主题来讨论这个话题

  • data streams
  • serialization
  • File system

接口总览

Interface Description
Closeable A Closeable is a source or destination of data that can be closed.
DataInput The DataInput interface provides for reading bytes from a binary stream and reconstructing from them data in any of the Java primitive types.
DataOutput The DataOutput interface provides for converting data from any of the Java primitive types to a series of bytes and writing these bytes to a binary stream.
Externalizable Only the identity of the class of an Externalizable instance is written in the serialization stream and it is the responsibility of the class to save and restore the contents of its instances.
FileFilter A filter for abstract pathnames.
FilenameFilter Instances of classes that implement this interface are used to filter filenames.
Flushable A Flushable is a destination of data that can be flushed.
ObjectInput ObjectInput extends the DataInput interface to include the reading of objects.
ObjectInputValidation Callback interface to allow validation of objects within a graph.
ObjectOutput ObjectOutput extends the DataOutput interface to include writing of objects.
ObjectStreamConstants Constants written into the Object Serialization Stream.
Serializable Serializability of a class is enabled by the class implementing the java.io.Serializable interface.

data streams

整体结构

I/O整体结构图

操作类型

  • 文件(file):FileInputStream、FileOutputStream、FileReader、FileWriter
  • 数组([]):
    • 2.1、字节数组(byte[]):ByteArrayInputStream、ByteArrayOutputStream
    • 2.2、字符数组(char[]):CharArrayReader、CharArrayWriter
  • 管道操作:PipedInputStream、PipedOutputStream、PipedReader、PipedWriter
  • 基本数据类型:DataInputStream、DataOutputStream
  • 缓冲操作:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
  • 打印:PrintStream、PrintWriter
  • 对象序列化反序列化:ObjectInputStream、ObjectOutputStream
  • 转换:InputStreamReader、OutputStreWriter
  • 字符串(String)Java8中已废弃StringBufferInputStream、StringBufferOutputStream、StringReader、StringWriter

传输类型

字节流 byte

InputStream、OutputStream

InputStream 类
inputStream方法
方法 方法介绍
public abstract int read() 读取数据
public int read(byte b[]) 将读取到的数据放在 byte 数组中,该方法实际上是根据下面的方法实现的,off 为 0,len 为数组的长度
public int read(byte b[], int off, int len) 从第 off 位置读取 len 长度字节的数据放到 byte 数组中,流是以 -1 来判断是否读取结束的(注意这里读取的虽然是一个字节,但是返回的却是 int 类型 4 个字节,这里当然是有原因,这里就不再细说了,推荐这篇文章,链接
public long skip(long n) 跳过指定个数的字节不读取,想想看电影跳过片头片尾
public int available() 返回可读的字节数量
public void close() 读取完,关闭流,释放资源
public synchronized void mark(int readlimit) 标记读取位置,下次还可以从这里开始读取,使用前要看当前流是否支持,可以使用 markSupport() 方法判断
public synchronized void reset() 重置读取位置为上次 mark 标记的位置
public boolean markSupported() 判断当前流是否支持标记流,和上面两个方法配套使用
inputStream 体系

image-20190922173230599

源码实现

InputStream 抽象类,所有子类实现如下

  • ByteArrayInputStream 字节数组输入流
  • PipedInputStream 管道流 用于线程间通信
  • FileInputStream 文件系统IO
  • FilterInputStream 装饰器模式
    • BufferedInputStream 带缓冲池的输入流
    • DataInputStream 基本数据类型的输入流
    • PushbackInputStream
  • ObjectInputStream
OutputStream 类

PrintStream ->System.out

方法 方法介绍
public abstract void write(int b) 写入一个字节,可以看到这里的参数是一个 int 类型,对应上面的读方法,int 类型的 32 位,只有低 8 位才写入,高 24 位将舍弃。
public void write(byte b[]) 将数组中的所有字节写入,和上面对应的 read() 方法类似,实际调用的也是下面的方法。
public void write(byte b[], int off, int len) 将 byte 数组从 off 位置开始,len 长度的字节写入
public void flush() 强制刷新,将缓冲中的数据写入
public void close() 关闭输出流,流被关闭后就不能再输出数据了

字符流 char

区别

1.实现接口

2.为什么有lock

其中

InputStreamReader,字节流通向字符流的桥梁:它使用指定的charset(nio)读取字节并将其解码为字符。功能是依赖于StreamDecoder完成的。

Reader、Writer

Reader 类
方法 方法介绍
public int read(java.nio.CharBuffer target) 读取字节到字符缓存中
public int read() 读取单个字符
public int read(char cbuf[]) 读取字符到指定的 char 数组中
abstract public int read(char cbuf[], int off, int len) 从 off 位置读取 len 长度的字符到 char 数组中
public long skip(long n) 跳过指定长度的字符数量
public boolean ready() 和上面的 available() 方法类似
public boolean markSupported() 判断当前流是否支持标记流
public void mark(int readAheadLimit) 标记读取位置,下次还可以从这里开始读取,使用前要看当前流是否支持,可以使用 markSupport() 方法判断
public void reset() 重置读取位置为上次 mark 标记的位置
abstract public void close() 关闭流释放相关资源
Writer 类
方法 方法介绍
public void write(int c) 写入一个字符
public void write(char cbuf[]) 写入一个字符数组
abstract public void write(char cbuf[], int off, int len) 从字符数组的 off 位置写入 len 数量的字符
public void write(String str) 写入一个字符串
public void write(String str, int off, int len) 从字符串的 off 位置写入 len 数量的字符
public Writer append(CharSequence csq) 追加吸入一个字符序列
public Writer append(CharSequence csq, int start, int end) 追加写入一个字符序列的一部分,从 start 位置开始,end 位置结束
public Writer append(char c) 追加写入一个 16 位的字符
abstract public void flush() 强制刷新,将缓冲中的数据写入
abstract public void close() 关闭输出流,流被关闭后就不能再输出数据了

参考链接:

@link: https://blog.csdn.net/panweiwei1994/article/details/78046000

@link https://www.jianshu.com/p/715659e4775f

serialization

​ Java序列化是指把Java对象保存为二进制字节码的过程,Java反序列化是指把二进制码重新转换成Java对象的过程。

为什么需要

  • 持久化
  • 网络传输

Serializable

https://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html

Serializability of a class is enabled by the class implementing the java.io.Serializable interface.

通过实现接口,开启可序列化

serialVersionUID

如果可序列化的类未明确声明serialVersionUID,则序列化运行时将根据该类的各个方面,为该类计算默认的serialVersionUID值,如Java(TM)对象序列化规范中所述。

但是,强烈建议所有可序列化的类显式声明serialVersionUID值,因为默认的serialVersionUID计算对类详细信息高度敏感,类详细信息可能会因编译器的实现而有所不同,因此可能导致意外情况 InvalidClassException在反序列化过程中。因此,为了保证不同Java编译器实现之间的serialVersionUID值一致,可序列化的类必须声明一个显式的serialVersionUID值。

还强烈建议显式serialVersionUID声明private在可能的情况下使用修饰符,因为此类声明仅适用于立即声明的类-serialVersionUID字段作为继承成员不起作用。数组类无法声明显式的serialVersionUID,因此它们始终具有默认的计算值,但是对于数组类,无需匹配serialVersionUID值。

ObjectStreamClass (序列化描述符)

官方文档对这个类的介绍如下

https://docs.oracle.com/javase/8/docs/api/java/io/ObjectStreamClass.html

Serialization’s descriptor for classes. It contains the name and serialVersionUID of the class. The ObjectStreamClass for a specific class loaded in this Java VM can be found/created using the lookup method.

可以看到ObjectStreamClass这个是类的序列化描述符,这个类可以描述需要被序列化的类的元数据,包括被序列化的类的名字以及序列号。可以通过lookup()方法来查找/创建在这个JVM中加载的特定的ObjectStreamClass对象

ObjectOutputStream (序列化)

image-20190923142345325

ObjectOutput

​ DataOutput includes methods for output of primitive types, ObjectOutput extends that interface to include objects, arrays, and Strings.

public interface ObjectOutput extends DataOutput, AutoCloseable {
    /**
     * Write an object to the underlying storage or stream.  The
     * class that implements this interface defines how the object is
     * written.
     *
     * @param obj the object to be written
     * @exception IOException Any of the usual Input/Output related exceptions.
     */
    public void writeObject(Object obj)
      throws IOException;

    /**
     * Writes a byte. This method will block until the byte is actually
     * written.
     * @param b the byte
     * @exception IOException If an I/O error has occurred.
     */
    public void write(int b) throws IOException;

    /**
     * Writes an array of bytes. This method will block until the bytes
     * are actually written.
     * @param b the data to be written
     * @exception IOException If an I/O error has occurred.
     */
    public void write(byte b[]) throws IOException;

    /**
     * Writes a sub array of bytes.
     * @param b the data to be written
     * @param off       the start offset in the data
     * @param len       the number of bytes that are written
     * @exception IOException If an I/O error has occurred.
     */
    public void write(byte b[], int off, int len) throws IOException;

    /**
     * Flushes the stream. This will write any buffered
     * output bytes.
     * @exception IOException If an I/O error has occurred.
     */
    public void flush() throws IOException;

    /**
     * Closes the stream. This method must be called
     * to release any resources associated with the
     * stream.
     * @exception IOException If an I/O error has occurred.
     */
    public void close() throws IOException;
}

ObjectStreamConstants

​ Constants written into the Object Serialization Stream.

更多细节 https://docs.oracle.com/javase/8/docs/api/java/io/ObjectStreamConstants.html

ObjectOutputStream

构造函数
public class ObjectOutputStream
    extends OutputStream implements ObjectOutput, ObjectStreamConstants{
        public ObjectOutputStream(OutputStream out) throws IOException {
        verifySubclass();
        bout = new BlockDataOutputStream(out);
        handles = new HandleTable(10, (float) 3.00);
        subs = new ReplaceTable(10, (float) 3.00);
        enableOverride = false;
        writeStreamHeader();
        bout.setBlockDataMode(true);
        if (extendedDebugInfo) {
            debugInfoStack = new DebugTraceInfoStack();
        } else {
            debugInfoStack = null;
        }
    }
    ...
}

构造函数中首先会把bout对绑定到底层的字节数据容器,接着会调用writeStreamHeader()方法

,会往底层字节容器中写入表示序列化的Magic Number以及版本号

writeObject
      public final void writeObject(Object obj) throws IOException {
        if (enableOverride) {
            writeObjectOverride(obj);
            return;
        }
        try {
            writeObject0(obj, false);
        } catch (IOException ex) {
            if (depth == 0) {
                writeFatalException(ex);
            }
            throw ex;
        }
    }
/**
 * Underlying writeObject/writeUnshared implementation.
 */
private void writeObject0(Object obj, boolean unshared)
    throws IOException
{
    boolean oldMode = bout.setBlockDataMode(false);
    depth++;
    try {
        // handle previously written and non-replaceable objects
        int h;
        if ((obj = subs.lookup(obj)) == null) {
            writeNull();
            return;
        } else if (!unshared && (h = handles.lookup(obj)) != -1) {
            writeHandle(h);
            return;
        } else if (obj instanceof Class) {
            writeClass((Class) obj, unshared);
            return;
        } else if (obj instanceof ObjectStreamClass) {
            writeClassDesc((ObjectStreamClass) obj, unshared);
            return;
        }

        // check for replacement object
        Object orig = obj;
        Class<?> cl = obj.getClass();
        ObjectStreamClass desc;
        for (;;) {
            // REMIND: skip this check for strings/arrays?
            Class<?> repCl;
            desc = ObjectStreamClass.lookup(cl, true);
            if (!desc.hasWriteReplaceMethod() ||
                (obj = desc.invokeWriteReplace(obj)) == null ||
                (repCl = obj.getClass()) == cl)
            {
                break;
            }
            cl = repCl;
        }
        if (enableReplace) {
            Object rep = replaceObject(obj);
            if (rep != obj && rep != null) {
                cl = rep.getClass();
                desc = ObjectStreamClass.lookup(cl, true);
            }
            obj = rep;
        }

        // if object replaced, run through original checks a second time
        if (obj != orig) {
            subs.assign(orig, obj);
            if (obj == null) {
                writeNull();
                return;
            } else if (!unshared && (h = handles.lookup(obj)) != -1) {
                writeHandle(h);
                return;
            } else if (obj instanceof Class) {
                writeClass((Class) obj, unshared);
                return;
            } else if (obj instanceof ObjectStreamClass) {
                writeClassDesc((ObjectStreamClass) obj, unshared);
                return;
            }
        }

        // remaining cases
        if (obj instanceof String) {
            writeString((String) obj, unshared);
        } else if (cl.isArray()) {
            writeArray(obj, desc, unshared);
        } else if (obj instanceof Enum) {
            writeEnum((Enum<?>) obj, desc, unshared);
        } else if (obj instanceof Serializable) {
            writeOrdinaryObject(obj, desc, unshared);
        } else {
            if (extendedDebugInfo) {
                throw new NotSerializableException(
                    cl.getName() + "\n" + debugInfoStack.toString());
            } else {
                throw new NotSerializableException(cl.getName());
            }
        }
    } finally {
        depth--;
        bout.setBlockDataMode(oldMode);
    }
}

这个方法主要就是 创建描述cl的ObjectStreamClass对象,然后根据是否实现接口调用writeOrdinaryObject方法

writeOrdinaryObject
/**
 * Writes representation of a "ordinary" (i.e., not a String, Class,
 * ObjectStreamClass, array, or enum constant) serializable object to the
 * stream.
 */
private void writeOrdinaryObject(Object obj,
                                 ObjectStreamClass desc,
                                 boolean unshared)
    throws IOException
{
    if (extendedDebugInfo) {
        debugInfoStack.push(
            (depth == 1 ? "root " : "") + "object (class \"" +
            obj.getClass().getName() + "\", " + obj.toString() + ")");
    }
    try {
        desc.checkSerialize();

       // 写入Object标志位
        bout.writeByte(TC_OBJECT);
        // 写入类元数据
        writeClassDesc(desc, false);
        handles.assign(unshared ? null : obj);
        if (desc.isExternalizable() && !desc.isProxy()) {
            writeExternalData((Externalizable) obj);
        } else {
           // 写入被序列化的对象的实例数据
            writeSerialData(obj, desc);
        }
    } finally {
        if (extendedDebugInfo) {
            debugInfoStack.pop();
        }
    }
}
writeSerialData
/**
 * Writes instance data for each serializable class of given object, from
 * superclass to subclass.
 */
private void writeSerialData(Object obj, ObjectStreamClass desc)
    throws IOException
{
    ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
    for (int i = 0; i < slots.length; i++) {
        ObjectStreamClass slotDesc = slots[i].desc;
        if (slotDesc.hasWriteObjectMethod()) {
            PutFieldImpl oldPut = curPut;
            curPut = null;
            SerialCallbackContext oldContext = curContext;

            if (extendedDebugInfo) {
                debugInfoStack.push(
                    "custom writeObject data (class \"" +
                    slotDesc.getName() + "\")");
            }
            try {
                curContext = new SerialCallbackContext(obj, slotDesc);
                bout.setBlockDataMode(true);
                slotDesc.invokeWriteObject(obj, this);
                bout.setBlockDataMode(false);
                bout.writeByte(TC_ENDBLOCKDATA);
            } finally {
                curContext.setUsed();
                curContext = oldContext;
                if (extendedDebugInfo) {
                    debugInfoStack.pop();
                }
            }

            curPut = oldPut;
        } else {
            defaultWriteFields(obj, slotDesc);
        }
    }
}

​ 在这个方法中首先会调用getClassDataSlot()方法获取被序列化对象的数据的布局, 需要注意的是这个方法会把从父类继承的数据一并返回,并且表示从父类继承的数据的ClassDataSlot对象在数组的最前面。

​ 然后根据slotDesc.hasWriteObjectMethod() , 对于没有自定义writeObject()方法的对象来说,接下来会调用defaultWriteFields()方法写入数据

defaultWriteFields
/**
 * Fetches and writes values of serializable fields of given object to
 * stream.  The given class descriptor specifies which field values to
 * write, and in which order they should be written.
 */
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
    throws IOException
{
    Class<?> cl = desc.forClass();
    if (cl != null && obj != null && !cl.isInstance(obj)) {
        throw new ClassCastException();
    }

    desc.checkDefaultSerialize();

    int primDataSize = desc.getPrimDataSize();
    if (primVals == null || primVals.length < primDataSize) {
        primVals = new byte[primDataSize];
    }
    // 获取对应类中的基本数据类型的数据并保存在primVals字节数组中
    desc.getPrimFieldValues(obj, primVals);
    // 把基本数据类型的数据写入底层字节容器中
    bout.write(primVals, 0, primDataSize, false);

        // 获取对应类的所有的字段对象
    ObjectStreamField[] fields = desc.getFields(false);
    Object[] objVals = new Object[desc.getNumObjFields()];
    int numPrimFields = fields.length - objVals.length;
    // 把对应类的Object类型(非原始类型)的对象保存到objVals数组中
    desc.getObjFieldValues(obj, objVals);
    for (int i = 0; i < objVals.length; i++) {
        if (extendedDebugInfo) {
            debugInfoStack.push(
                "field (class \"" + desc.getName() + "\", name: \"" +
                fields[numPrimFields + i].getName() + "\", type: \"" +
                fields[numPrimFields + i].getType() + "\")");
        }
        try {
            // 对所有Object类型的字段递归调用writeObject0()方法写入对应的数据
            writeObject0(objVals[i],
                         fields[numPrimFields + i].isUnshared());
        } finally {
            if (extendedDebugInfo) {
                debugInfoStack.pop();
            }
        }
    }
}

可以看到,在这个方法中会做下面几件事情:

  • 获取对应类的基本类型的字段的数据,并写入到底层的字节容器中。
  • 获取对应类的Object类型(非基本类型)的字段成员,递归调用writeObject0()方法写入相应的数据。

从上面对写入数据的分析可以知道,写入数据是是按照先父类后子类的顺序来写的。

总结

1. static和transient字段不能被序列化。

序列化的时候所有的数据都是来自于ObejctStreamClass对象,在生成ObjectStreamClass的构造函数中会调用fields = getSerialFields(cl);

​ int mask = Modifier.STATIC | Modifier.TRANSIENT;

2. 如何实现自定义序列化和反序列化?

​ 在ObejctStreamClass的构造函数中会查找被序列化类中有没有定义为void writeObject(ObjectOutputStream oos) 的函数,如果找到的话,则会把找到的方法赋值给writeObjectMethod这个变量,如果没有找到的话则为null。

ObjectInputStream (反序列化)

image-20190923142258452

反序列化过程就是按照前面介绍的序列化算法来解析二进制数据。

有一个需要注意的问题就是,如果子类实现了Serializable接口,但是父类没有实现Serializable接口,这个时候进行反序列化会发生什么情况?

答:如果父类有默认构造函数的话,即使没有实现Serializable接口也不会有问题,反序列化的时候会调用默认构造函数进行初始化,否则的话反序列化的时候会抛出.InvalidClassException:异常,异常原因为no valid constructor。

序列化性能

https://blog.csdn.net/qq_31457665/article/details/82587942

参考链接:

@link : https://mp.weixin.qq.com/s/1D-CtyvAXOYO8mH66HHJJQ

file system

File

概念

from https://docs.oracle.com/javase/8/docs/api/java/io/File.html

An abstract representation of file and directory pathnames.

User interfaces and operating systems use system-dependent pathname strings to name files and directories. This class presents an abstract, system-independent view of hierarchical pathnames. An abstract pathname has two components:

  1. An optional system-dependent prefix string, such as a disk-drive specifier, "/" for the UNIX root directory, or "\\\\" for a Microsoft Windows UNC pathname, and
  2. A sequence of zero or more string names.

文件和目录路径名的抽象表示形式。 我们知道,对于不同的操作系统,文件路径的描述是不同的 比如

  • windows平台:用\
  • linux平台:用/

File是Java为了这一概念提供的抽象描述,与系统无关的视图

抽象路径名有两个组件:

1.可选的与系统有关的前缀 字符串 比如盘符,”/“ 表示 UNIX 中的根目录,”\\“ 表示 Microsoft Windows UNC 路径名

2.零个或者多个 字符串 名称 序列

最后一个名称可以是目录,也可以是文件名称,那么File 并不一定就是一个文件,也可以是一个文件路径,也就是目录

File API分类

  • 文件自身属性读取
  • 创建文件/目录基本操作
  • 文件/目录 列表读取
  • 文件权限访问以及文件信息设置
  • 其他

FileSystem

File中有一个变量fs 类型为FileSystem

private static final FileSystem fs = DefaultFileSystem.getFileSystem();

​ 操作系统有各自的文件系统,这些文件系统又存在很多差异,而Java 因为是跨平台的,所以它必须要统一处理这些不同平台文件系统之间的差异,才能往上提供统一的入口。

说白了又是接口来实现统一,不同的操作系统实现这个接口,就可以提供统一的表现形式

FileSystem是一个抽象类,不同系统会有不同的实现

java.nio.file

在1.7中重构了文件系统

public Path toPath() {
    Path result = filePath;
    if (result == null) {
        synchronized (this) {
            result = filePath;
            if (result == null) {
                result = FileSystems.getDefault().getPath(path);
                filePath = result;
            }
        }
    }
    return result;
}

Nio file中解决了一些问题,和扩展

Interoperability with java.nio.file package

The java.nio.file package defines interfaces and classes for the Java virtual machine to access files, file attributes, and file systems. This API may be used to overcome many of the limitations of the java.io.File class. The toPath method may be used to obtain a Path that uses the abstract path represented by a File object to locate a file. The resulting Path may be used with the Files class to provide more efficient and extensive access to additional file operations, file attributes, and I/O exceptions to help diagnose errors when an operation on a file fails.

参考链接:

@Link:https://cloud.tencent.com/developer/article/1333744


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 951488791@qq.com

文章标题:JDK_Packages_java_io

字数:4.5k

本文作者:zhengyumin

发布时间:2019-09-23, 16:07:43

最后更新:2020-01-12, 23:08:36

原始链接:http://zyumin.github.io/2019/09/23/JDK-Packages-java-io/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。