• Java序列化总结(最全)


    概念

    • 实现 Serializable 接口, 它只是一个标记接口,不实现也能够进行序列化
    • RMI: 远程方法调用
    • RPC: 远程过程调用

    序列化ID

    解决了序列化与反序列出现代码不一致的问题, 不一致将导致序列化失败
    private static final long serialVersionUID = 1L; // 便于进行代码版本控制
    private static final long serialVersionUID = -5453781658505116230L; //便于控制代码结构

    静态变量序列化

    • x*- 序列化的是对象,而不是类,静态变量属于类级别,所以序列化不会保存静态变量

    父类序列化与Trancient关键字

    • 一个子类实现了 Serializable 接口,它的父类没有实现 Serializable 接口,那么序列化子类时,父类的值都不会进行保存
      •   需要父类保存值 ,就需要让父类也实现Serializable 接口
      •   取父对象的变量值时,它的值是调用父类无参构造函数后的值,出现如 int 型的默认是 0,string 型的默认是 null, 要指定值,那么需要在父类构造方法中进行指定
    • Trancient关键字指定的内容将不会被保存(阻止序列化)
      •   在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 nul
      • 使用继承关系同样可以实现,Trancient一样的效果,,即为父类不需要实现Serializable接口

    利用PutField getPutField字段进行加密

    原理:

    • 1: 进行序列化时,JVM试图调用对象的writeObject() readObject() 方法(允许自己私有化实现)
    • 2:默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法
    private void writeObject(ObjectOutputStream out) {
            try {
                PutField putFields = out.putFields(); //放到
                System.out.println("原密码:" + password);
                password = "encryption";// 模拟加密
                putFields.put("password", password);
                System.out.println("加密后的密码" + password);
                out.writeFields();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        private void readObject(ObjectInputStream in) {
            try {
                GetField readFields = in.readFields();
                Object object = readFields.get("password", "");
                System.out.println("要解密的字符串:" + object.toString());
                password = "pass";// 模拟解密,需要获得本地的密钥
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
     // 调用的时候直接调用 out的writeObject(),或者in的readObject() 即可

    序列化存储规则

    • 对同一对象两次写入文件, 第一次写入实际对象的序列化后的数据,第二次写入同一个对象的引用数据.(即为指向同一个对象)
      • 1: 节约了磁盘存储空间  
      • 2: 反序列化后的数据的值,应该是第一次保存的数据的值,(对于同一个对象第二次序列化,值是不会进行保存的)  


    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("result.obj"));
    Test test = new Test();
    test.i = ; // 有效
    out.writeObject(test);
    out.flush();
    test.i = ; //无效 第二次反序列化 只写出对象的引用关系 表示为同一个 引用对象,节约了磁盘空间
    out.writeObject(test);
    out.close();
    ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
    "result.obj"));
    Test t = (Test) oin.readObject();
    Test t = (Test) oin.readObject();
    System.out.println(t.i);//
    System.out.println(t.i);//

    Serializable接口的定义:

     public interface Serializable {} // 可以知道这个只是 一个标记接口, 并且JVM 并没有实现相应的反射代码,真的据说是起到标记作用! 那么这个标记 是在哪里进行判断的?

    标记的具体定义地方:

    writeObject0方法中有这么一段代码: 

    if (obj instanceof String) {
     2                writeString((String) obj, unshared);
     3            } else if (cl.isArray()) {
     4                writeArray(obj, desc, unshared);
     5            } else if (obj instanceof Enum) {
     6                writeEnum((Enum<?>) obj, desc, unshared);
     7            } else if (obj instanceof Serializable) {
     8                writeOrdinaryObject(obj, desc, unshared);
     9            } else {
    10                if (extendedDebugInfo) {
    11                    throw new NotSerializableException(
    12                        cl.getName() + "/n" + debugInfoStack.toString());
    13                } else {
    14                    throw new NotSerializableException(cl.getName());
    15                }
    16            }

    可以看出:  在进行序列化操作时,会判断要被序列化的类是否是Enum、Array和Serializable类型,如果不是则直接抛出 NotSerializableException 

    ArrayList分析

    • 要实现序列化 必须实现Serializable接口,ArrayList 也实现了这个接口

    transient Object[] elementData; //为什么要让ArrayList 存储数据的结构丢弃呢?
    答案:
    ArrayList实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,
    而实际只放了一个元素,那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化,
    ArrayList把元素数组设置为transient (一句话只对实际有效的值进行保存)

    • -* 实现策略:

    ArrayList 对writeObject readObject 方法进行了重写, 对NULL值数据进行了过滤

    具体分析:

    在ArrayList中定义了来个方法: writeObject 和 readObject 

    private void readObject(java.io.ObjectInputStream s)
     2        throws java.io.IOException, ClassNotFoundException {
     3        elementData = EMPTY_ELEMENTDATA;
     4        // Read in size, and any hidden stuff
     5        s.defaultReadObject();
     6        // Read in capacity
     7        s.readInt(); // ignored
     8        if (size > 0) {
     9            // be like clone(), allocate array based upon size not capacity
    10            ensureCapacityInternal(size);
    11            Object[] a = elementData;
    12            // Read in all elements in the proper order.
    13            for (int i=0; i<size; i++) {
    14                a[i] = s.readObject();
    15            }
    16        }
    17    }
    private void writeObject(java.io.ObjectOutputStream s)
     2        throws java.io.IOException{
     3        // Write out element count, and any hidden stuff
     4        int expectedModCount = modCount;
     5        s.defaultWriteObject();
     6        // Write out size as capacity for behavioural compatibility with clone()
     7        s.writeInt(size);
     8        // Write out all elements in the proper order.
     9        for (int i=0; i<size; i++) {
    10            s.writeObject(elementData[i]);
    11        }
    12        if (modCount != expectedModCount) {
    13            throw new ConcurrentModificationException();
    14        }
    15    }

    总结; 如何自定义的序列化和反序列化策略 重写 writeObject 和 readObject 方法,

    这两个方法是怎么被调用的?

    void invokeWriteObject(Object obj, ObjectOutputStream out)
     2        throws IOException, UnsupportedOperationException
     3    {
     4        if (writeObjectMethod != null) {
     5            try {
     6                writeObjectMethod.invoke(obj, new Object[]{ out });
     7            } catch (InvocationTargetException ex) {
     8                Throwable th = ex.getTargetException();
     9                if (th instanceof IOException) {
    10                    throw (IOException) th;
    11                } else {
    12                    throwMiscException(th);
    13                }
    14            } catch (IllegalAccessException ex) {
    15                // should not occur, as access checks have been suppressed
    16                throw new InternalError(ex);
    17            }
    18        } else {
    19            throw new UnsupportedOperationException();
    20        }
    21    }

    其中 writeObjectMethod.invoke(obj, new Object[]{ out }); 是关键,通过反射的方式调用writeObjectMethod方法。官方是这么解释这个writeObjectMethod的:

    class-defined writeObject method, or null if none

    在我们的例子中,这个方法就是我们在ArrayList中定义的writeObject方法。通过反射的方式被调用了

    那么怎么反射的呢?

      在  ObjectStreamClass这个方法中 有这么一段代码: 这样 readObjectMethod readObjectNoDataMethod 就拿到 了

                        if (externalizable) {
                            cons = getExternalizableConstructor(cl);
                        } else {
                            cons = getSerializableConstructor(cl);
                            writeObjectMethod = getPrivateMethod(cl, "writeObject",
                                new Class<?>[] { ObjectOutputStream.class },
                                Void.TYPE);
                            readObjectMethod = getPrivateMethod(cl, "readObject",
                                new Class<?>[] { ObjectInputStream.class },
                                Void.TYPE);
                            readObjectNoDataMethod = getPrivateMethod(
                                cl, "readObjectNoData", null, Void.TYPE);
                            hasWriteObjectData = (writeObjectMethod != null);
                        }
                        domains = getProtectionDomains(cons, cl);
                        writeReplaceMethod = getInheritableMethod(
                            cl, "writeReplace", null, Object.class);
                        readResolveMethod = getInheritableMethod(
                            cl, "readResolve", null, Object.class);
                        return null;
                    }
  • 相关阅读:
    如何做一个快乐的人
    嵌入式实时操作系统的可裁剪性及其实现
    Hello China操作系统运行截图(完整版)
    物联网操作系统的概念和特点
    Windows Phone 31日谈——第4日:设备方向
    Windows Phone 31日谈——第6日:工具栏
    Windows Phone 31 日谈——第8日:选择器
    Windows Phone 31日谈——第5日:系统主题
    Windows Phone 31 日谈——第10日:输入范围和文本框
    Windows Phone 31 日谈——第13日:位置服务
  • 原文地址:https://www.cnblogs.com/dgwblog/p/11710814.html
Copyright © 2020-2023  润新知