`

java序列化的内部实现(一)

阅读更多

前言

 

上一遍关于HashSet的最后部分提到自定义序列化重写序列化的readObectwriteObject方法,个人感觉结束比较仓促。由于序列化在java中有着举重轻重的地位,尤其是在RPC框架中,对象的传输都是通过序列化完成。所以萌生了对java的序列化做一次系统的总结。

 

初步设想关于java序列化的总结分成三部分:java序列化的内部实现、java反序列化的内部实现、java序列化用法以及理论。

 

本篇分享为第一部分“java序列化的内部实现,主要是通过一个序列化实例 剖析java序列化的内部实现过程。

 

Java对象序列化

 

java提供了一个“对象序列化框架”,可以将“对象”编码为“字节流”,这个过程称之为序列化;反之 也可以从字节流编码中重新构建成一个新的对象,这个过程称之为反序列化。一旦一个对象被序列化后,就可以通过网络从一台服务器传输到另一台服务器,再进行存储或者反序列化后使用。这个过程在RPC框架中大量使用,因此良好的序列化设计可以减少这个过程的开销,提升服务性能。

 

Java对象的序列化和反序列化可以通过ObjectOutputStreamObjectInputStream实现,首先编写一个简单的对象序列化和反序列化实现,这里模拟将一个User对象序列化并存储到D盘的user.txt文件中,代码如下:

 

package com.sky.serial;
 
import java.io.*;
 
/**
 * Created by gantianxing on 2017/5/26.
 */
public class User implements Serializable {
 
    //可以用eclipse生成, 也可以随意指定一个非0的值
    private static final long serialVersionUID = 1L;
 
    private final String name;//姓名
 
    private final int sex;//性别0-女 1-男
 
    private String phoneNum;//手机号
 
    public User(String name,int sex){
        this.name = name;
        this.sex = sex;
    }
 
    public String getName() {
        return name;
    }
 
    public int getSex() {
        return sex;
    }
 
    public String getPhoneNum() {
        return phoneNum;
    }
 
    public void setPhoneNum(String phoneNum) {
        this.phoneNum = phoneNum;
    }
 
    @Override
    public String toString(){
        return "user info: name="+name+",sex="+sex+",phoneNum="+phoneNum;
    }
 
    public static void main(String[] args) throws Exception{
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D://user.txt"));
        //实例化一个user对象
        User user = new User("zhang san",1);
        user.setPhoneNum("13888888888");
        //将对象序列化存储到D:/user.txt 文件中
        out.writeObject(user);
 
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("D://user.txt"));
        //反序列化
        Object newUser = in.readObject();
        System.out.println(newUser);
    }
}
 

 

 

执行上面代码的main方法,控制台打印结果为:

” user info: name=zhang san,sex=1,phoneNum=13888888888”

 

打开D:/user.txt 文件进行查看,由于我们是将字节流存储到文件中的,所有直接查看 看到的是乱码,所以需要以二进制的格式查看。java序列化的的过程中都是以16进制写入字节流,采用16进制格式查看对阅读java序列化的源码很方便(用UEEditPlus都可以,我这里使用的EditPlus)。结果为:

 

AC ED 00 05 73 72 00 13 63 6F 6D 2E 73 6B 79 2E

73 65 72 69 61 6C 2E 55 73 65 72 00 00 00 00 00

00 00 01 02 00 03 49 00 03 73 65 78 4C 00 04 6E

61 6D 65 74 00 12 4C 6A 61 76 61 2F 6C 61 6E 67

2F 53 74 72 69 6E 67 3B 4C 00 08 70 68 6F 6E 65

4E 75 6D 71 00 7E 00 01 78 70 00 00 00 01 74 00

09 7A 68 61 6E 67 20 73 61 6E 74 00 0B 31 33 38

38 38 38 38 38 38 38 38 

 

思维先转变过来:这里的每个空格隔开的是116进制位 表示的是一个字节。直接看这一串16进制可以能有点晕。通过阅读java对象序列化的源码,我把它翻译了一下,每个字节的具体含义如下:

 

 

 

 

对应上图,我把每一个块儿,再详细描述一次(一共分为28步):

1AC ED 00 05:在调用ObjectOutputStream(OutputStream out) 构造方法时生成。具体是在writeStreamHeader()方法:

 

protected void writeStreamHeader() throws IOException {
        bout.writeShort(STREAM_MAGIC); //魔法数(Magic number 翻译)
        bout.writeShort(STREAM_VERSION);//版本号
}

 

 

STREAM_MAGIC STREAM_VERSION 是在ObjectStreamConstants中定义的常量:

  

  /**
     * Magic number that is written to the stream header.
     */
    final static short STREAM_MAGIC = (short)0xaced;
 
    /**
     * Version number that is written to the stream header.
     */
final static short STREAM_VERSION = 5;

 

 

 

AC ED为魔法数。版本号为short类型的5short是两个字节,用两个字节的16进制表示5 即为:00 05

 

273TC_OBJECTObjectStreamConstants中定义为新对象,表示接下来是一个对象。

 

   /**
     * new Object.
     */
    final static byte TC_OBJECT =       (byte)0x73;

 

 

 

372TC_CLASSDESC表示接下来是类的描述信息,在ObjectStreamConstants中定义为:

 

/**
     * new Class Descriptor.
     */
    final static byte TC_CLASSDESC =    (byte)0x72;

 

 

 

400 13:表示该对象对应类的全路径类名长度(com.sky.serial.User长度为19),这里是16进制表示 0x13,转换为10进制即为 19

563 6F 6D 2E 73 6B 79 2E 73 65 72 69 61 6C 2E 55 73 65 72:就是用16进制表示的字符串com.sky.serial.User

 

 

 

接下来是成员变量的描述信息:

 

600 00 00 00 00 00 00 01:8个字节表示的是long型的SUID,对应的private static final long serialVersionUID = 1L;

 

702 : SC_SERIALIZABLE表示该类支持序列化,在ObjectStreamConstants中定义为:

 

/**
     * Bit mask for ObjectStreamClass flag. Indicates class is Serializable.
     */
    final static byte SC_SERIALIZABLE = 0x02;

 

 

 

800 03表示成员变量个数,user类中的成员变量个数为3namesexphoneNum)。

 

949: 表示第一个成员变量(sex)的类型,16进制的49转换为十进制为73,刚好是大写字母’I’对应的值。’I’ 表示该类型为int

1000 03: 表示该变量名的长度,“sex”的长度为3

1173 65 78:转换成十进制分别为 115 101 120,对应的字符分布为s e x,即字符串sex

第一个成员的描述信息结束

 

124C: 表示第二个成员变量(name)的类型,转换为十进制:76,对应为大写字母‘L’,表示是一个类。

1300 04:表示变量名的长度,“name”的长度为4

6E 61 6D 65:转换为十进制分别为110 97 109 101,对应的字符拼接起来刚好是字符串”name”

 

1474TC_STRING 表示对象为String类型,在ObjectStreamConstants中定义为:

/**

     * new String.

     */

    final static byte TC_STRING =       (byte)0x74;

 

1500 12:表示这个类描述信息的长度("Ljava/lang/String;"),对应的十进制为18

 

164C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B: 表示的即为字符串 "Ljava/lang/String;"

 

 

 

第二个成员变量描述信息结束

 

174C:第三个成员变量(phoneNum),依然是一个对象(还是为String)。

1800 08” phoneNum”的长度为8

1970 68 6F 6E 65 4E 75 6D:对应的值即为字符串 ” phoneNum”

 

2071: TC_REFERENCE表示是引用,这里” phoneNum”也是String类型,前面已经解析过一次String(第二个成员变量),以后再用直接应用即可。在ObjectStreamConstants中定义为:

 

/**
     * Reference to an object already written into the stream.
     */
    final static byte TC_REFERENCE =    (byte)0x71;

 

 

 

2100 7E 00 01:新创建对象从00 7E 00 00开始,01表示引用序号(00XX) 这里表示第二个成员变量。在ObjectStreamConstants中定义为:

    

/**
     * First wire handle to be assigned.
     */
final static int baseWireHandle = 0x7e0000;

 

 

 

2278TC_ENDBLOCKDATA表是类的描述信息序列化结束,与前面的72相对应,在ObjectStreamConstants中定义为:

 

/**
     * End of optional block data blocks for an object.
     */
final static byte TC_ENDBLOCKDATA = (byte)0x78;

 

 

 

2370TC_NULL表示已经被引用,即没有父类,如果该类还是父类,下一步继续解析父类的类描述信息。这里我们的user没有父类,类描述信息序列化结束。在ObjectStreamConstants中定义为:

 

  

 /**
     * Null object reference.
     */
    final static byte TC_NULL =         (byte)0x70;

 

 

 

接下来开始序列化,成员变量的值信息。

 

2400 00 00 01:第一个成员变量sex的值,这里为整型值1int4个字节,即表示为:00 00 00 01

 

2574 00 09:第二成员变量,74表示接下来的值为一个String对象,00 09 表示值的长度,这里的值为“zhang san” 刚好为9

 

267A 68 61 6E 67 20 73 61 6E:表示的是第二个成员变量的值 “zhang san”

 

2774 00 0B:第三个成员变量,74同样表示接下来的值为一个String对象,00 0B表示长度是11,这里刚好是“13888888888”的长度

 

2831 33 38 38 38 38 38 38 38 38 38:表示的是第三个成员变量的值“13888888888”(逐个字符拼接,比如字符8,对应的int值为56,对应的16进制即为0x38)。

 

到这里整个序列化流程结束。这只是一个最简单的对象序列化过程,复杂点的比如user类多级继承,成员变量还有自定义对象,自定义对象里又有多级继承等等,序列化会解析类的整个拓扑结构。这里就不再对其他复杂的序列化字节文件进行逐一分析,我们可以在下一节阅读源码中,看到各种情况的序列化过程。

 

简单总结下:java对象的序列化分成两部分,前一部分是对类以及成员的描述进行序列化(元数据),第二部分是对成员的值进行序列化。

 

序列化过程源码解析

 

也许你会问上一节中的字节码是怎么解析出来的,其实一切尽在阅读java序列化框架的源码。Java序列化框架的核心类包括:ObjectOutputStreamObjectInputStreamObjectStreamClassObjectStreamFieldObjectStreamConstants

 

其中序列化和反序列化的主要业务逻辑都在ObjectOutputStreamObjectInputStream中完成,ObjectStreamClass里主要存放类描述信息,ObjectStreamField存放成员变量描述信息,ObjectStreamConstants存放的主要是一些16进制的常量(文章最后会列举出主要的常量信息)。

 

先看下上一节中的序列化代码:

 

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D://user.txt"));
        //实例化一个user对象
        User user = new User("zhang san",1);
        user.setPhoneNum("13888888888");
        //将对象序列化存储到D:/user.txt 文件中
        out.writeObject(user);

 

 

 

第一步创建首一个ObjectOutputStream的实例out,然后调用outwriteObject()方法。我们就以这里为入口,开始阅读java对象序列化的源码。

 

1ObjectOutputStream(OutputStream out) 构造方法处理逻辑:

 

 

public ObjectOutputStream(OutputStream out) throws IOException {
        verifySubclass(); //验证有无子类,这里没有
        bout = new BlockDataOutputStream(out);//实例化输出流,通过调用bout.writexxx方法向流中写入内容
        handles = new HandleTable(10, (float) 3.00); //初始化handles map,已序列化过的对象成员会放入其中
        subs = new ReplaceTable(10, (float) 3.00); //初始化替换对象(替换)map
        enableOverride = false;
        writeStreamHeader();//写入魔法数、版本 “AC ED 00 05”
        bout.setBlockDataMode(true);
        if (extendedDebugInfo) {//开启序列化日志-D sun.io.serialization.extendedDebugInfo=true
            debugInfoStack = new DebugTraceInfoStack();
        } else {
            debugInfoStack = null;
        }
    }

 

 

 

该构造方法,主要初始化工作:a、实例化输出流对象bout,后续调用其writexxx方法向流中写入数据;b、初始化一个内部实现的handles Map,后续用于存放已经处理过的对象成的引用;c、初始化一个内部实现的subs Map,后续用于存放ObjectOutputStream的受信子类的替换对象(用一个对象替换另外一个对象)

 

2、第二步再开来序列化入口方法writeObject(Object obj)

 

 

public final void writeObject(Object obj) throws IOException {
        if (enableOverride) {//初始化时是false,跳过
            writeObjectOverride(obj);//执行子类的writeObjectOverride方法
            return;
        }
        try {
            writeObject0(obj, false);//调用该方法向流 写入对象
        } catch (IOException ex) {
            if (depth == 0) {
                writeFatalException(ex);
            }
            throw ex;
        }
    }

 

 

这个方法先判断bout是否是ObjectOutputStream的子类。这我们直接使用ObjectOutputStream进行实例化,跳过if方法,直接执行writeObject0方法。

 

3、第三步,调用writeObject0方法:

 

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) { // subs.lookup(obj) 查找是否有替换对象
                writeNull(); //写入空对象 TC 70
                return;
            } else if (!unshared && (h = handles.lookup(obj)) != -1) {// handles.lookup(obj) 查找是否写入,如果已经写入过,直接引用
                writeHandle(h);//写入引用TC 71
                return;
            } else if (obj instanceof Class) {
                writeClass((Class) obj, unshared); //写入引用类TC 76
                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);//根据cl创建ObjectStreamClass,即把待序列化对象 相关信息写入ObjectStreamClass,
                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) {//如果已替换,重新执行一次检查,写入TC
                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); //写入String
            } 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); //写入序列化对象 TC_OBJECT 73 、类描述信息(类名、suid、序列化、成员个数)
            } else {
                if (extendedDebugInfo) {
                    throw new NotSerializableException(
                        cl.getName() + "\n" + debugInfoStack.toString());
                } else {
                    throw new NotSerializableException(cl.getName());
                }
            }
        } finally {
            depth--;
            bout.setBlockDataMode(oldMode);
        }
    }

 

 

第一次进入writeObject0 方法前面验证都会跳过,直到调用writeOrdinaryObject方法,开始写入类描述信息

 

4、第四步,调用writeOrdinaryObject方法 开始写入User类描述信息

 

 

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();
 
            bout.writeByte(TC_OBJECT);//写入新类TC 73
            writeClassDesc(desc, false);//写入类描述信息、成员变量描述信息
            handles.assign(unshared ? null : obj);//处理过的类写入handles,以便下次直接引用使用
            if (desc.isExternalizable() && !desc.isProxy()) {
                writeExternalData((Externalizable) obj);//调用用户重写的writeExternal方法,写入成员变量值
            } else {
                writeSerialData(obj, desc);//否则调用默认方法 写入成员变量值
            }
        } finally {
            if (extendedDebugInfo) {
                debugInfoStack.pop();
            }
        }
    }

 

 

 

这个方法首先调用writeClassDesc()方法向流中写入类描述信息、成员变量描述信息。再调用writeSerialData()方法(在步骤6中讲解),写入成员变量值。先看writeClassDesc()

 

 

private void writeClassDesc(ObjectStreamClass desc, boolean unshared)
        throws IOException
    {
        int handle;
        if (desc == null) {
            writeNull();
        } else if (!unshared && (handle = handles.lookup(desc)) != -1) {//handle中已经存在 该类描述信息 直接引用
            writeHandle(handle);
        } else if (desc.isProxy()) {
            writeProxyDesc(desc, unshared);//代理
        } else {
            writeNonProxyDesc(desc, unshared);//非代理写入
        }
    }

 

 

 

这里调用 writeNonProxyDesc方法。

 

 

private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)
        throws IOException
    {
        bout.writeByte(TC_CLASSDESC);
        handles.assign(unshared ? null : desc);
 
        if (protocol == PROTOCOL_VERSION_1) {
            // do not invoke class descriptor write hook with old protocol
            desc.writeNonProxy(this);
        } else {
            writeClassDescriptor(desc);
        }
 
        Class<?> cl = desc.forClass();
        bout.setBlockDataMode(true);
        if (cl != null && isCustomSubclass()) {
            ReflectUtil.checkPackageAccess(cl);
        }
        annotateClass(cl);
        bout.setBlockDataMode(false);
        bout.writeByte(TC_ENDBLOCKDATA); //该类描述信息写入完毕 TC_ENDBLOCKDATA 78;
 
        writeClassDesc(desc.getSuperDesc(), false); //判断是否还有父类,如果有继续写父类描述信息,否则写入空对象TC_NULL 结束
    }

 

 

 

该方法先写入类描述开始TC_CLASSDESC (72)标记;

然后调用writeClassDescriptor(ObjectStreamClass desc)方法à调用ObjectStreamClass类的writeNonProxy方法写入类描述信息,以及成员描述信息;

最后写入类描述完毕TC_ENDBLOCKDATA标记(78), 并判断是否还有父类,如果有,继续递归调用writeClassDesc方法写入父类描述信息,否则 写入空对象标记TC_NULL(70)

 

5、最终调用的ObjectStreamClass类的writeNonProxy方法,写入类描述信息:

 

 

protected void writeClassDescriptor(ObjectStreamClass desc)
        throws IOException
    {
        desc.writeNonProxy(this);
    }
//ObjectStreamClass类的writeNonProxy方法
void writeNonProxy(ObjectOutputStream out) throws IOException {
        out.writeUTF(name);
        out.writeLong(getSerialVersionUID()); //写入suid,这里是1L, 8个字节表示为: 00 00 00 00 00 00 00 01                
 
        byte flags = 0;
        if (externalizable) {
            flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
            int protocol = out.getProtocolVersion();
            if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) {
                flags |= ObjectStreamConstants.SC_BLOCK_DATA;
            }
        } else if (serializable) {
            flags |= ObjectStreamConstants.SC_SERIALIZABLE;//这里User类实现Serializable,判断逻辑走这里
        }
        if (hasWriteObjectData) {
            flags |= ObjectStreamConstants.SC_WRITE_METHOD;
        }
        if (isEnum) {
            flags |= ObjectStreamConstants.SC_ENUM;
        }
        out.writeByte(flags); //写入 SC_SERIALIZABLE (02)
 
        out.writeShort(fields.length); //写入成员变量个数3个
        for (int i = 0; i < fields.length; i++) {//遍历成员变量sex、name、phoneNum
            ObjectStreamField f = fields[i];
            out.writeByte(f.getTypeCode()); //写入成员变量类型
            out.writeUTF(f.getName()); //写入成员变量名称
            if (!f.isPrimitive()) { //如果是对象,写入对象类型,这里name、phoneNum需要写入
                out.writeTypeString(f.getTypeString());//写入对象类型
            }
        }
    }
 
void writeTypeString(String str) throws IOException {
        int handle;
        if (str == null) {
            writeNull();
        } else if ((handle = handles.lookup(str)) != -1) {
            writeHandle(handle); //phoneNum调用这个方法,直接引用name的String描述即可
        } else {
            writeString(str, false); //name字段 调用这个方法
        }
    }
 
    private void writeString(String str, boolean unshared) throws IOException {
        handles.assign(unshared ? null : str); //name 字段的类型String 在这一步写入handles Map,后续如果还有String成员,直接引用即可。
        long utflen = bout.getUTFLength(str);
        if (utflen <= 0xFFFF) {
            bout.writeByte(TC_STRING);
            bout.writeUTF(str, utflen);
        } else {
            bout.writeByte(TC_LONGSTRING);
            bout.writeLongUTF(str, utflen);
        }
    }

 

 

以上是类描述和成员变量描述信息的序列化。

 

6、回到步骤4,类描述和成员变量描述信息的序列化完成后,继续调用writeSerialData方法写入各个成员变量值信息。

 

 

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()) { //判断是否重新了writeObject方法
                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);//调用对象重写的writeObject方法
                    bout.setBlockDataMode(false);
                    bout.writeByte(TC_ENDBLOCKDATA);
                } finally {
                    curContext.setUsed();
                    curContext = oldContext;
                    if (extendedDebugInfo) {
                        debugInfoStack.pop();
                    }
                }
 
                curPut = oldPut;
            } else {
                defaultWriteFields(obj, slotDesc); //User对象的成员值写入,调用这个默认方法
            }
        }
    }

 

 

 

ObjectStreamClass 类的解析成员拓扑结构方法

 

private ClassDataSlot[] getClassDataLayout0()
        throws InvalidClassException
    {
        ArrayList<ClassDataSlot> slots = new ArrayList<>();
        Class<?> start = cl, end = cl;
 
        // locate closest non-serializable superclass
        while (end != null && Serializable.class.isAssignableFrom(end)) {
            end = end.getSuperclass();
        }
 
        HashSet<String> oscNames = new HashSet<>(3);
 
        for (ObjectStreamClass d = this; d != null; d = d.superDesc) {
            if (oscNames.contains(d.name)) {
                throw new InvalidClassException("Circular reference.");
            } else {
                oscNames.add(d.name);
            }
 
            // search up inheritance hierarchy for class with matching name
            String searchName = (d.cl != null) ? d.cl.getName() : d.name;
            Class<?> match = null;
            for (Class<?> c = start; c != end; c = c.getSuperclass()) {
                if (searchName.equals(c.getName())) {
                    match = c;
                    break;
                }
            }
 
            // add "no data" slot for each unmatched class below match
            if (match != null) {
                for (Class<?> c = start; c != match; c = c.getSuperclass()) {
                    slots.add(new ClassDataSlot(
                        ObjectStreamClass.lookup(c, true), false));
                }
                start = match.getSuperclass();
            }
 
            // record descriptor/class pairing
            slots.add(new ClassDataSlot(d.getVariantFor(match), true)); //记录类的继承拓扑关系()
        }
 
        // add "no data" slot for any leftover unmatched classes
        for (Class<?> c = start; c != end; c = c.getSuperclass()) {
            slots.add(new ClassDataSlot(
                ObjectStreamClass.lookup(c, true), false)); //记录类的继承拓扑关系
        }
 
        // order slots from superclass -> subclass
        Collections.reverse(slots);
        return slots.toArray(new ClassDataSlot[slots.size()]);
    }

 

 

 

7defaultWriteFields默认的值写入方法

 

 

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];
        }
        desc.getPrimFieldValues(obj, primVals);
        bout.write(primVals, 0, primDataSize, false);//写入基础类型,如:int char long等,这里是写入sex字段对应的值
 
        ObjectStreamField[] fields = desc.getFields(false);
        Object[] objVals = new Object[desc.getNumObjFields()];
        int numPrimFields = fields.length - objVals.length;
        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 {
                writeObject0(objVals[i],
                             fields[numPrimFields + i].isUnshared()); //递归调用writeObject0方法写入对象,这里是写入String值
            } finally {
                if (extendedDebugInfo) {
                    debugInfoStack.pop();
                }
            }
        }
    }

 

 

 

到这里整个序列化过程结束。

 

ObjectStreamClass 描述类初始化过程

 

在上一节中步骤3中,调用desc = ObjectStreamClass.lookup(cl, true);根据cl创建ObjectStreamClass。简单的说就是把User对象相关信息先写入ObjectStreamClass中,共后续bout写入流使用。

 

 

static ObjectStreamClass lookup(Class<?> cl, boolean all) {
        if (!(all || Serializable.class.isAssignableFrom(cl))) {
            return null;
        }
        processQueue(Caches.localDescsQueue, Caches.localDescs);
        WeakClassKey key = new WeakClassKey(cl, Caches.localDescsQueue); //创建弱引用的key对象
        Reference<?> ref = Caches.localDescs.get(key);
        Object entry = null;
        if (ref != null) {
            entry = ref.get();
        }
        EntryFuture future = null;
        if (entry == null) {
            EntryFuture newEntry = new EntryFuture();
            Reference<?> newRef = new SoftReference<>(newEntry);
            do {
                if (ref != null) {
                    Caches.localDescs.remove(key, ref); //如果缓存中已经存在,先移除
                }
                ref = Caches.localDescs.putIfAbsent(key, newRef);//放入的新类描述到缓存中
                if (ref != null) {
                    entry = ref.get();
                }
            } while (ref != null && entry == null);
            if (entry == null) {
                future = newEntry;
            }
        }
 
        if (entry instanceof ObjectStreamClass) {  // check common case first
            return (ObjectStreamClass) entry;
        }
        if (entry instanceof EntryFuture) {
            future = (EntryFuture) entry;
            if (future.getOwner() == Thread.currentThread()) {
                /*
                 * Handle nested call situation described by 4803747: waiting
                 * for future value to be set by a lookup() call further up the
                 * stack will result in deadlock, so calculate and set the
                 * future value here instead.
                 */
                entry = null;
            } else {
                entry = future.get();
            }
        }
        if (entry == null) {
            try {
                entry = new ObjectStreamClass(cl); //调用该构造方法进行初始化
            } catch (Throwable th) {
                entry = th;
            }
            if (future.set(entry)) {
                Caches.localDescs.put(key, new SoftReference<Object>(entry)); //类描述信息放入缓存
            } else {
                // nested lookup call already set future
                entry = future.get();
            }
        }
 
        if (entry instanceof ObjectStreamClass) {
            return (ObjectStreamClass) entry;
        } else if (entry instanceof RuntimeException) {
            throw (RuntimeException) entry;
        } else if (entry instanceof Error) {
            throw (Error) entry;
        } else {
            throw new InternalError("unexpected entry: " + entry);
        }
    }

 

 

 

 

//类描述初始化
private ObjectStreamClass(final Class<?> cl) {
        this.cl = cl;
        name = cl.getName(); //对象类名
        isProxy = Proxy.isProxyClass(cl); //是否是代理类
        isEnum = Enum.class.isAssignableFrom(cl); //是否是枚举
        serializable = Serializable.class.isAssignableFrom(cl); //是否实现了序列化接口
        externalizable = Externalizable.class.isAssignableFrom(cl);//是否实现了externalizable接口
 
        Class<?> superCl = cl.getSuperclass();
        superDesc = (superCl != null) ? lookup(superCl, false) : null; //直接父类,Object不算
        localDesc = this;
 
        if (serializable) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    if (isEnum) {
                        suid = Long.valueOf(0);
                        fields = NO_FIELDS;
                        return null;
                    }
                    if (cl.isArray()) {
                        fields = NO_FIELDS;
                        return null;
                    }
 
                    suid = getDeclaredSUID(cl); //获取对象的suid
                    try {
                        fields = getSerialFields(cl);
                        computeFieldOffsets();
                    } catch (InvalidClassException e) {
                        serializeEx = deserializeEx =
                            new ExceptionInfo(e.classname, e.getMessage());
                        fields = NO_FIELDS;
                    }
 
                    if (externalizable) { //是否实现Externalizable接口
                        cons = getExternalizableConstructor(cl);
                    } else {
                        cons = getSerializableConstructor(cl);
                        writeObjectMethod = getPrivateMethod(cl, "writeObject",
                            new Class<?>[] { ObjectOutputStream.class },
                            Void.TYPE); //获取重写的writeObject方法
                        readObjectMethod = getPrivateMethod(cl, "readObject",
                            new Class<?>[] { ObjectInputStream.class },
                            Void.TYPE);//获取重写的readObject方法
                        readObjectNoDataMethod = getPrivateMethod(
                            cl, "readObjectNoData", null, Void.TYPE);//获取重写的readObjectNoData方法
                        hasWriteObjectData = (writeObjectMethod != null);
                    }
                    writeReplaceMethod = getInheritableMethod(
                        cl, "writeReplace", null, Object.class);//获取重写的writeReplace方法
                    readResolveMethod = getInheritableMethod(
                        cl, "readResolve", null, Object.class);//获取重写的readResolve方法
                    return null;
                }
            });
        } else {
            suid = Long.valueOf(0);
            fields = NO_FIELDS;
        }
 
        try {
            fieldRefl = getReflector(fields, this); //初始化成员变量描述信息
        } catch (InvalidClassException ex) {
            // field mismatches impossible when matching local fields vs. self
            throw new InternalError(ex);
        }
 
        if (deserializeEx == null) {
            if (isEnum) {
                deserializeEx = new ExceptionInfo(name, "enum type");
            } else if (cons == null) {
                deserializeEx = new ExceptionInfo(name, "no valid constructor");
            }
        }
        for (int i = 0; i < fields.length; i++) {
            if (fields[i].getField() == null) {
                defaultSerializeEx = new ExceptionInfo(
                    name, "unmatched serializable field(s) declared");
            }
        }
        initialized = true;
    }

 

 

 

 

//获取对象的成员变量列表
private static ObjectStreamField[] getSerialFields(Class<?> cl)
        throws InvalidClassException
    {
        ObjectStreamField[] fields;
        if (Serializable.class.isAssignableFrom(cl) &&
            !Externalizable.class.isAssignableFrom(cl) &&
            !Proxy.isProxyClass(cl) &&
            !cl.isInterface())
        {
            if ((fields = getDeclaredSerialFields(cl)) == null) {
                fields = getDefaultSerialFields(cl);
            }
            Arrays.sort(fields); //排序,这就是为什么按照sex、name、phoneNum进行排序
        } else {
            fields = NO_FIELDS;
        }
        return fields;
    }

 

 

 

 

//初始化成员变量描述信息,
private static FieldReflector getReflector(ObjectStreamField[] fields,
                                               ObjectStreamClass localDesc)
        throws InvalidClassException
    {
        // class irrelevant if no fields
        Class<?> cl = (localDesc != null && fields.length > 0) ?
            localDesc.cl : null;
        processQueue(Caches.reflectorsQueue, Caches.reflectors);
        FieldReflectorKey key = new FieldReflectorKey(cl, fields,
                                                      Caches.reflectorsQueue);
        Reference<?> ref = Caches.reflectors.get(key);
        Object entry = null;
        if (ref != null) {
            entry = ref.get();
        }
        EntryFuture future = null;
        if (entry == null) {
            EntryFuture newEntry = new EntryFuture();
            Reference<?> newRef = new SoftReference<>(newEntry);
            do {
                if (ref != null) {
                    Caches.reflectors.remove(key, ref);
                }
                ref = Caches.reflectors.putIfAbsent(key, newRef);//重新缓存
                if (ref != null) {
                    entry = ref.get();
                }
            } while (ref != null && entry == null);
            if (entry == null) {
                future = newEntry;
            }
        }
 
        if (entry instanceof FieldReflector) {  // check common case first
            return (FieldReflector) entry;
        } else if (entry instanceof EntryFuture) {
            entry = ((EntryFuture) entry).get();
        } else if (entry == null) {
            try {
                entry = new FieldReflector(matchFields(fields, localDesc)); //调用FieldReflector构造方法进行初始化
            } catch (Throwable th) {
                entry = th;
            }
            future.set(entry);
            Caches.reflectors.put(key, new SoftReference<Object>(entry));//成员表里描述信息放入缓存
        }
 
        if (entry instanceof FieldReflector) {
            return (FieldReflector) entry;
        } else if (entry instanceof InvalidClassException) {
            throw (InvalidClassException) entry;
        } else if (entry instanceof RuntimeException) {
            throw (RuntimeException) entry;
        } else if (entry instanceof Error) {
            throw (Error) entry;
        } else {
            throw new InternalError("unexpected entry: " + entry);
        }
    }

 

 

 

FieldReflectorObjectStreamClass的内部静态类,上一步中循环调用matchFields方法对ObjectStreamField初始化:

 

private static ObjectStreamField[] matchFields(ObjectStreamField[] fields,
                                                   ObjectStreamClass localDesc)
        throws InvalidClassException
    {
        ObjectStreamField[] localFields = (localDesc != null) ?
            localDesc.fields : NO_FIELDS;
 
        /*
         * Even if fields == localFields, we cannot simply return localFields
         * here.  In previous implementations of serialization,
         * ObjectStreamField.getType() returned Object.class if the
         * ObjectStreamField represented a non-primitive field and belonged to
         * a non-local class descriptor.  To preserve this (questionable)
         * behavior, the ObjectStreamField instances returned by matchFields
         * cannot report non-primitive types other than Object.class; hence
         * localFields cannot be returned directly.
         */
 
        ObjectStreamField[] matches = new ObjectStreamField[fields.length];
        for (int i = 0; i < fields.length; i++) {
            ObjectStreamField f = fields[i], m = null;
            for (int j = 0; j < localFields.length; j++) {
                ObjectStreamField lf = localFields[j];
                if (f.getName().equals(lf.getName())) {
                    if ((f.isPrimitive() || lf.isPrimitive()) &&
                        f.getTypeCode() != lf.getTypeCode())
                    {
                        throw new InvalidClassException(localDesc.name,
                            "incompatible types for field " + f.getName());
                    }
                    if (lf.getField() != null) {
                        m = new ObjectStreamField(
                            lf.getField(), lf.isUnshared(), false); //调用ObjectStreamField的构造方法进行初始化
                    } else {
                        m = new ObjectStreamField(
                            lf.getName(), lf.getSignature(), lf.isUnshared());
                    }
                }
            }
            if (m == null) {
                m = new ObjectStreamField(
                    f.getName(), f.getSignature(), false);
            }
            m.setOffset(f.getOffset());
            matches[i] = m;
        }
        return matches;
    }

 

 

 

 

// ObjectStreamField构造方法
ObjectStreamField(Field field, boolean unshared, boolean showType) {
        this.field = field;
        this.unshared = unshared;
        name = field.getName();
        Class<?> ftype = field.getType();
        type = (showType || ftype.isPrimitive()) ? ftype : Object.class;
        signature = getClassSignature(ftype).intern();
    }

 

关于常量

 

 

需要说下ObjectStreamConstants常量类,里面记录有java序列化的各种标记,多熟悉这里的常量,对阅读源码很有帮助。这里就不再列举,可以自行查阅。

 

关于成员变量的标记在ObjectStreamFieldgetClassSignature中定义的:

 

private static String getClassSignature(Class<?> cl) {
        StringBuilder sbuf = new StringBuilder();
        while (cl.isArray()) {
            sbuf.append('['); //数组类型
            cl = cl.getComponentType();
        }
        if (cl.isPrimitive()) { //基础类型
            if (cl == Integer.TYPE) {
                sbuf.append('I');
            } else if (cl == Byte.TYPE) {
                sbuf.append('B');
            } else if (cl == Long.TYPE) {
                sbuf.append('J');
            } else if (cl == Float.TYPE) {
                sbuf.append('F');
            } else if (cl == Double.TYPE) {
                sbuf.append('D');
            } else if (cl == Short.TYPE) {
                sbuf.append('S');
            } else if (cl == Character.TYPE) {
                sbuf.append('C');
            } else if (cl == Boolean.TYPE) {
                sbuf.append('Z');
            } else if (cl == Void.TYPE) {
                sbuf.append('V');
            } else {
                throw new InternalError();
            }
        } else {
            sbuf.append('L' + cl.getName().replace('.', '/') + ';'); //对象类型
        }
        return sbuf.toString();
    }

 

 

 

保护的构造方法中也有体现:

    

ObjectStreamField(String name, String signature, boolean unshared) {
        if (name == null) {
            throw new NullPointerException();
        }
        this.name = name;
        this.signature = signature.intern();
        this.unshared = unshared;
        field = null;
 
        switch (signature.charAt(0)) {
            case 'Z': type = Boolean.TYPE; break;
            case 'B': type = Byte.TYPE; break;
            case 'C': type = Character.TYPE; break;
            case 'S': type = Short.TYPE; break;
            case 'I': type = Integer.TYPE; break;
            case 'J': type = Long.TYPE; break;
            case 'F': type = Float.TYPE; break;
            case 'D': type = Double.TYPE; break;
            case 'L':
            case '[': type = Object.class; break;
            default: throw new IllegalArgumentException("illegal signature");
        }
    }

 

 

成员变量的type signature互转的逻辑,也在上面两个方法中。

 

关于ObjectOutputStream中的引用重用

 

ObjectOutputStream中有两个静态内部类HandleTableReplaceTable,是两个简单的map实现。对应的成员变量为:

 

private final HandleTable handles;
private final ReplaceTable subs;

 

 

 

分别用于存放已经序列化过的类描述对象引用、替换类描述对象引用。以便再后续序列化中,遇到相同类型直接引用即可,不再重复解析。

 

关于序列化对象替换:

 

新建一个类继承ObjectOutputStream,重写其replaceObject方法。看一个例子就明白了:

 

 

   package com.sky.serial;
 
import java.io.*;
 
/**
 * Created by gantianxing on 2017/5/28.
 */
public class ObjectOutputStreamDemo extends ObjectOutputStream {
 
    public ObjectOutputStreamDemo(OutputStream out) throws IOException {
        super(out);
    }
 
    @Override
    public Object replaceObject(Object obj) throws IOException {
        return "replace";
    }
 
    public static void main(String[] args) {
 
        Object s1 = "string1";
        Object s2 = "string2";
        try {
 
            // create a new file with an ObjectOutputStream
            FileOutputStream out = new FileOutputStream("D://ss.txt");
            ObjectOutputStreamDemo oout = new ObjectOutputStreamDemo(out);
 
            // 序列化s1
            oout.writeObject(s1);
 
            // 开启允许替换
            oout.enableReplaceObject(true);
 
            // 替换
            oout.replaceObject(s2);
 
            //写入S2,这时S2会被"replace"
            oout.writeObject(s2);
 
            // close the stream
            oout.close();
 
            // create an ObjectInputStream for the file we created before
            ObjectInputStream ois =
                    new ObjectInputStream(new FileInputStream("D://ss.txt"));
 
            // read and print an int
            System.out.println("" + (String) ois.readObject());
            System.out.println("" + (String) ois.readObject());
 
        } catch (Exception ex) {
            ex.printStackTrace();
        }
 
    }
}

 

 

 打印信息为:

string1
replace

 

说明“string2”在序列化时被“replace”替换。其实上面的源码解析中,也有涉及这部分内容。不再进行详细解析。

 

 

关于ObjectStreamClass类的缓存

 

ObjectStreamClass中一个静态的缓存内部类Caches,主要用途是在序列化的过程中尽量使用,已经存在的类描述对象和成员描述对象,减少内存消耗。

 

    

private static class Caches {
        /** cache mapping local classes -> descriptors */
        static final ConcurrentMap<WeakClassKey,Reference<?>> localDescs =
            new ConcurrentHashMap<>();
 
        /** cache mapping field group/local desc pairs -> field reflectors */
        static final ConcurrentMap<FieldReflectorKey,Reference<?>> reflectors =
            new ConcurrentHashMap<>();
 
        /** queue for WeakReferences to local classes */
        private static final ReferenceQueue<Class<?>> localDescsQueue =
            new ReferenceQueue<>();
        /** queue for WeakReferences to field reflectors keys */
        private static final ReferenceQueue<Class<?>> reflectorsQueue =
            new ReferenceQueue<>();
}

 

 

localDescslocalDescsQueue对应的是类描述的缓存;

reflectorsreflectorsQueue对应的是成员描述的缓存;

localDescsreflectorskey是弱引用WeakReferencevalue是软引用SoftReference

软弱引用都需要配合引用队列使用ReferenceQueue

关于软、弱引入,引用队列这里就不展开讲啦,后面有时间再单独进行总结。

 

java基础类型的序列化比较简单,直接调用out.writexxx方法即可(比如out.writeInt(x))。其实java对象的序列化,本质上也是转化为基础类型的序列化,但为了表示对象的内部关系加了很多TC标识而已。

 

这次就到这里吧:-D

 

下一篇讲解《java反序列化的内部实现(二)

  • 大小: 54.5 KB
  • 大小: 14 KB
  • 大小: 13.9 KB
0
0
分享到:
评论

相关推荐

    java面试过程当中遇到的一些题目

    6. 什么是java序列化,如何实现java序列化?(写一个实例) 10 7. 一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制? 10 8. 排序都有哪几种方法?请列举。用JAVA实现一个快速排序? 10 9. Overload...

    java面试题(面试 宝典)

    6. 什么是java序列化,如何实现java序列化?(写一个实例) 10 7. 一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制? 10 8. 排序都有哪几种方法?请列举。用JAVA实现一个快速排序? 10 9. Overload...

    【Java面试+Java学习指南】 一份涵盖大部分Java程序员所需要掌握的核心知识

    序列化和反序列化 继承、封装、多态的实现原理 容器 Java集合类总结 Java集合详解1:一文读懂ArrayList,Vector与Stack使用方法和实现原理 Java集合详解2:Queue和LinkedList Java集合详解3:Iterator,fail-fast机制...

    java源码包---java 源码 大量 实例

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

    Java开发技术大全(500个源代码).

    Student.java 定义一个用来序列化的类 ThreadIn.java 接收数据用的线程类 ThreadOut.java 发送数据用的线程类 TypeFile.java 显示文件内容的类 useScanner.java 用Scanner接收用户的输入 第8章 示例描述:本章...

    JAVA上百实例源码以及开源项目

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

    java面试题进阶版附答案.docx

    六、序列化和反序列化:解释了Java中序列化和反序列化的概念,以及通过实现Serializable接口进行对象的序列化和反序列化的过程。 七、内部类和匿名类:介绍了Java中的内部类和匿名类的概念,包括不同类型的内部类...

    JAVA_API1.6文档(中文)

    javax.sql.rowset.serial 提供实用工具类,允许 SQL 类型与 Java 编程语言数据类型之间的可序列化映射关系。 javax.sql.rowset.spi 第三方供应商在其同步提供者的实现中必须使用的标准类和接口。 javax.swing 提供...

    JAVA上百实例源码以及开源项目源代码

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

    java源码包4

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

    java源码包3

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

    java培训机构内部预习文档

    chp1.语言基础 基本语法、标识符、命名规范、八种基本类型、...chp14.I/O框架 File类、流的分类、基本字节流、基本字符流、字节过滤流、字符过滤流、桥转换、对象序列化 chp15.反射 类对象及其获取方式,反射常用方法

    Java工程师面试复习指南

    序列化和反序列化 继承封装多态的实现原理 集合类 Java集合类总结 Java集合详解:一文读懂ArrayList,Vector与Stack使用方法和实现原理 Java集合详解:Queue和LinkedList Java集合详解:迭代器,快速失败机制与比较器...

    java源码包2

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

    java jdk实列宝典 光盘源代码

    日历:使用swing和awt实现一个图形化的日历可以查询星期、日期和年份信息(CalenderTrain.java);标准计算器(Callulator.java);更改组建外观,对日历设置几个显示外观(lookandfeel.java);自定义对话框Dialog...

    Java 1.6 API 中文 New

    javax.sql.rowset.serial 提供实用工具类,允许 SQL 类型与 Java 编程语言数据类型之间的可序列化映射关系。 javax.sql.rowset.spi 第三方供应商在其同步提供者的实现中必须使用的标准类和接口。 javax.swing 提供一...

    JAVA基础课程讲义

    JAVA对象的序列化和反序列化 161 为什么需要序列化和反序列化 161 对象的序列化主要有两种用途 161 序列化涉及的类和接口 162 序列化/反序列化的步骤和实例 162 综合的序列化和反序列化练习 163 JAVA.IO包相关流对象...

    【大厂面试题总结】JavaSE面试题总结详细教程

    【大厂面试题总结】JavaSE面试题总结详细教程: 目录: 递归算法之输出某个目录下所有文件和子目录列表 泛型中extends和super的区别 ...java序列化方式 java中实现多态的机制 string常量池和intern韩雅茹

    java api最新7.0

    javax.sql.rowset.serial 提供实用工具类,允许 SQL 类型与 Java 编程语言数据类型之间的可序列化映射关系。 javax.sql.rowset.spi 第三方供应商在其同步提供者的实现中必须使用的标准类和接口。 javax.swing 提供一...

Global site tag (gtag.js) - Google Analytics