目录
对象流
// 从文件中按照 id 值查找对应的对象int id = dis . readInt (); // 用户 id-- 用户标识int len = dis . readInt (); // 用户名称的字符数StringBuilder username = new StringBuilder (); // 用户名称for ( int i = 0 ; i < len ; i ++ ) // 一次读取一个字符,然后拼接成完整的字符串username . append ( dis . readChar ());len = dis . readInt ();StringBuilder password = new StringBuilder (); // 用户口令for ( int i = 0 ; i < len ; i ++ )password . append ( dis . readChar ());double balance = dis . readDouble (); // 用户余额if ( dis == id ){res = new Account (); //Account 是一个自定义类型,用于封装账户信息res . setUsername ( username . toString ());res . setPassword ( password . toString ());res . setBalance ( balance );break ;}
SUN提供了ObjectInputStream/ObjectOutputStream可以直接将Object写入或读出
这里实际上还有针对8种简单类型及其包装类的操作方法,以及针对String类型的操作方法
- readObject():Object
- writeObject(Object):void
// 简单写法,应该使用 try/finally 结构或者使用 try/resource 的写法Date now = new Date ();ObjectOutputStream oos = new ObjectOutputStream (new BufferedOutputStream ( new FileOutputStream ( "data3.txt" )));oos . writeObject ( now );oos . close ();ObjectInputStream ois = new ObjectInputStream (new BufferedInputStream ( new FileInputStream ( "data3.txt" )));Object obj = ois . readObject ();if ( obj != null && obj instanceof Date ) {Date dd = ( Date ) obj ;System . out . println ( dd );}ois . close ();
- NotSerializableException
对象序列化【简单来说就是将对象可以直接转换为二进制数据流】/对象的反序列化【可以将二进制数据流转换为对象】,这一般依靠JVM实现,编程中只做声明对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象。对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点,其他程序一旦获取到这种二进制流,都可以将这种二进制流恢复成原来的Java对象
User user = new User ();user . setId ( 100L );user . setUsername ( "zhangsan" );user . setPassword ( "123456" );ObjectOutputStream oos = new ObjectOutputStream ( newFileOutputStream ( "users.data" ));oos . writeObject ( user );oos . close ();
4、读取对象
ObjectInputStream ois = new ObjectInputStream(new
FileInputStream ( "users.data" ));Object temp = ois . readObject ();if ( temp != null && temp instanceof User ) {User user = ( User ) temp ;System . out . println ( user );}ois . close ();
编码细节
class User implements Serializable
2、Serializable接口是标志接口,没有需要实现的方法,所有的序列化和反序列化操作由VM负责实现。Externalizable接口定义为public interface Externalizable extends java.io.Serializable,这个接口中包含两个方法需要实现writeExternal自定义实现对象的序列化,readExternal自定义实现对象的反序列化。除非特殊需求一般不使用Externalizable接口,因为没有必要自定义
class User implements Externalizable {@Overridepublic void writeExternal ( ObjectOutput out ) throws IOException {// 写出对象的操作System . out . println ( " 现在需要写出对象 :" + this );out . writeLong ( this . id );out . writeUTF ( this . username );out . writeUTF ( this . password );}@Overridepublic void readExternal ( ObjectInput in ) throws IOException ,ClassNotFoundException {// 读取对象的操作this . id = in . readLong ();this . username = in . readUTF ();this . password = in . readUTF ();}public User (){}
3、类型转换问题:
Object temp = ois . readObject ();if ( temp != null && temp instanceof User ) {User user = ( User ) temp ;System . out . println ( user );}
4、private static final long serialVersionUID = 6889840055394511246L
如果不添加序列号,则会有警告信息,但是不是错误信息
一般选择Add generated serial version ID会生成一个在项目中永不重复的的序列版本编号
InvalidClassException
class User implements Serializable {private transient String password ; //transient 用于声明该属性不支持序列化操作
class User implements Serializable {private String username ;private transient String password ;private Role role ; // 因为 Role 没有实现序列化接口,所以写出 user 对象时会有报错NotSerializableException 。处理报错的方法有: 1 、可以给 Role 类定义添加序列接口。 2 、在 role属性上添加 transient 表示这个属性不序列化处理
6、读文件的判断:读取文件时可以通过EOFException异常来判断文件读取结束
try (ObjectInputStream ois = new ObjectInputStream ( newFileInputStream ( "users.data" ));) {while ( true ) {try {Object temp = ois . readObject ();if ( temp != null && temp instanceof User ) {User user = ( User ) temp ;System . out . println ( user );}} catch ( EOFException ex ) { // 这个异常是用于判断文件结尾,所以不需要进行处理break ;}}}
已经向文件中写入数据后,继续追加存储,则读取数据会出现StreamCorruptedException
- ObjectOutputStream oos=new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("users.data",true)));
// 向文件中追加新数据 true
// 首先读取数据,然后再统一写入Object [] arr = new Object [ 100 ];int counter = 0 ;File ff = new File ( "user.data" );if ( ff . exists ()) {ObjectInputStream ois = new ObjectInputStream ( newFileInputStream ( "user.data" ));while ( true ) {try {Object temp = ois . readObject ();arr [ counter ++ ] = temp ;} catch ( EOFException e ) {break ;}}ois . close ();}// 追加数据User user = new User ();user . setId ( 299L ); user . setUsername ( "name299" );user . setPassword ( "pwd299" );arr [ counter ++ ] = user ;// 然后统一写出到文件中ObjectOutputStream oos = new ObjectOutputStream ( newFileOutputStream ( "user.data" ));for ( int i = 0 ; i < arr . length ; i ++ ) {Object temp = arr [ i ];if ( temp != null )oos . writeObject ( temp );}oos . close ();
class User implements Externalizable {
private Long id ; // 要求自增长private String username ;private String password ;private InputStream is ; // 输入流不能被序列@Overridepublic void writeExternal ( ObjectOutput out ) throws IOException {//writeObject 方法时调用is . close (); // 释放资源out . writeLong ( this . id );out . writeUTF ( this . username );}@Overridepublic void readExternal ( ObjectInput in ) throws IOException ,ClassNotFoundException { //readObject 方法时执行this . id = in . readLong ();this . username = in . readUTF ();is = new FileInputStream ( "ddd.txt" ); // 重新获取资源}}
序列化总结
- 当父类实现了Serializable接口的时候,所有的子类都能序列化
- 子类实现了Serializable接口,父类没有,父类中的属性不能被序列化(不报错,但是数据会丢失)
- 如果序列化的属性是对象,对象必须也能序列化,否则会报错
- 反序列化的时候,如果对象的属性有修改或则删减,修改的部分属性会丢失,但是不会报错
- 在反序列化的时候serialVersionUID被修改的话,会反序列化失败
- 在存Java环境下使用Java的序列化机制会支持的很好,但是在多语言环境下需要考虑别的序列化机制,比如xml、json或protobuf等
serialVersionUID值是用于确保类序列化与反序列化的兼容性问题的,如果序列化和反序列化过程中这
- Java 序列化只是针对对象的属性的传递,至于方法和序列化过程无关
- 当一个父类实现了序列化,那么子类会自动实现序列化,不需要显示实现序列化接口,反过来,子类实现序列化,而父类没有实现序列化则序列化会失败---即序列化具有传递性
- 当一个对象的实例变量引用了其他对象,序列化这个对象的时候会自动把引用的对象也进行序列化(实现深度克隆)
- 当某个字段被申明为 transient 后,默认的序列化机制会忽略这个字段
- 被申明为 transient 的字段,如果需要序列化,可以添加两个私有方法writeObject和readObject或者实现Externalizable接口
对象克隆
在Java中所有的类都是缺省的继承自Java语言包中的Object类的,查看它的源码
native 方法是非 Java 语言实现的代码,供 Java 程序调用的,因为 Java 程序是运行在 JVM 虚拟机上面的,要想访问到比较底层的与操作系统相关的就没办法了,只能由靠近操作系统的语言来实现。
克隆的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的状态就靠clone方法。那么把这个对象的临时属性一个一个的赋值给新new的对象不也行嘛?可以是可以,但是一来麻烦不说,通过上面的源码都发现了clone是一个native方法,在底层实现的。
两种不同的克隆方法,浅克隆ShallowClone和深克隆DeepClone。浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制
浅克隆
public class Student implements Cloneable{
private int number ;public int getNumber () {return number ;}public void setNumber ( int number ) {this . number = number ;}@Overridepublic Object clone () {Student stu = null ;try {stu = ( Student ) super . clone ();} catch ( CloneNotSupportedException e ) {e . printStackTrace ();}return stu ;}}
调用
Student stu1 = new Student();
stu1 . setNumber ( 12345 );Student stu2 = ( Student ) stu1 . clone ();
深克隆
原型模式
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。优点: 1 、性能提高。2、逃避构造函数的约束。缺点: 1 、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一 定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。2、必须实现
public abstract class Shape implements Cloneable {private String id ;protected String type ;abstract void draw ();public String getType (){return type ;}public String getId () {return id ;}public void setId ( String id ) {this . id = id ;}public Object clone () {Object clone = null ;try {clone = super . clone ();} catch ( CloneNotSupportedException e ) {e . printStackTrace ();}return clone ;}}
扩展抽象类的实体类
public class Rectangle extends Shape {
public Rectangle(){
type = "Rectangle";
}
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
创建一个类获取实体类,并把它们存储在一个 Hashtable 中
public class ShapeCache {
private static Hashtable<String, Shape> shapeMap = new Hashtable<String, Shape>();
public static Shape getShape(String shapeId) {
Shape cachedShape = shapeMap.get(shapeId);
return (Shape) cachedShape.clone();
}
// 对每种形状都运行操作创建该形状shapeMap.put(shapeKey, shape);
public static void loadCache() {
Circle circle = new Circle();
circle.setId("1");
shapeMap.put(circle.getId(),circle);
}
}
public class PrototypePatternDemo {
public static void main(String[] args) {
ShapeCache.loadCache();
Shape clonedShape = (Shape) ShapeCache.getShape("1");
System.out.println("Shape : " + clonedShape.getType());
}
}