- 浏览: 441003 次
- 性别:
- 来自: 吉林→上海
文章分类
- 全部博客 (174)
- Java基础-接口与内部类 (5)
- Java基础-流与文件 (10)
- Java基础-JDBC (12)
- Java基础-XML解析 (7)
- Java基础-多线程 (11)
- Java基础-网络 (6)
- Java基础-注解 (5)
- Hibernate 研究记录 (7)
- JavaScript 研究记录 (6)
- ECMAScript 研究记录 (7)
- CSS 研究记录 (9)
- Maven 研究记录 (8)
- SQL 随笔 (5)
- 权限控制和单点登陆 (8)
- Hadoop 研究记录 (6)
- 随想杂谈 (33)
- JAVA EE (4)
- 测试 (3)
- Redis (10)
- Memcached (2)
- MongoDB (6)
- ElasticSearch (3)
最新评论
-
sgq0085:
无尘灬 写道楼主,在吗?可以加你qq咨询一下问题吗?公司禁用Q ...
Shiro通过Redis管理会话实现集群 -
无尘灬:
楼主,在吗?可以加你qq咨询一下问题吗?
Shiro通过Redis管理会话实现集群 -
zhouminsen:
感谢楼主的无私奉献
Shiro通过Redis管理会话实现集群 -
tonny1228:
经测试还是运行在local
远程调用执行Hadoop Map/Reduce -
asdhobby:
楼主,个人感觉每次调用SessionDAO的doUpdate方 ...
Shiro通过Redis管理会话实现集群
Java支持对象序列化(object serialization),可以将任何对象写出到流中,并在之后将其读回。
(1)保存对象数据通过使用ObjectOutputStream对象
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("filename"));
(2)通过writeObject方法保存对象
Employee jack = new Employee("Jack", 5000, 1986, 10, 1); Manager boss = new Manager("Carl", 50000, 1980, 10, 1); out.writeObject(jack); out.writeObject(jack);
(3)通过ObjectInputStream读回对象
ObjectInputStream in = new ObjectInputStream(new FileOutputStream("filename"));
(4)用readObject方法以这些对象被写出的顺序获取它们
Employee e1 = (Bean)in.readObject(); Employee e2 = (Bean)in.readObject();
(5)对所有希望在对象流中存储或恢复的类需要实现Serializable接口,Serializable接口没有任何方法。
class Bean implements Serializable{...}
注意:只有写对象时才使用writeObject/readObject方法,对于基本数据类型值,需要使用writeInt/readInt这样的方法。
对象序列化算法,对象都是用一个序列号(serial number)保存的,这就是这种机制成为对象序列化的原因,用于处理一个对象被对个对象共享作为它们各自属性的一部分。
(1)每一个对象引用都关联一个序列号。
(2)每个对象,第一次遇到时,保存其对象数据到流中。
(3)如果一个对象已经保存过,那么只写出“与之前保存过的序列号为X的对象相同”。在读回对象时,整个过程是反过来的。
(4)对于流中的对象,第一次遇到其序列号时,构建它,并使用流中数据来初始化它,然后记录顺序号和新对象直接的关联。
(5)当遇到“与之前保存过的序列号为X的对象相同”标记时,获取这个顺序号相关联的对象引用。
1.对象序列化文件格式
(1)每个文件以都是以 AC ED 两个字节开始的。
(2)后面紧跟着对象序列化的版本号,目前是 00 05 (Java SE 6.0)
(3)包含的对象序列,顺序是它们的存储顺序
例如,字符串对象"string"被存为 74 00 05 string ,字符串中的Unicode字符被存储过修订过的UTF-8格式
(4)当存储一个对象时,这个对象所属的类也必须存储。
这个类的描述包含:类名;序列化的版本唯一的ID,它是数据域类型和方法签名的指纹;描述序列化方法的表示集;对数据域的描述。
指纹是通过对类、超类、接口、域类型和方法签名按照规范方法排序,然后将安全散列算法(SHA)应用于这些数据而获得的。
SHA是一种可以为较大的信息块提供指纹的快速算法,不论最初的数据块尺寸有多大,这种指纹总是20个字节的数据包。它是通过在数据上执行一个灵巧的位操作序列而创建的。但是序列化机制只是用了SHA码前8个字节。即使这样,当类的数据域方法发生变化时,其指纹跟着变化的可能性还是非常大。
在读入一个对象时,会拿起指纹与它所属的类的当前指纹进行对比,如果他们不匹配,那么就说明这个类的定义在该对象被写出之后发生过变化,因此会产生一个异常。
(5)类标识符存储过程
72
2字节长的类名
类名
8字节长的指纹
1字节长的标示
2字节长的数据域描述符的计数值
数据域描述符
78 (结束标记)
超类类型 (如果没有就是70)
(6)标示字节是由在java.io.ObjectStreamConstants中定义的3位掩码构成的:
// Bit mask for ObjectStreamClass flag. Indicates a Serializable class .defines its own writeObject method.
final static byte SC_WRITE_METHOD = 0x01;
//Bit mask for ObjectStreamClass flag. Indicates class is Serializable.
final static byte SC_SERIALIZABLE = 0x02;
//Bit mask for ObjectStreamClass flag. Indicates class is Externalizable.
final static byte SC_EXTERNALIZABLE = 0x04;
可外部化的类向客户提供了可以导出其实例域的输出的读写方法,我们要写出的这些类实现了Serializable,并且其标志值为02,而可序列化的java.util.Date类定义了它自己的readObject/writeObject方法,并且其标志值为03
(6)每个数据域描述符的格式如下:
1字节长的类型编码
2字节长的域名
域名
类名(如果域是对象)
(8)其中类型编码是下列取值之一:B byte; C char; D double; F float; I int; J Long; L 对象; S short; Z boolean; [ 数组;
当类型编码为L时,域名后面紧跟域的类型。类名和域名字符串不是以字符串编码74开头的,而域类型却是。域类型使用的是与域类型稍有不同的编码机制,即本地方法使用的格式。
e.g.Employee类的薪水域被编码为
D 00 06 salary
e.g.Employee类完整的类描述符:
72 00 08 Emplyee
E6 D2 86 7D AE AC 18 1B 02 指纹和标志
00 03 实例域的数量
D 00 06 salary 实例域的类型和名字
L 00 07 hireDay 实例域的类型和名字
74 00 10 Ljava/util/Date; 实例域的类名-Date
L 00 04 name 实例域的类型和名字
74 00 12 Ljava/lang/String; 实例域的类名-String
78 结束标记
70 无超类
(9)如果在文件中再次需要相同的类描述符,可以使用一种缩写版
71 4字节长的序列号
这个序列号将引用到前面已经描述过的类描述符
对象将被存储为:
73 类描述符 对象数据
e.g.Employee对象如何存储
40 E8 6A 00 00 00 00 00 salary域的值-double
73 hireDay域的值-新对象
71 00 7E 00 08 已有的类java.util.Date
77 08 00 00 00 91 1B 4E B1 80 78 外部存储
74 00 0C Harry Hacker name域的值-String
(10)数组存储格式
75 描述符 4字节长的数组项的数量 数组项
在类描述符中的数组名的格式与本地化方法中使用的格式相同。类名以L开头,以分号结束。
(11)所有对象(包括数组和字符串)和所有类的描述符在存储到输出文件时都被赋予了一个序列号,这个数字以00 7E 00 00 开头。
e.g.上例中Date类的重复引用
71 00 7E 00 08
相同的机制还被用于对象。如果写一个对之前存储过的对象的引用,那么这个引用就会以完全相同的方式存储,即71后面跟随序列号,从上下文中可以清楚的了解这个特殊的序列引用表示的是类描述符还是对象。
(12)空引用被存储为: 70
注意:
(1)对象输出流中包含所有对象的类型和数据域。
(2)每个对象都被赋予一个序列号。
(3)相同的对象重复出现将被存储为对这个对象的序列号的引用。
2.修改默认的序列化机制
(1)transient:跳过序列化。
例如只对本地访问有意义的存储文件句柄或窗口句柄的整数值等数据域永远都不应该被序列化,或数据域属于不可序列化的类,就将标记成transient。
(2)可序列化的类可以定义具有的签名方法:序列化机制为单个类提供了一种方法,向默认的读写行为添加验证或任何其他想要的行为
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
private void writeObject(ObjectOutputStream out) throws IOException, ClassNotFoundException
ObjectInputStream.defaultReadObject()和DataOutputStream.defaultWriteObject()为特殊方法只能在可序列化类中的writeObject方法和readObject方法中调用。
之后属性不在自动序列化,而是调用这些方法。
e.g.java.awt.geom.Point2D.Double不可序列化的处理方法
public class LabeledPoint implements Serializable{ //将Point2D.Double标记成transient private transient Point2D.Double point; //先通过defaultWriteObject方法写出对象描述符和其他属性,再用标准的DataOutput调用写出坐标点 private void writeObject(ObjectOutputStream out) throws IOException, ClassNotFoundException{ out.defaultWriteObject(); out.writeDouble(point.getX()); out.writeDouble(point.getY()); } //先通过defaultWriteObject方法写出对象描述符和其他属性,再用标准的DataOutput调用写出坐标点 private void readObject(ObjectInputStream in) throws IOException{ in.defalutReadObject(); point = new Point2D.Double(out.readDouble(), out.readDouble()); } }
另一个例子是java.util.Date,它提供了自己的readObject和writeObject方法。
注意:readObject和writeObject方法只需要保护和加载他们的数据域(属性),而不需要关心超类数据和任何其他类的信息。
(3)Externalizable接口:定义类自己的机制
实现该接口必须实现两个方法
public void readExternal(ObjectInputStream in) throws IOException, ClassNotFoundException
public void writeExternal(ObjectOutputStream out) throws IOException
与readObject和writeObject方法不同,这两个方法对包括超类数据在内的整个对象的存储和恢复负全责,而序列化机制在流中仅仅是记录该对象所属的类。在读入可外部化的类时,对象流将用默认的构造器创建一个对象,然后调用readExternal方法。
e.g.Employee实现readExternal和writeExternal方法
public void readExternale(ObjectInputStream in) throws IOException{ name = s.readUTF(); salary = s.readDouble(); hireDay = new Date(s.readLong()); } public void writeExternale(ObjectOutputStream out) throws IOException, ClassNotFoundException{ out.writeUTF(name); out.writeDouble(salary); out.writeLong(hireDay.getTime()); }
注意:
(1)同时实现Serialziable和Externalizable接口的情况下,序列化调用readExternal和writeExternal方法,且调用readExternal和writeExternale方法必须有有效的构造。
(2)序列化效率不高的原因是虚拟机必须解析每个对象的结构,如果想要提高性能,并需要读写某个特定类的大量对象,需要使用到Externalizable接口。
(3)readObject和writeObject方法是私有的,并且只能被序列化机制调用。而readExternal和writeExternal方法是公共的。特别是,readExternal方法还潜在的允许修改现有对象的状态。
3.序列化单例和类型安全的枚举
问题描述:
序列化和反序列化对象在实现单例和类型安全的枚举时需要特别注意。
比如下面这样风格的枚举类型:
public class Orientation{ public static final Orientation HORIZONTAL = new Orientation(1); public static final Orientation VERTICAL = new Orientation(2); private int value; private Orientation(int v){ value = v; } }
因为构造器是私有的,所以不可能创造出超出Orientation.HORIZONTAL和Orientation.VERTICAL之外的对象
但是当写出一个Orientation,并读回后的实例是Orientation类型的一个全新对象,它与任何预定义的常量都不相同,即使构造器是似有的,序列化机制也可以创建新对象。
Orientation ori = Orientation.HORIZONTAL ObjectOutputStream out = ...; out.writeObject(ori); out.close(); ObjectInputStream in = ...; Orientation savedOri = (Orientation)in.readObject(); //此时 ori != savedOri !!!
解决办法:
readResolve的特殊序列化方法
如果定义了readResolve方法,在对象被序列化之后就会调用它,它必须返回一个对象,而该对象之后会成为readObject的返回值。在上面的情况中,readResolve方法将检查value域并返回恰当的枚举常量。
protected Object readResolve() throws ObjectStreamException{ if(value==1){ return Orientation.HORIZONTAL; }else if(value == 2){ return Orientation.VERTICAL; } return null; }
需要向遗留代码中所有类型安全的枚举以及所有支持单例设计模式的类中添加readResolve方法。
同时存在readExternal和readResolve的情况下先执行readExternale方法后执行readResolve方法。
4. 版本管理
当类可能进行修改,即SHA指纹会变化的情况下,需要读入这个类的不同版本,通过serialVersionUID实现。
e.g.public static final long serialVersionUID = -7920294076289299567L;
当存储名为serialVersionUID的静态常量之后就不再计算指纹,而是直接使用这个值。
注意:
(1)当属性名字匹配而类型不匹配的情况下,对象流不会尝试将一种类型转换成另一种类型,因为两个对象不兼容。
(2)如果流中的对象具有在当前版本中所没有的属性时,对象流会忽略这些额外的数据。
(3)如果当前版本具有在流化对象中所没有的属性,那么新添加的属性将会被设置成它们的默认值(对象 null, 数字 0, boolean false)
(4)如果需要所有的属性都初始化为非null的值,那么要在readObject方法中实现额外的代码去修订版本不兼容问题。
5.为克隆使用序列化
通过序列化对现有对象实现深拷贝(deep copy):直接将对象序列化到输出流中,然后将其读回。
package clone; import java.io.Serializable; import java.util.Date; public class Employee extends SerialCloneable{ private String name; private Double salary; private Date hireDay; public Employee() { } public Employee(String name, Double salary, Date hireDay) { this.setName(name); this.setSalary(salary); this.setHireDay(hireDay); } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } public Date getHireDay() { return hireDay; } public void setHireDay(Date hireDay) { this.hireDay = hireDay; } @Override public String toString() { return "Employee [name=" + name + ", salary=" + salary + ", hireDay=" + hireDay + "]"; } }
package clone; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class SerialCloneable implements Cloneable, Serializable{ public Object clone(){ try{ ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bout); out.writeObject(this); out.close(); ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); ObjectInputStream in = new ObjectInputStream(bin); Object obj = in.readObject(); in.close(); return obj; }catch(Exception e){ e.printStackTrace(); return null; } } }
package clone; import java.util.Date; public class CloneMain { public static void main(String[] args) { Employee e1 = new Employee("e1", 5000d, new Date()); Employee e2 = (Employee) e1.clone(); e2.setName("e2"); System.out.println(e1); System.out.println(e2); } }
发表评论
-
高效读取大数据文本文件(上亿行数据)
2015-05-29 11:41 11159一.前言 本文是对大数据文本文件读取(按行读取)的 ... -
涉及API
2011-12-20 23:04 1155涉及API 类 java.io.InputStream a ... -
(八) 正则表达式
2011-12-20 23:03 1270正则表达式(regular express ... -
(七) 新I/O
2011-12-20 22:00 1988Java SE 1.4引入大量用于改进输入/输出处理机制的特性 ... -
(六) 文件管理
2011-12-20 21:25 893(1)File类封装了在用户机 ... -
(四) ZIP文档
2011-12-16 21:56 1651ZIP文档(通常)以压缩格式存储了一个或多个文件,每个ZIP文 ... -
(三) 读写二进制数据
2011-12-13 11:20 965DataOutput接口定义了用于二进制格式写数组、字符 ... -
(二) 文本输入与输出
2011-12-12 23:33 1093指定编码格式输入输出文本 InputStream ... -
(一) 流
2011-12-12 21:14 894输入流 : 可以从其中读入一个字节序列的对象称做输入流。 输 ...
相关推荐
在Java编程语言中,对象流(也称为序列化)是一个重要的概念,它允许我们将Java对象转换为字节流,以便可以存储在磁盘上、通过网络传输或保存在持久性存储中。序列化是Java平台的标准特性,由java.io.Serializable...
Java对象的序列化和反序列化是Java编程中一项重要的技术,主要用于将对象的状态转换为字节流,以便存储或在网络上传输。这一过程对于理解Java的IO操作、持久化数据以及实现分布式通信等场景非常关键。 首先,我们来...
5. **对象流**:ObjectInputStream和ObjectOutputStream用于对象的序列化和反序列化,详细阐述了如何实现Serializable接口以及序列化过程中的注意事项。 6. **序列化详解**:解释了如何实现序列化接口,序列化和反...
2. **对象序列化与反序列化**: 序列化是将对象转换为可存储或传输的形式,如字节流或文本,而反序列化则是将这些形式恢复为原始对象。C#中的`System.Runtime.Serialization`命名空间提供了多种序列化工具,如`...
### C#对象序列化与反序列化 #### 1. 对象序列化的介绍 ##### (1).NET支持对象序列化的几种方式 .NET框架提供了多种序列化机制,它们各自有不同的应用场景和特点。 - **二进制序列化**: - **定义**:二进制...
#### 五、可序列化对象的版本管理 **5.1 概览** 由于Java对象可能会随着时间发生变化,因此需要一套机制来管理序列化对象的版本。这样可以确保即使对象的结构发生了变化,仍然能够正确地进行序列化和反序列化。 *...
Java对象的序列化和反序列化...总的来说,Java对象的序列化与反序列化是Java平台中一种强大的工具,它能够帮助开发者在多种场景下有效地处理对象数据。理解并掌握这项技术,对于提升Java应用程序的性能和功能至关重要。
对象序列化和反序列化流
比如,我们可以先序列化对象,然后将得到的序列化数据通过压缩流进行压缩,再写入到文件或发送到网络。在接收端,首先解压缩接收到的数据,然后再进行反序列化,还原出原来的对象。 下面是一个简单的示例,展示如何...
Java对象序列化与反序列化是Java编程中重要的概念,主要应用于数据持久化、网络传输以及存储等场景。本文将详细解析这两个概念及其在实际应用中的实现方式。 **一、Java对象序列化** 1. **定义**: Java对象序列化...
标题“文件流序列化”指的是使用Delphi中的文件流进行对象序列化的技术。文件流是TStream接口的一个具体实现,用于与磁盘上的文件进行交互。这种技术在数据持久化、网络通信以及跨进程通信等场景中十分常见。 ...
在Java编程语言中,字节流、字符流、对象流和序列化是处理数据传输和存储的核心概念。这些概念在程序设计中占据了重要的地位,尤其是在处理输入/输出操作时。让我们逐一深入理解这些主题。 首先,字节流(Byte ...
简单来说序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化,流的概念这里不用多说(就是I/O),我们可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间(注:要想将对象...
在描述中提到,`Student`对象包含了图片,这种情况下二进制序列化可以高效地处理图像数据,因为它是原始字节流。 2. **SOAP序列化(SOAP Serialization)** SOAP序列化将对象转换为符合SOAP标准的XML消息,适合于...
序列化是将一个对象转换为字节流的过程,而反序列化则是将字节流还原为原来的对象。这两个过程在许多场景下都非常有用,比如在网络传输、存储到磁盘或数据库、以及实现跨进程通信时。 首先,我们需要了解`...
1. **概念**:反序列化是将已序列化的字节流恢复为原来的Java对象的过程。 2. **过程**:通过`ObjectInputStream`的`readObject()`方法,可以将字节流还原为与原始对象等效的新对象。 3. **安全问题**:反序列化过程...
序列化是将对象转换为字节流的过程,而反序列化是将字节流转换回对象的过程。 在 Java 中,序列化和反序列化是通过实现 Serializable 接口来实现的。Serializable 接口是一个标记接口,不包含任何方法,但它告诉 ...
在C#编程中,对象序列化和反序列化是至关重要的技术,它们允许开发者将复杂的对象状态转换为可存储或可传输的数据格式,如XML...以上就是关于C#对象序列化与反序列化的基本知识,以及如何在实践中应用这些概念的实例。
8. **序列化与克隆** 虽然序列化和克隆都能创建对象的副本,但二者有本质区别。序列化涉及将对象转换为字节流,而克隆是直接创建一个与原对象具有相同属性的新对象,通常通过实现`Cloneable`接口并覆盖`clone()`...