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; }