java 序列化

本文介绍了Java序列化的基本概念,包括对象的序列化和反序列化过程,并通过具体示例展示了如何实现序列化接口来完成这一过程。此外,还探讨了序列化过程中的一些注意事项,如如何确保单例模式下序列化对象的唯一性。

工作有几个年头了,也想写些东西。把我所了解的java序列化写出来,知识共享。

java序列化:
   当2个java进程进行远程通信的时候,需要发送方把对象转换为字节序列(对象的序列化),然后在网络上传输。
   接收方需要把接受到的字节序列恢复成java对象(对象的反序列化)后,使用该对象。


实现:
   要想实现java的序列化,需要实现(implements)java.io.Serializable接口或者java.io.Externalizable接口
   实现序列化。

简单的例子,创建Customer对象储存本地customer.dat文件中。然后把序列化的字节恢复成Customer对象。

 

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;


public class Customer implements java.io.Serializable {
	
	/**
	 * serialVersionUID.
	 */
	private static final long serialVersionUID = 4186775250920519367L;
	
	/**
	 * customer'name property.
	 */
	private String name;
	
	/**
	 * customer' address property. 
	 */
	private String address;
	
	/**
	 * construct a new Customer instance with name and address argument.
	 * @param name - customer's name.
	 * @param address - customer'address.
	 */
	public Customer(String name,String address) {
		this.name = name;
		this.address = address;
	}

	/**
	 * get customer's name.
	 * @return customer's name.
	 */
	public String getName() {
		return name;
	}

	/**
	 * set customer's name property.
	 * @param name - name.
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * get customer's address.
	 * @return - address.
	 */
	public String getAddress() {
		return address;
	}

	/**
	 * set customer's address.
	 * @param address -address.
	 */
	public void setAddress(String address) {
		this.address = address;
	}
	
	/**
	 * 
	 */
	public boolean equals(Object obj) {
		if(obj == this) {
			return true;
		}
			
		if(!(obj instanceof Customer)) {
			return false;
		}
		
		Customer c = (Customer) obj;
		
		return (name.equals(c.getName()) && address.equals(c.getAddress()));
	}
	
	public int hashCode() {
		return name.hashCode() + address.hashCode();
	}
	
	public String toString() {
		return "[" + name + "," + address + "]";
	}
	
	private void readObject(ObjectInputStream objectinputstream) throws ClassNotFoundException, IOException {
	    objectinputstream.defaultReadObject();
	}
	
	private void writeObject(ObjectOutputStream objectOutputStream) throws IOException {
		objectOutputStream.defaultWriteObject();
	}
	
	public static void main(String[] args) {
		Customer c = new Customer("zc","liaoning.dl");
		//Serialize
		System.out.println("before serialize:" + c);
		ObjectOutputStream oos = null;
		try {
			oos = new ObjectOutputStream(new FileOutputStream("c:\\customer.dat"));
			oos.writeObject(c);
		} catch (FileNotFoundException e) {
			//ignore
		} catch (IOException e) {
			//ignore
		} finally {
			try {
				if(oos != null) {
					oos.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		//deserialize
		ObjectInputStream ois = null;
		Customer c2 = null;
		try {
			ois = new ObjectInputStream(new FileInputStream("c:\\customer.dat"));
			c2 = (Customer) ois.readObject();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} finally {
			try {
				if(ois != null) {
					ois.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		System.out.println("deserialize:" + c2);
		System.out.println("compare result:" + c.equals(c2));
	}

}

 

执行结果:

before serialize:[zc,liaoning.dl]
deserialize:[zc,liaoning.dl]
compare result:true

 上面就是简单的序列化例子。

 

 

从上面例子可以看出序列化前后对象比较是equal,真正意义上2个对象,只是内容相同而已。如何能让序列化后的对象绝对相等(==比较)?

也就是说单例如何序列化后的对象是一个?(用java中反射获得的单例对象,其实也是一个新对象)

 

下面是一个最简单的单例模式:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Singleton implements java.io.Serializable {

	private static Singleton instance = new Singleton();

	private Singleton() {}

	public static Singleton getInstance() {
		return instance;
	}

	public static void main(String[] args) {
		Singleton singleton = Singleton.getInstance();
		ObjectOutputStream oos = null;
		try {
			oos = new ObjectOutputStream(new FileOutputStream("c:\\singleton.dat"));
			oos.writeObject(singleton);
		} catch (FileNotFoundException e) {
			//ignore
		} catch (IOException e) {
			//ignore
		} finally {
			try {
				if(oos != null) {
					oos.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		//deserialize
		ObjectInputStream ois = null;
		Singleton singleton2 = null;
		try {
			ois = new ObjectInputStream(new FileInputStream("c:\\singleton.dat"));
			singleton2 = (Singleton) ois.readObject();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} finally {
			try {
				if(ois != null) {
					ois.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		System.out.println(singleton == singleton2);
	}
}
 

执行结果:

false
 

如果在单例中实现如下方法:

	private Object readResolve() {
		return instance;
	}

 执行结果:

true

 

 

readResolve方法把序列化后的对象覆盖,返回readResolve方法值。

 

在实现序列化的过程中,有许多需要注意的地方。

在网络的传输的过程中,有可能把传输的字节码修改。这对于类中对某一个属性有限制(比如年龄<120),修改成非法的字节码。

详细的内容请查看 effective java的第10章序列化中的内容。

 

 

### 序列化的含义 序列化指将堆内存中的 Java 对象数据,通过某种方式存储到磁盘文件中,或者传递给其他网络节点(网络传输),即将对象转化为二进制,用于保存或网络传输。反序列化则是从 IO 流中恢复对象[^1][^4]。 ### 序列化的意义 序列化机制允许将实现序列化Java 对象转换为字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在[^1]。 ### 序列化的使用场景 所有可在网络上传输的对象都必须是可序列化的,比如 RMI(remote method invoke,即远程方法调用),传入的参数或返回的对象都是可序列化的,否则会出错;所有需要保存到磁盘的 Java 对象都必须是可序列化的。通常建议程序创建的每个 JavaBean 类都实现 Serializable 接口[^1]。 ### 序列化实现的方式 #### Serializable 接口 - **普通序列化**:实现 Serializable 接口后,可使用 ObjectOutputStream 将对象写入流,使用 ObjectInputStream 从流中读取对象。 ```java import java.io.*; class Person implements Serializable { String name; int age; public Person(String name, int age) { this.name = name; this.age = age; } } public class SerializationExample { public static void main(String[] args) { Person person = new Person("John", 30); try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) { oos.writeObject(person); } catch (IOException e) { e.printStackTrace(); } try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) { Person deserializedPerson = (Person) ois.readObject(); System.out.println(deserializedPerson.name + " " + deserializedPerson.age); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } } ``` - **成员是引用的序列化**:若类的成员是引用类型,该引用类型也必须是可序列化的,否则当前类无法实现序列化。 - **同一对象序列化多次的机制**:Java 序列化算法会保证同一对象多次序列化时,不会重复序列化对象的内容,而是只记录对象的引用。 - **Java 序列化算法潜在的问题**:例如序列化版本号不匹配、性能问题等。 - **可选的自定义序列化**:可通过实现 `writeObject` 和 `readObject` 方法来自定义序列化和反序列化过程。 #### Externalizable 接口 Externalizable 接口强制自定义序列化,需要实现 `writeExternal` 和 `readExternal` 方法。 ```java import java.io.*; class Employee implements Externalizable { String name; int id; public Employee() {} public Employee(String name, int id) { this.name = name; this.id = id; } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(name); out.writeInt(id); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = (String) in.readObject(); id = in.readInt(); } } ``` ### 序列化版本号 serialVersionUID serialVersionUID 用于确保序列化和反序列化时类的版本一致性。若没有显式定义 serialVersionUID,Java 会根据类的结构自动生成一个。当类的结构发生变化时,可能会导致 serialVersionUID 改变,从而在反序列化时抛出 `InvalidClassException`。建议显式定义 serialVersionUID。 ```java import java.io.Serializable; class MyClass implements Serializable { private static final long serialVersionUID = 1L; // 类的成员和方法 } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值