你了解Java序列化与反序列化吗

2021/5/14 20:32:00

本文主要是介绍你了解Java序列化与反序列化吗,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

本文主要了解Java的对象序列化的使用,序列化与反序列化源码追踪,Serializable接口、Externalizable接口、transient关键字、ObjectOutputStream、ObjectInputStream的运用。

说说我个人的理解,对象序列化就是将程序中的对象转换为其他形式的存在,某介质的存储于对象之间是可以随意转换,如人民币可以转换为黄金,可以转换为美元等等,序列化就是将数据转换为其他形式的存在。

那么在程序设计中存储数据的格式存在有多种,如:JSON、XML、YAML 是数据的存储格式,也是序列的一种,再者如编码字符集、base64、或者其他等等,使用自定义的编码规则,对数据进行转义编码,类比 y=f(x),x经f序列化得到y,y经f-1反序列化则可以得到x,但在Java中,有一种专属与Java的的序列化工具ObjectOutputStream,可以将Java的对象直接序列化为数据,且该数据还能反序列化为Java对象。

Java中的对象要能被序列化,在class中必须实现Serializable接口(若不实现Serializable接口,在序列化则会出现异常java.io.NotSerializableException)

 // 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());//异常就是在这里抛出的
                }
            }

开始上代码,新建一个User类,包含属性,实现序列化接口,如果有不需要序列化的字段可以加transient修饰

package csdn.io;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.OutputStream;
import java.io.Serializable;

public class User implements Serializable  {
	private static final long serialVersionUID = -6775433616501122464L;
	String name;
	transient String id;
	public User() {
	}
	public User(String name, String id) {
		super();
		this.name = name;
		this.id = id;
	}
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public  String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public String toString() {
		return "User [name=" + name + ", id=" + id + "]";
	}
	
	public static void main(String[] args) {
		try {
			User user = new User("denglintao", "1");
			String file = "E://1.txt";
			OutputStream out = new FileOutputStream(file);
			ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
			objectOutputStream.writeObject(user);
			objectOutputStream.close();
			FileInputStream fileInputStream = new FileInputStream(file);
			ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
			Object readObject = objectInputStream.readObject();
			System.out.println(readObject);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
}

主测试类debug调试观察

debug运行调试可以发现,打开序列化的文件,使用的是记事本默认编码,

可以简单看出 好像有个java类的包名加类名 csdn.io.User  还有一个字符串属性String值为denglintao,别的看不懂了,简单来说该文件就是记录User类的元数据结构信息,与各个结构数据对应的值,16进制显示如下:

可以发现transient修饰的id字段没有被序列化,console输出如下:

User [name=denglintao, id=null]

此时如果属性使用transient修饰就真的无法序列化吗?答案是非,接下来上代码,新建User1继承User实现  Externalizable接口,重写了writeExternal方法与readExernal方法,这两个方法分别实现的功能是将java对象序列化与反序列化的属性自行设置

package csdn.io;

import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.OutputStream;

public class User1 extends User implements Externalizable{

	public User1() {
	}
	
	public User1(String string, String string2) {
		super(string,string2);
	}

	@Override
	public void writeExternal(ObjectOutput out) throws IOException {
		out.writeObject(this.id);
		out.writeObject(this.name);//自定义序列化的字段 transient 修饰也无用
	}

	@Override
	public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
		//顺序与序列化一致
		this.id = (String) in.readObject();
		this.name = (String) in.readObject();
	}
	
	public static void main(String[] args) {
		try {
			User1 user = new User1("denglintao", "1");
			String file = "E://1.txt";
			OutputStream out = new FileOutputStream(file);
			ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
			objectOutputStream.writeObject(user);
			objectOutputStream.close();

			FileInputStream fileInputStream = new FileInputStream(file);
			ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

			Object readObject = objectInputStream.readObject();
			System.out.println(readObject);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

经debug调试可以发现transient修饰的属性也是可以被序列化的,Externalizable重写的两个方法就是手工配置自定义序列化的属性,输出结果如下:

User [name=denglintao, id=1]

跟踪源码可以发现Externalizable是继承了Serializable接口,而外新增了两个方法

package java.io;
import java.io.ObjectOutput;
import java.io.ObjectInput;
public interface Externalizable extends java.io.Serializable {
    void writeExternal(ObjectOutput out) throws IOException;
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

然后在ObjectOutputStream发现如下,当是Externalizable与Serializable走的是不同的分支实现

/**
     * 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();

            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序列化兴趣了可以继续往下追踪,这里就简单说下,如何选择过滤transient关键字修饰的字段,可以使用类反射拿到访问修饰符的值class.getModifiers()

那么如何判断属性是否有transient关键字修饰,可使用Modifier.isTransient(Modifier.TRANSIENT) 来进行判断,java语言的内部修饰符使用了位图来标识是否有某关键字,可以看见Modifier.TRANSIENT=128 二进制位图10000000只要进行"与"运算结果是1,那么该位图值就是1,所以存在该修饰符,同理判断是否含有其他修饰符原理一致

/**
     * Return {@code true} if the integer argument includes the
     * {@code transient} modifier, {@code false} otherwise.
     *
     * @param   mod a set of modifiers
     * @return {@code true} if {@code mod} includes the
     * {@code transient} modifier; {@code false} otherwise.
     */
    public static boolean isTransient(int mod) {
        return (mod & TRANSIENT) != 0;
    }

反之,反序列化也是同理,使用ObjectInputStream读取序列化输出的文件,进行反序列化,这里,我们重写ObjectInputStream的一个方法打个断点

public static void main(String[] args) {
		try {
			User1 user = new User1("denglintao", "1");
			String file = "E://1.txt";
			OutputStream out = new FileOutputStream(file);
			ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
			objectOutputStream.writeObject(user);
			objectOutputStream.close();
			FileInputStream fileInputStream = new FileInputStream(file);
			ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream) {

				@Override
				protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
					String name = desc.getName();//断点
					return super.resolveClass(desc);
				}
			};

			Object readObject = objectInputStream.readObject();
			System.out.println(readObject);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

 

debug调试可以发现ObjectStreamClass存储着反序列化class的元数据信息,resolveClass方法就是使用类反射加载得到一个class,后续就是使用该class创建一个实例对象,往对象里面set值

protected Class<?> resolveClass(ObjectStreamClass desc)
        throws IOException, ClassNotFoundException
    {
        String name = desc.getName();
        try {
            return Class.forName(name, false, latestUserDefinedLoader());
        } catch (ClassNotFoundException ex) {
            Class<?> cl = primClasses.get(name);
            if (cl != null) {
                return cl;
            } else {
                throw ex;
            }
        }
    }

如果是Externalizable接口的反序列化就手工反序列化设置值,否则就是普通根据transient修饰然后去除属性之类的设值

if (desc.isExternalizable()) {
    readExternalData((Externalizable) obj, desc);
 } else {
    readSerialData(obj, desc);
 }

 

 



这篇关于你了解Java序列化与反序列化吗的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程