Разкрит е алгоритъмът за сериализация на Java

Сериализацията е процес на запазване на състоянието на обекта в последователност от байтове; десериализацията е процес на възстановяване на тези байтове в жив обект. API за сериализация на Java предоставя стандартен механизъм за разработчиците да се справят с обектната сериализация. В този съвет ще видите как да сериализирате обект и защо понякога е необходима сериализация. Ще научите за алгоритъма за сериализация, използван в Java, и ще видите пример, който илюстрира сериализирания формат на обект. Докато приключите, трябва да имате солидни познания за това как работи алгоритъмът за сериализация и какви обекти са сериализирани като част от обекта на ниско ниво.

Защо се изисква сериализация?

В съвременния свят типично корпоративно приложение ще има множество компоненти и ще бъде разпределено в различни системи и мрежи. В Java всичко е представено като обекти; ако два компонента на Java искат да комуникират помежду си, трябва да има механизъм за обмен на данни. Един от начините да постигнете това е да дефинирате свой собствен протокол и да прехвърлите обект. Това означава, че приемащият край трябва да знае протокола, използван от подателя за пресъздаване на обекта, което би затруднило много разговора с компоненти на трети страни. Следователно трябва да има общ и ефективен протокол за прехвърляне на обекта между компонентите. За тази цел е дефинирана сериализация и компонентите на Java използват този протокол за прехвърляне на обекти.

Фигура 1 показва изглед на високо ниво на комуникация клиент / сървър, където обект се прехвърля от клиента към сървъра чрез сериализация.

Фигура 1. Изглед на високо ниво на сериализация в действие (щракнете, за да увеличите)

Как да сериализираме обект

За да сериализирате обект, трябва да се уверите, че класът на обекта реализира java.io.Serializableинтерфейса, както е показано в Листинг 1.

Листинг 1. Внедряване на сериализуемо

 import java.io.Serializable; class TestSerial implements Serializable { public byte version = 100; public byte count = 0; } 

В Листинг 1 единственото нещо, което трябваше да направите различно от създаването на нормален клас, е да внедрите java.io.Serializableинтерфейса. В Serializableинтерфейса е маркер интерфейс; той не декларира никакви методи. Той казва на механизма за сериализация, че класът може да бъде сериализиран.

След като вече сте направили класа допустим за сериализация, следващата стъпка е всъщност да сериализирате обекта. Това става чрез извикване на writeObject()метода на java.io.ObjectOutputStreamкласа, както е показано в листинг 2.

Листинг 2. Извикване на writeObject ()

 public static void main(String args[]) throws IOException { FileOutputStream fos = new FileOutputStream("temp.out"); ObjectOutputStream oos = new ObjectOutputStream(fos); TestSerial ts = new TestSerial(); oos.writeObject(ts); oos.flush(); oos.close(); } 

Листинг 2 съхранява състоянието на TestSerialобекта във файл, наречен temp.out. oos.writeObject(ts);всъщност започва алгоритъма за сериализация, който от своя страна записва обекта в temp.out.

За да създадете отново обекта от постоянния файл, трябва да използвате кода в Листинг 3.

Листинг 3. Пресъздаване на сериализиран обект

 public static void main(String args[]) throws IOException { FileInputStream fis = new FileInputStream("temp.out"); ObjectInputStream oin = new ObjectInputStream(fis); TestSerial ts = (TestSerial) oin.readObject(); System.out.println("version="+ts.version); } 

В листинг 3 възстановяването на обекта се извършва с oin.readObject()извикването на метода. Извикването на този метод чете в суровите байтове, които преди това сме поддържали, и създава жив обект, който е точно копие на оригиналната графика на обекта. Тъй като readObject()може да чете всеки обект, който може да се сериализира, е необходим глас към правилния тип.

Изпълнението на този код ще се отпечата version=100на стандартния изход.

Сериализираният формат на обект

Как изглежда сериализираната версия на обекта? Не забравяйте, че примерният код в предишния раздел запазва сериализираната версия на TestSerialобекта във файла temp.out. Листинг 4 показва съдържанието на temp.out, показано в шестнадесетично число. (Трябва ви шестнадесетичен редактор, за да видите изхода в шестнадесетичен формат.)

Листинг 4. Шестнадесетична форма на TestSerial

 AC ED 00 05 73 72 00 0A 53 65 72 69 61 6C 54 65 73 74 A0 0C 34 00 FE B1 DD F9 02 00 02 42 00 05 63 6F 75 6E 74 42 00 07 76 65 72 73 69 6F 6E 78 70 00 64 

Ако погледнете отново действителния TestSerialобект, ще видите, че той има само два байтови члена, както е показано в Листинг 5.

Листинг 5. Членове на байта на TestSerial

 public byte version = 100; public byte count = 0; 

Размерът на байтова променлива е един байт и следователно общият размер на обекта (без заглавката) е два байта. Но ако погледнете размера на сериализирания обект в Листинг 4, ще видите 51 байта. Изненада! Откъде идват допълнителните байтове и какво е тяхното значение? Те се въвеждат от алгоритъма за сериализация и са необходими, за да се създаде отново обекта. В следващия раздел ще разгледате подробно този алгоритъм.

Алгоритъм за сериализация на Java

Досега трябва да имате доста добри познания за това как да сериализирате обект. Но как протича процесът под капака? Като цяло алгоритъмът за сериализация прави следното:

  • Той записва метаданните на класа, свързан с екземпляр.
  • Той рекурсивно изписва описанието на суперкласа, докато го намери java.lang.object.
  • След като завърши записването на информацията за метаданните, тя започва с действителните данни, свързани с екземпляра. Но този път започва от най-високия суперклас.
  • Той рекурсивно записва данните, свързани с екземпляра, започвайки от най-малкия суперклас до най-извлечения клас.

Написах различен примерен обект за този раздел, който ще обхване всички възможни случаи. Новият примерен обект за сериализиране е показан в листинг 6.

Листинг 6. Примерен сериализиран обект

 class parent implements Serializable { int parentVersion = 10; } class contain implements Serializable{ int containVersion = 11; } public class SerialTest extends parent implements Serializable { int version = 66; contain con = new contain(); public int getVersion() { return version; } public static void main(String args[]) throws IOException { FileOutputStream fos = new FileOutputStream("temp.out"); ObjectOutputStream oos = new ObjectOutputStream(fos); SerialTest st = new SerialTest(); oos.writeObject(st); oos.flush(); oos.close(); } } 

Този пример е ясен. Той сериализира обект от тип SerialTest, който е получен от parentи има обект контейнер contain,. Сериализираният формат на този обект е показан в листинг 7.

Листинг 7. Сериализирана форма на примерен обект

 AC ED 00 05 73 72 00 0A 53 65 72 69 61 6C 54 65 73 74 05 52 81 5A AC 66 02 F6 02 00 02 49 00 07 76 65 72 73 69 6F 6E 4C 00 03 63 6F 6E 74 00 09 4C 63 6F 6E 74 61 69 6E 3B 78 72 00 06 70 61 72 65 6E 74 0E DB D2 BD 85 EE 63 7A 02 00 01 49 00 0D 70 61 72 65 6E 74 56 65 72 73 69 6F 6E 78 70 00 00 00 0A 00 00 00 42 73 72 00 07 63 6F 6E 74 61 69 6E FC BB E6 0E FB CB 60 C7 02 00 01 49 00 0E 63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E 78 70 00 00 00 0B 

Фигура 2 предлага поглед от високо ниво на алгоритъма за сериализация за този сценарий.

Фигура 2. Очертаване на алгоритъма за сериализация

Нека да разгледаме подробно сериализирания формат на обекта и да видим какво представлява всеки байт. Започнете с информацията за протокола за сериализация:

  • AC ED: STREAM_MAGIC. Посочва, че това е протокол за сериализация.
  • 00 05: STREAM_VERSION. Версията за сериализация.
  • 0x73: TC_OBJECT. Посочва, че това е ново Object.

The first step of the serialization algorithm is to write the description of the class associated with an instance. The example serializes an object of type SerialTest, so the algorithm starts by writing the description of the SerialTest class.

  • 0x72: TC_CLASSDESC. Specifies that this is a new class.
  • 00 0A: Length of the class name.
  • 53 65 72 69 61 6c 54 65 73 74: SerialTest, the name of the class.
  • 05 52 81 5A AC 66 02 F6: SerialVersionUID, the serial version identifier of this class.
  • 0x02: Various flags. This particular flag says that the object supports serialization.
  • 00 02: Number of fields in this class.

Next, the algorithm writes the field int version = 66;.

  • 0x49: Field type code. 49 represents "I", which stands for Int.
  • 00 07: Length of the field name.
  • 76 65 72 73 69 6F 6E: version, the name of the field.

And then the algorithm writes the next field, contain con = new contain();. This is an object, so it will write the canonical JVM signature of this field.

  • 0x74: TC_STRING. Represents a new string.
  • 00 09: Length of the string.
  • 4C 63 6F 6E 74 61 69 6E 3B: Lcontain;, the canonical JVM signature.
  • 0x78: TC_ENDBLOCKDATA, the end of the optional block data for an object.

The next step of the algorithm is to write the description of the parent class, which is the immediate superclass of SerialTest.

  • 0x72: TC_CLASSDESC. Specifies that this is a new class.
  • 00 06: Length of the class name.
  • 70 61 72 65 6E 74: SerialTest, the name of the class
  • 0E DB D2 BD 85 EE 63 7A: SerialVersionUID, the serial version identifier of this class.
  • 0x02: Various flags. This flag notes that the object supports serialization.
  • 00 01: Number of fields in this class.

Now the algorithm will write the field description for the parent class. parent has one field, int parentVersion = 100;.

  • 0x49: Field type code. 49 represents "I", which stands for Int.
  • 00 0D: Length of the field name.
  • 70 61 72 65 6E 74 56 65 72 73 69 6F 6E: parentVersion, the name of the field.
  • 0x78: TC_ENDBLOCKDATA, the end of block data for this object.
  • 0x70: TC_NULL, which represents the fact that there are no more superclasses because we have reached the top of the class hierarchy.

So far, the serialization algorithm has written the description of the class associated with the instance and all its superclasses. Next, it will write the actual data associated with the instance. It writes the parent class members first:

  • 00 00 00 0A: 10, the value of parentVersion.

Then it moves on to SerialTest.

  • 00 00 00 42: 66, the value of version.

The next few bytes are interesting. The algorithm needs to write the information about the contain object, shown in Listing 8.

Listing 8. The contain object

 contain con = new contain(); 

Remember, the serialization algorithm hasn't written the class description for the contain class yet. This is the opportunity to write this description.

  • 0x73: TC_OBJECT, designating a new object.
  • 0x72: TC_CLASSDESC.
  • 00 07: Length of the class name.
  • 63 6F 6E 74 61 69 6E: contain, the name of the class.
  • FC BB E6 0E FB CB 60 C7: SerialVersionUID, the serial version identifier of this class.
  • 0x02: Various flags. This flag indicates that this class supports serialization.
  • 00 01: Number of fields in this class.

Next, the algorithm must write the description for contain's only field, int containVersion = 11;.

  • 0x49: Field type code. 49 represents "I", which stands for Int.
  • 00 0E: Length of the field name.
  • 63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E: containVersion, the name of the field.
  • 0x78: TC_ENDBLOCKDATA.

Next, the serialization algorithm checks to see if contain has any parent classes. If it did, the algorithm would start writing that class; but in this case there is no superclass for contain, so the algorithm writes TC_NULL.

  • 0x70: TC_NULL.

Finally, the algorithm writes the actual data associated with contain.

  • 00 00 00 0B: 11, the value of containVersion.

Conclusion

In this tip, you have seen how to serialize an object, and learned how the serialization algorithm works in detail. I hope this article gives you more detail on what happens when you actually serialize an object.

About the author

Sathiskumar Palaniappan има повече от четири години опит в ИТ индустрията и работи с технологии, свързани с Java, повече от три години. В момента работи като системен софтуерен инженер в Java Technology Center, IBM Labs. Той също има опит в телекомуникационната индустрия.

Ресурси

  • Прочетете спецификацията за сериализация на обект Java. (Спецификацията е PDF.)
  • „Изравнете обектите си: Открийте тайните на API за сериализация на Java“ (Todd M. Greanier, JavaWorld, юли 2000 г.) предлага поглед към гайките на процеса на сериализация.
  • Глава 10 от Java RMI (William Grosso, O'Reilly, октомври 2001 г.) също е полезна справка.

Тази история „Разкритият алгоритъм за сериализация на Java“ първоначално е публикувана от JavaWorld.