Maison  >  Article  >  Java  >  Exemple de code détaillé pour la sérialisation et la désérialisation d'objets Java

Exemple de code détaillé pour la sérialisation et la désérialisation d'objets Java

黄舟
黄舟original
2017-03-31 10:37:471352parcourir

Cet article présente principalement la sérialisation et la désérialisation des objets Java L'éditeur pense que c'est plutôt bon, je vais donc le partager avec vous maintenant et le donner comme référence. Suivons l'éditeur et jetons un coup d'œil.

Dans l'article précédent, nous avons introduit l'utilisation de flux de caractères de flux d'octets. À cette époque, nous utilisions le flux DataOutputStream pour sortir un objet dans le flux Each La valeur de l'attribut est sortie dans le flux une par une, et vice versa lors de la lecture. À notre avis, ce comportement est vraiment fastidieux, surtout lorsqu'il y a de nombreuses valeurs d'attribut dans cet objet. Sur cette base, le mécanisme de sérialisation d'objets en Java peut très bien résoudre cette opération. Cet article présente brièvement la sérialisation des objets Java. Le contenu principal est le suivant :

  1. Implémentation de code concise

  2. Algorithme de base pour l'implémentation de la sérialisation

  3. Deux situations particulières

  4. Mécanisme de sérialisation personnalisé

  5. Contrôle de version de sérialisation

1. Implémentation de code simple

Avant de présenter comment utiliser la sérialisation d'objet, jetons un œil à ce que nous avons fait auparavant. Stocke les données d'un type d'objet.

//简单定义一个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);
 }
Résultat de sortie : je m'appelle : Walker, l'âge est : 21


De toute évidence, ce type d'écriture de code est fastidieux, voyons comment l'utiliser. La sérialisation est utilisé pour enregistrer les informations de l'objet.

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);
 }
Lors de l'écriture d'un fichier, une seule instruction est utilisée, writeObject, et lors de la lecture, une seule instruction, readObject, est utilisée. Et les méthodes set et get dans Student ne sont plus utilisées. N'est-ce pas très simple ? Les détails de mise en œuvre sont présentés ensuite.

2. Algorithme de base pour la sérialisation

Dans ce mécanisme, chaque objet correspond à un numéro de série unique, et chaque Lorsque chaque objet est enregistré, il correspond à chaque objet différent en fonction de ce numéro de série. La sérialisation des objets fait référence à l'utilisation du numéro de série de chaque objet à enregistrer et à lire. Tout d'abord, prenons l'exemple de l'écriture d'un objet dans un flux. Pour chaque objet, les informations de base de l'objet seront enregistrées dans le flux lors de sa première rencontre. Si l'objet actuellement rencontré a été enregistré, il ne le sera pas. être utilisé à nouveau. Enregistrez ces informations et enregistrez plutôt le numéro de série de cet objet (car les données n'ont pas besoin d'être enregistrées à plusieurs reprises). En cas de lecture, chaque objet rencontré dans le flux sera directement sorti s'il est rencontré pour la première fois. Si le numéro de série d'un objet est lu, l'objet associé sera trouvé et sorti.


Expliquez quelques points. Si un objet veut être sérialisable, il doit implémenter l'

interface java.io.Seriallessly;. mettre en œuvre n’importe quelle méthode. Notre flux ObjectOutputStream est un flux qui peut convertir les informations d'objet en octets. Le constructeur est le suivant :

public ObjectOutputStream(OutputStream out)
Autrement dit, tous les flux d'octets peuvent être transmis en tant que paramètres, compatibles. avec toutes les opérations sur les octets. Les méthodes writeObject et readObject sont définies dans ce flux pour implémenter des objets de sérialisation et des objets de désérialisation. Bien entendu, nous pouvons également personnaliser le mécanisme de sérialisation en implémentant ces deux méthodes dans la classe, qui seront présentées en détail ultérieurement. Ici, il suffit de comprendre l'ensemble du mécanisme de sérialisation. Une seule copie de toutes les données de l'objet sera enregistrée. Quant au même objet réapparaissant, seul le numéro de série correspondant sera enregistré. Ci-dessous, nous expérimenterons intuitivement son algorithme de base à travers deux situations particulières.

3. Deux exemples particuliers

Regardez d'abord le premier exemple :

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("相同对象");
 }
Le résultat est très évident. l'objet est sorti. Nous avons défini deux objets de type étudiant dans la fonction principale, mais ils

font tous deux référence au même objet enseignant de en interne. Une fois la sérialisation terminée, les deux objets sont désérialisés. En comparant si leurs objets enseignants internes sont la même instance, on peut voir que t est écrit dans le flux lorsque le premier objet étudiant est sérialisé, mais lorsqu'il rencontre l'instance de l'objet enseignant. du deuxième objet étudiant, on constate qu'il a déjà été écrit, il n'est donc plus écrit dans le flux, et seul le numéro de séquence correspondant est enregistré comme référence. Bien entendu, le principe est similaire lors de la désérialisation. Il s’agit du même algorithme de base que nous avons présenté ci-dessus.

Regardez le deuxième exemple spécial ci-dessous :

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,此处的值只用于演示并不对应任意某个类。这个版本号是根据该类中的字段等一些属性信息计算出来的,唯一性较高。每次读出的时候都会去比较之前和现在的版本号确认是否发生版本不一致情况,如果版本不一致,就会按照上述的情形分别做处理。

对象的序列化就写完了,如果有什么内容不妥的地方,希望大家指出!

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn