`

java序列化机制学习及好文积累

    博客分类:
  • java
 
阅读更多

 

转自:http://coolxing.iteye.com/blog/1222783#bc2350923

java序列化机制

博客分类:

[coolxing按: 转载请注明作者和出处, 如有谬误, 欢迎在评论中指正.]

 

java的序列化机制支持将对象序列化为本地文件或者通过网络传输至别处, 而反序列化则可以读取流中的数据, 并将其转换为java对象. 被序列化的类需要实现Serializable接口, 使用ObjectInputStream和ObjectOutputStream进行对象的读写操作.

 

当然, java的序列化机制并非如此简单, 以下是个人总结的一些知识点:

 

1. 对象读取的顺序应该和写入的顺序一致, 而且读取的次数不能超过已写入对象的个数. 比如文件中仅仅存在2个对象, 就不能连续调用3次readObject()方法, 除非调用了reset, skip等对流重新定位的方法.

 

2. java序列化机制针对的是对象, 而不是类. 因此只有非静态成员变量才会被序列化成二进制数据.

 

3. 使用transient关键字修饰的成员变量不会被序列化为二进制数据.

 

4. 将对象序列化为二进制数据, 将二进制数据反序列化为java对象, 这两个操作可能位于不同的应用中, 甚至也可能在不同的计算机上进行. 需要保证这两种场合下都有class文件, 在序列化处和反序列处的class文件需要完成一致, 包括包名.

 

5. 序列化ID的作用. 上面的Person类中定义了序列化ID: private static final long serialVersionUID = 1L;

这是一个非强制定义的静态成员, 如果不定义序列化ID, 那么eclipse会给出一个黄色的警告, 这个警告可以忽略.

考虑这样的情形: Person类定义了序列化ID, 且序列化对象时serialVersionUID的值为1, 而反序列化时serialVersionUID的值不为1, 那么此时将无法反序列化成功. 所以序列化ID可以用来限制某些用户的反序列化.

 

6. 父类的序列化问题. 根据java的对象实例化机制可知, 创建一个子类对象的过程中, 会创建其父类对象, 反序列化也不例外. 如果一个类实现了Serializable接口, 而其父类却没有实现Serializable接口, 那么在反序列化时, 虚拟机会调用父类的无参构造函数创建父类对象, 因此反序列化后父类成员变量的值为调用无参构造函数之后的值. 如果父类既没有实现Serializable接口, 也不存在无参构造函数, 那么在反序列化时将发生程序错误.

假设存在一个没有实现Serializable接口的Male类:

 

Java代码 复制代码 收藏代码
  1. publicclass Male {
  2. private String name;
  3. privateint age;
  4. public Male() {
  5. }
  6. public String getName() {
  7. return name;
  8. }
  9. publicvoid setName(String name) {
  10. this.name = name;
  11. }
  12. publicint getAge() {
  13. return age;
  14. }
  15. publicvoid setAge(int age) {
  16. this.age = age;
  17. }
  18. @Override
  19. public String toString() {
  20. return"Male [name=" + name + ", age=" + age + "]";
  21. }
  22. }
public class Male {
	private String name;
	private int age;

	public Male() {

	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "Male [name=" + name + ", age=" + age + "]";
	}

}

Male的子类MaleStudent则实现了Serializable接口:

 

Java代码 复制代码 收藏代码
  1. publicclass MaleStudent extends Male implements Serializable {
  2. privateint studentID;
  3. public MaleStudent(int studentID) {
  4. super();
  5. this.studentID = studentID;
  6. }
  7. publicint getStudentID() {
  8. return studentID;
  9. }
  10. publicvoid setStudentID(int studentID) {
  11. this.studentID = studentID;
  12. }
  13. @Override
  14. public String toString() {
  15. return"MaleStudent [studentID=" + studentID + "]";
  16. }
  17. }
public class MaleStudent extends Male implements Serializable {
	private int studentID;

	public MaleStudent(int studentID) {
		super();
		this.studentID = studentID;
	}
	
	public int getStudentID() {
		return studentID;
	}

	public void setStudentID(int studentID) {
		this.studentID = studentID;
	}

	@Override
	public String toString() {
		return "MaleStudent [studentID=" + studentID + "]";
	}
}

 

则反序列化MaleStudent对象后, 其name和age属性都发生了改变:

 

Java代码 复制代码 收藏代码
  1. MaleStudent student = new MaleStudent(1);
  2. student.setName("coolxing");
  3. student.setAge(24);
  4. ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(
  5. "male.obj"));
  6. out.writeObject(student);
  7. out.close();
  8. ObjectInputStream in = new ObjectInputStream(new FileInputStream(
  9. "male.obj"));
  10. MaleStudent maleStudent = (MaleStudent) in.readObject();
  11. System.out.println(maleStudent.getName() + ", " + maleStudent.getAge()
  12. + ", " + maleStudent.getStudentID());
MaleStudent student = new MaleStudent(1);
		student.setName("coolxing");
		student.setAge(24);
		ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(
				"male.obj"));
		out.writeObject(student);
		out.close();

		ObjectInputStream in = new ObjectInputStream(new FileInputStream(
				"male.obj"));
		MaleStudent maleStudent = (MaleStudent) in.readObject();
		System.out.println(maleStudent.getName() + ", " + maleStudent.getAge()
				+ ", " + maleStudent.getStudentID());

 

程序的输出为:

 

name = null, age = 0, studentID = 1

可见, 父类属性值都"丢失"了, name和age都是调用Male无参构造函数之后的值. 假设将Male类中的无参构造函数删除, 再加上一个有参的构造函数, 在反序列化时将发生程序错误.
 
7. 自定义序列化和反序列化操作. 如果为了某些特殊需求, 需要自定义序列化和反序列化操作, 只要重写实现了Serializable接口的类中的writeObject()和readObject()方法即可, 典型的应用场景是对密码之类的敏感成员进行加密:
Java代码 复制代码 收藏代码
  1. publicclass User implements Serializable {
  2. privatestaticfinallong serialVersionUID = 1L;
  3. private String userName;
  4. private String passWord;
  5. public User(String userName, String passWord) {
  6. this.userName = userName;
  7. this.passWord = passWord;
  8. }
  9. public User() {
  10. }
  11. privatevoid writeObject(ObjectOutputStream out) {
  12. try {
  13. PutField field = out.putFields();
  14. field.put("userName", userName);
  15. System.out.println("加密前: passWord = " + passWord);
  16. // 模拟加密
  17. passWord = passWord + "1";
  18. System.out.println("加密后: passWord = " + passWord);
  19. field.put("passWord", passWord);
  20. out.writeFields();
  21. } catch (IOException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. privatevoid readObject(ObjectInputStream in) {
  26. try {
  27. GetField field = in.readFields();
  28. userName = (String) field.get("userName", "");
  29. passWord = (String) field.get("passWord", "");
  30. System.out.println("读取的原始passWord = " + passWord);
  31. // 模拟解密
  32. passWord = passWord.substring(0, passWord.length() - 1);
  33. System.out.println("解密后的passWord = " + passWord);
  34. } catch (Exception e) {
  35. e.printStackTrace();
  36. }
  37. }
  38. }
public class User implements Serializable {
	private static final long serialVersionUID = 1L;
	private String userName;
	private String passWord;

	public User(String userName, String passWord) {
		this.userName = userName;
		this.passWord = passWord;
	}

	public User() {
	}

	private void writeObject(ObjectOutputStream out) {
		try {
			PutField field = out.putFields();
			field.put("userName", userName);
			System.out.println("加密前: passWord = " + passWord);
			// 模拟加密
			passWord = passWord + "1";
			System.out.println("加密后: passWord = " + passWord);
			field.put("passWord", passWord);
			out.writeFields();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	private void readObject(ObjectInputStream in) {
		try {
			GetField field = in.readFields();
			userName = (String) field.get("userName", "");
			passWord = (String) field.get("passWord", "");
			System.out.println("读取的原始passWord = " + passWord);
			// 模拟解密
			passWord = passWord.substring(0, passWord.length() - 1);
			System.out.println("解密后的passWord = " + passWord);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
程序的输出为:
加密前: passWord = 1987810
加密后: passWord = 19878101
读取的原始passWord = 19878101
解密后的passWord = 1987810
User [userName=coolxing, passWord=1987810]
 
8. 重复存储问题. 如果将同一个对象多次写入文件, 会有怎样的结果?
Java代码 复制代码 收藏代码
  1. File file = new File("user.obj");
  2. User user = new User("coolxing", "1987810");
  3. ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.obj"));
  4. out.writeObject(user);
  5. System.out.println(file.length());
  6. // 改变userName的值后再次将user对象存入文件
  7. user.setUserName("min");
  8. out.writeObject(user);
  9. System.out.println(file.length());
  10. ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.obj"));
  11. User userFromFile1 = (User) in.readObject();
  12. User userFromFile2 = (User) in.readObject();
  13. System.out.println(userFromFile1.toString());
  14. System.out.println(userFromFile2.toString());
File file = new File("user.obj");
		User user = new User("coolxing", "1987810");
		ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.obj"));
		out.writeObject(user);
		System.out.println(file.length());
		// 改变userName的值后再次将user对象存入文件
		user.setUserName("min");
		out.writeObject(user);
		System.out.println(file.length());
		
		ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.obj"));
		User userFromFile1 = (User) in.readObject();
		User userFromFile2 = (User) in.readObject();
		System.out.println(userFromFile1.toString());
		System.out.println(userFromFile2.toString());

程序的输出结果为:

 

109

114

User [userName=coolxing, passWord=1987810]

User [userName=coolxing, passWord=1987810]

程序中调用了2次readObject()方法, 且没有出现程序错误, 由此可知确实向文件中写入了2个对象. 第二次写入user对象时, 系统发现文件中已经存在user对象, 将不再存入user对象的内容, 只写入一个引用和一些控制信息, 所以第二次写入user对象后文件的大小增加的很少, 而且userName也没有发生改变.

 

9. 如果一个类中包含非基本数据类型的成员变量, 那么不仅类本身需要实现Serializable接口, 类中的非基本数据类型也需要实现Serializable接口. java的一些核心类, 如String,

基本数据类型的包装类等都已经实现了Serializable接口, 使用的时候可以查看文档.

 

10. 对于包含集合型成员的类来说, 不仅类本身需要现Serializable接口, 集合中所存储的元素也要实现Serializable接口.

那么集合类(List, Set, Map)到底有没有实现Serializable接口呢? 这是我疑惑的地方, 文档中并没有说明. 可以确定的是,

只要集合中的元素是可序列化的, 序列化过程就不会出错.

 

关于java序列化机制的一些其他方面的叙述, 请参见http://www.ibm.com/developerworks/cn/java/j-5things1/index.html?ca=drs-cn-0504

分享到:
评论

相关推荐

    个人Java学习过程中所有学习笔记

    这份"个人Java学习过程中所有学习笔记"包含了作者在学习Java时积累的宝贵经验,旨在帮助初学者或有经验的程序员巩固基础,提升技能。 笔记内容可能涵盖以下几个主要部分: 1. **Java基础**:这部分通常包括Java的...

    Java技术体系与学习路线

    3. **输入输出与文件操作**:学习Java I/O流,理解流的分类及如何进行文件操作,包括读写文件、序列化和反序列化。 4. **多线程**:理解线程的概念,学习如何创建和管理线程,包括同步机制(如synchronized关键字和...

    java学习总结

    6. **I/O流**:Java I/O流用于处理输入输出,包括字节流和字符流,以及对象序列化。掌握InputStream、OutputStream、Reader、Writer及其子类,以及缓冲流、转换流和文件流的使用。 7. **数据库操作**:Java通过JDBC...

    Java SE 基础 练习 Java学习资料

    8. **IO流**:掌握输入输出流的概念,包括文件读写、对象序列化和反序列化,以及使用BufferedReader和PrintWriter进行文本处理。 9. **多线程**:了解线程的基本概念,如何创建和管理线程,同步机制(synchronized...

    多年Java精华积累

    - 数据流操作,如ObjectInputStream和ObjectOutputStream用于对象的序列化和反序列化。 8. **多线程** - 理解线程的基本概念,如何创建和控制线程(Thread类和Runnable接口)。 - 线程同步机制,如synchronized...

    java精华学习笔记

    这些笔记是作者在深入学习Java过程中积累的经验总结,旨在帮助初学者快速理解和掌握Java编程。 1. **Java基础** - **数据类型**: Java分为基本数据类型(如整型、浮点型、字符型、布尔型)和引用数据类型(类、...

    JAVA笔试题积累

    7. **IO流**:掌握字节流和字符流的区别,以及缓冲流、对象序列化、文件操作、管道流等。 8. **反射**:理解反射的概念,如何动态获取类信息,创建对象,调用方法,修改字段值等。 9. **泛型**:泛型的引入是为了...

    成为Java高手的25个学习要点

    Java的核心知识涵盖了集合框架、序列化、流处理、网络编程、多线程、反射、事件处理、NIO、国际化等内容。这些技术是构建复杂应用的基础,对于提升开发效率至关重要。 #### 3. 理解JVM工作原理 深入了解JVM的工作...

    java面试,基础学习笔记

    3. **IO流**:学习输入输出流的使用,包括文件操作、对象序列化和网络通信。 4. **多线程**:掌握线程的创建、同步、通信和生命周期管理,理解线程池的原理。 5. **反射机制**:通过反射可以动态地获取类的信息并...

    JAVA从学习到精通

    这在插件系统、序列化、动态代理等方面有广泛应用。 9. **Java标准库**:掌握Java标准库中的常用类和API,如Math类、Date和Calendar类、Collections工具类等,可以极大地提高编程效率。 10. **JDBC数据库编程**:...

    java学习路径三篇

    6. **IO流**:理解输入输出流的概念,包括字节流和字符流,以及缓冲流、对象序列化和文件操作。 7. **多线程**:学习如何创建和管理线程,掌握同步机制(synchronized关键字、wait/notify、锁对象)以解决并发问题...

    Java编程学习路线图与核心技术详解

    内容概要:本文为 Java 学习路线图,涵盖从基本语法到高级主题的内容,包括数据类型与变量、条件语句、循环结构、面向对象编程、内存管理、集合框架、序列化机制以及使用 Gradle/Maven/Ant 进行项目构建等方面的基础...

    java JAVA入门电子教案

    7. **输入/输出流**:介绍I/O流的概念,包括文件操作、字节流和字符流,以及缓冲区和对象序列化。 8. **集合框架**:Java集合框架是处理对象组的重要工具,包括ArrayList、LinkedList、HashSet、HashMap等容器的...

    java学习

    2.2 输入/输出(I/O):Java提供了丰富的I/O流类库,支持文件操作、网络通信、序列化等多种功能。流是数据传输的通道,分为字节流和字符流两大类。 2.3 多线程:Java内置了对多线程的支持,可以同时执行多个任务,...

    java教程(谭浩强)

    8. **输入输出流**:Java的IO流系统用于读写数据,包括文件操作、字符流和字节流、缓冲流、对象序列化等。 9. **集合框架**:Java集合框架提供了一系列接口和类,如List、Set、Map,用于存储和管理对象。理解它们的...

    java课件完整版

    通过实践,你可以更好地理解Java编程的细节,积累经验,并学会解决实际问题。 总之,这个"java课件完整版"涵盖了从入门到进阶的全面Java知识,无论是初学者还是有经验的开发者,都能从中受益。通过深入学习和实践,...

    郑志远的java学习笔记

    5. **IO流**:讲解了Java的输入/输出流系统,包括文件流、字节流、字符流、对象序列化以及缓冲流的使用,以及NIO(非阻塞I/O)的简介。 6. **多线程**:Java提供了丰富的多线程支持,笔记中会详细介绍线程的创建、...

    java学习计划.7z

    7. **第11周**:十一周可能介绍I/O流,包括文件读写、字符流、字节流、缓冲流以及对象序列化,这些都是处理数据传输的关键技术。 8. **第12周**:十二周可能涉及Java的反射API,这是一个强大的工具,允许程序在运行...

    Java学习基础_总结

    掌握字节流和字符流的区别,以及缓冲流、对象序列化等高级IO概念,可以提高程序的效率和功能。 6. **多线程**:Java内置对多线程的支持,通过Thread类和Runnable接口可以实现并发执行。了解同步机制(如...

Global site tag (gtag.js) - Google Analytics