이 글에서는 주로 Java객체직렬화와 역직렬화에 대해 소개합니다. 편집자는 꽤 좋다고 생각해서 지금 공유하고 참고용으로 제공하겠습니다. 편집기를 따라가서 살펴보겠습니다.
이전 기사에서는 DataOutputStream 스트림을 사용하여 각 속성 값은 스트림에 하나씩 출력되며, 그 반대의 경우도 마찬가지입니다. 우리 생각에는 이 동작이 정말 번거롭다고 생각합니다. 특히 이 객체에 속성 값이 많을 때 더욱 그렇습니다. 이를 바탕으로 Java의 객체 직렬화 메커니즘은 이 작업을 매우 잘 해결할 수 있습니다. 이번 글에서는 자바 객체 직렬화에 대해 간략히 소개한다.
1. 간단한 코드 구현
//简单定义一个Student类 public class Student { private String name; private int age; public Student(){} public Student(String name,int age){ this.name = name; this.age=age; } public void setName(String name){ this.name = name; } public void setAge(int age){ this.age = age; } public String getName(){ return this.name; } public int getAge(){ return this.age; } //重写toString @Override public String toString(){ return ("my name is:"+this.name+" age is:"+this.age); } }
//main方法实现了将对象写入文件并读取出来 public static void main(String[] args) throws IOException{ DataOutputStream dot = new DataOutputStream(new FileOutputStream("hello.txt")); Student stuW = new Student("walker",21); //将此对象写入到文件中 dot.writeUTF(stuW.getName()); dot.writeInt(stuW.getAge()); dot.close(); //将对象从文件中读出 DataInputStream din = new DataInputStream(new FileInputStream("hello.txt")); Student stuR = new Student(); stuR.setName(din.readUTF()); stuR.setAge(din.readInt()); din.close(); System.out.println(stuR); }출력 결과: my name is:walker age is:21
public static void main(String[] args) throws IOException, ClassNotFoundException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt")); Student stuW = new Student("walker",21); oos.writeObject(stuW); oos.close(); //从文件中读取该对象返回 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt")); Student stuR = (Student)ois.readObject(); System.out.println(stuR); }파일을 쓸 때는 writeObject 문 하나만 사용하고, 읽을 때는 readObject 문 하나만 사용합니다. 그리고 Student의 set 및 get 메소드는 더 이상 사용되지 않습니다. 아주 간단하지 않나요? 구현 세부 사항은 다음에 소개됩니다.
2. 직렬화를 위한 기본 알고리즘
인터페이스 java.io.Serialized;를 구현해야 합니다. 이는 마크 인터페이스이며 그럴 필요가 없습니다. 무엇이든 구현하십시오. 우리의 ObjectOutputStream 스트림은 객체 정보를 바이트로 변환할 수 있는 스트림입니다. 생성자 는 다음과 같습니다.
public ObjectOutputStream(OutputStream out)즉, 모든 바이트 스트림은 매개변수로 전달될 수 있으며 호환됩니다. All byte 운영. writeObject 및 readObject 메서드는 객체의 직렬화 및 역직렬화를 구현하기 위해 이 스트림에 정의됩니다. 물론 나중에 자세히 소개할 클래스에서 이 두 가지 메서드를 구현하여 직렬화 메커니즘을 사용자 정의할 수도 있습니다. 여기서는 전체 직렬화 메커니즘만 이해하면 됩니다. 모든 개체 데이터의 복사본은 하나만 저장됩니다. 동일한 개체가 다시 나타나는 경우 해당 일련 번호만 저장됩니다. 아래에서는 두 가지 특별한 상황을 통해 그의 기본 알고리즘을 직관적으로 경험해 보겠습니다.
3. 두 가지 특별한 예
public class Student implements Serializable { String name; int age; Teacher t; //另外一个对象类型 public Student(){} public Student(String name,int age,Teacher t){ this.name = name; this.age=age; this.t = t; } public void setName(String name){this.name = name;} public void setAge(int age){this.age = age;} public void setT(Teacher t){this.t = t;} public String getName(){return this.name;} public int getAge(){return this.age;} public Teacher getT(){return this.t;} } public class Teacher implements Serializable { String name; public Teacher(String name){ this.name = name; } } public static void main(String[] args) throws IOException, ClassNotFoundException { Teacher t = new Teacher("li"); Student stu1 = new Student("walker",21,t); Student stu2 = new Student("yam",22,t); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt")); oos.writeObject(stu1); oos.writeObject(stu2); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt")); Student stuR1 = (Student)ois.readObject(); Student stuR2 = (Student)ois.readObject(); if (stuR1.getT() == stuR2.getT()) System.out.println("相同对象"); }결과는 매우 명확합니다. 동일한 개체가 출력됩니다. main 함수에 두 개의 Student 유형 객체를 정의했지만, 둘 다 아래의 두 번째 특별한 예를 살펴보세요.
public class Student implements Serializable { String name; Teacher t; } public class Teacher implements Serializable { String name; Student stu; } public static void main(String[] args) throws IOException, ClassNotFoundException { Teacher t = new Teacher(); Student s =new Student(); t.name = "walker"; t.stu = s; s.name = "yam"; s.t = t; ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt")); oos.writeObject(t); oos.writeObject(s); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt")); Teacher tR = (Teacher)ois.readObject(); Student sR = (Student)ois.readObject(); if(tR == sR.t && sR == tR.stu)System.out.println("ok"); }
输出的结果是ok,这个例子可以叫做:循环引用。从结果我们可以看出来,序列化之前两个对象存在的相互的引用关系,经过序列化之后,两者之间的这种引用关系是依然存在的。其实按照我们之前介绍的判断算法来看,首先我们先序列化了teacher对象,因为他内部引用了student的对象,两者都是第一次遇到,所以将两者序列化到流中,然后我们去序列化student对象,发现这个对象以及内部的teacher对象都已经被序列化了,于是只保存对应的序列号。读取的时候根据序列号恢复对象。
四、自定义序列化机制
综上,我们已经介绍完了基本的序列化与反序列化的知识。但是往往我们会有一些特殊的要求,这种默认的序列化机制虽然已经很完善了,但是有些时候还是不能满足我们的需求。所以我们看看如何自定义序列化机制。自定义序列化机制中,我们会使用到一个关键字,它也是我们之前在看源码的时候经常遇到的,transient。将字段声明transient,等于是告诉默认的序列化机制,这个字段你不要给我写到流中去,我会自己处理的。
public class Student implements Serializable { String name; transient int age; public String toString(){ return this.name + ":" + this.age; } } public static void main(String[] args) throws IOException, ClassNotFoundException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt")); Student stu = new Student(); stu.name = "walker";stu.age = 21; oos.writeObject(stu); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt")); Student stuR = (Student)ois.readObject(); System.out.println(stuR); }
输出结果:walker:0
我们不是给age字段赋初始值了么,怎么会是0呢?正如我们上文所说的一样,被transient修饰的字段不会被写入流中,自然读取出来就没有值,默认是0。下面看看我们怎么自己来序列化这个age。
//改动过的student类,main方法没有改动,大家可以往上看 public class Student implements Serializable { String name; transient int age; private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); oos.writeInt(25); } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); age = ois.readInt(); } public String toString(){ return this.name + ":" + this.age; } }
输出结果:walker:25
结果既不是我么初始化的21,也不是0,而是我们在writeObject方法中写的25。现在我们一点一点看看每个步骤的意义。首先,要想要实现自定义序列化,就需要在该对象定义的类中实现两个方法,writeObject和readObject,而且格式必须和上面贴出来的一样,笔者试过改动方法修饰符,结果导致不能成功序列化。这是因为,Java采用反射机制,检查该对象所在的类中有没有实现这两个方法,没有的话就使用默认的ObjectOutputStream中的这个方法序列化所有字段,如果有的话就执行你自己实现的这个方法。
接下来,看看这两个方法实现的细节,先看writeObject方法,参数是ObjectOutputStream 类型的,这个拿到的是我们在main方法中定义的ObjectOutputStream 对象,要不然它怎么知道该把对象写到那个地方去呢?第一行我们调用的是oos.defaultWriteObject();这个方法实现的功能是,将当前对象中所有没有被transient修饰的字段写入流中,第二条语句我们显式的调用了writeInt方法将age的值写入流中。读取的方法类似,此处不再赘述。
五、版本控制
最后我们来看看,序列化过程的的版本控制问题。在我们将一个对象序列化到流中之后,该对象对应的类的结构改变了,如果此时我们再次从流中将之前保存的对象读取出来,会发生什么?这要分情况来说,如果原类中的字段被删除了,那从流中输出的对应的字段将会被忽略。如果原类中增加了某个字段,那新增的字段的值就是默认值。如果字段的类型发生了改变,抛出异常。在Java中每个类都会有一个记录版本号的变量:static final serivalVersionUID = 115616165165L,此处的值只用于演示并不对应任意某个类。这个版本号是根据该类中的字段等一些属性信息计算出来的,唯一性较高。每次读出的时候都会去比较之前和现在的版本号确认是否发生版本不一致情况,如果版本不一致,就会按照上述的情形分别做处理。
对象的序列化就写完了,如果有什么内容不妥的地方,希望大家指出!
위 내용은 Java 객체 직렬화 및 역직렬화를 위한 자세한 샘플 코드의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!