Serializacja
Serializacja – w programowaniu proces przekształcania obiektów, tj. instancji określonych klas, do postaci szeregowej, czyli w strumień bajtów lub postać tekstową (np. XML, JSON) z zachowaniem aktualnego stanu obiektu. Serializowany obiekt może zostać utrwalony w pliku dyskowym, przesłany do innego procesu lub innego komputera poprzez sieć. Procesem odwrotnym do serializacji jest deserializacja. Proces ten polega na odczytaniu wcześniej zapisanego strumienia danych i odtworzeniu na tej podstawie obiektu klasy wraz z jego stanem bezpośrednio sprzed serializacji.
Serializacja służy do zapisu stanu obiektu, a później do odtworzenia jego stanu. Mechanizm ten jest używany między innymi na platformach .NET, Java, PHP, Python, Ruby.
Implementacje serializacji
edytujSerializacja na platformie .NET
edytujIstnieją dwa sposoby serializacji:
- poprzez użycie atrybutu Serializable
- poprzez implementację interfejsu ISerializable
Poniżej znajduje się przykład klasy w języku Delphi dla .NET, której obiekty mogą być serializowane:
type
[Serializable] // Obiekty tej klasy będą mogły być serializowane
TSerializableClass = class
FName: System.&String;
FValue: System.Int32;
[NonSerialized] // Nie serializuj poniższego pola
FNonSerialized: System.Int16;
end;
Poniżej znajduje się przykład w języku C# dla .NET, który serializuje przykład powyżej do pliku:
TSerializableClass Ts = new TSerializableClass();
//Tutaj powinien znajdować się fragment ustawiający wartości wybranych pól
BinaryFormatter binFormat = new BinaryFormatter(); // Tworzymy Formatter
Stream fStream = new FileStream("Przyklad.dat", FileMode.Create, FileAccess.Write, FileShare.None); //tworzymy strumień z prawami do utworzenia nowego pliku
binFormat.Serialize(fStream, Ts); // serializacja naszej klasy do pliku Przyklad.dat
fStream.Close(); // zamknięcie strumienia
Klasa zawiera trzy pola, przy czym jedno z nich – FNonSerialized
– nie będzie serializowane (wskazuje na to atrybut NonSerialized).
Środowisko .NET oferuje trzy podstawowe formaty zapisu (formatery) serializowanych klas: binarny, SOAP oraz XML.
Środowisko uruchomieniowe CLR podczas procesu serializacji tworzy graf obiektu. Każdy obiekt posiada swój identyfikator. Wszystkie obiekty odwołujące się do serializowanego obiektu są wiązane z obiektem głównym.
Formatery
edytujZadaniem formatera jest konwersja obiektu do formatu, w którym zostanie zserializowany obiekt, lub z którego zostanie zdeserializowany. Platforma .NET udostępnia trzy podstawowe formatery: binarny (obiekt zostanie zapisany jako ciąg zero-jedynkowy; formater zdefiniowany w przestrzeni nazw, System.Runtime.Serialization.Formatters.Binary), SOAP (obiekt zostanie zapisany w formacie przypominającym format XML; formater zdefiniowany w przestrzeni nazw System.Runtime.Serialization.Formatters.SOAP), XML (obiekt zostanie zapisany w formacie XML; formater zdefiniowany w przestrzeni nazw System.Xml.Serialization.XmlSerializer). System formaterów jest rozszerzalny.
Różnice między rodzajami serializacji
edytuj- Binary – bardzo prosty sposób zapisu obiektu w strumień. Zaletą jest prostota użycia (wymaga atrybutu [Serializable] + strumień + formatter). Wady – oprócz standardowych informacji, zapisywane są też metadane dotyczące aktualnej wersji platformy .NET oraz specyfikacja typów (zgodna z aktualną wersją platformy).
- SOAP – bardzo prosty sposób zapisu obiektu w strumień. Plik wynikowy przypomina swoją strukturą plik XML, jednak jest on wstępnie formatowany przez platformę .NET, dzięki czemu uzyskujemy prostotę użycia identyczną jak w przypadku BinaryFormatera. Dodatkowym atutem jest brak niepotrzebnych metadanych, dzięki czemu zapis typu SOAP jest w pełni niezależny od platformy. Wada – tak jak w przypadku języka XML serializowany obiekt powinien posiadać „pusty” konstruktor i składać się z publicznie dostępnych pól (pole albo samo powinno być publiczne [public] albo posiadać publiczną [public] właściwość).
- XML – serializacja w pełni zgodna ze standardami języka XML. Każde serializowane pole powinno być public (lub mieć publiczną właściwość), wymagany jest „pusty” konstruktor, dodatkowo zarówno podczas serializacji jak i deserializacji należy podać pełną strukturę pliku XML.
By obiekt mógł być serializowany, musi implementować interfejs Serializable. Dla wygody sporo standardowych klas Javy implementuje ten interfejs, nie ma więc potrzeby wyprowadzać np. własnego obiektu będącego dzieckiem klasy Vector.
W przypadku serializacji obiektu, który agreguje inne obiekty, serializacji ulegnie cała hierarchia obiektów, jednak każdy z nich musi implementować interfejs Serializable. Przykładem może być serializacja wyżej wspomnianego wektora.
Przykład:
package test;
import java.io.Serializable;
public class Account implements Serializable {
private String surname = null;
private String firstname = null;
public Account (String surname, String firstname) {
this.surname = surname;
this.firstname = firstname;
}
public String getFirstname() {
return this.firstname;
}
public String getSurname() {
return this.surname;
}
}
Interfejs Serializable nie wymaga implementacji żadnej metody. Każdy obiekt, który zaimplementował interfejs Serializable, użytkownik może serializować/deserializować do/ze strumienia. Dla powyższego przykładu i serializacji do pliku o nazwie test.ser mogłoby to wyglądać tak jak poniżej:
Account a = new Account("Jan","Nowak");
FileOutputStream fos = null;
ObjectOutputStream oos = null;
/*
* Zapis do strumienia (plikowego, ale może być dowolne)
*/
try {
fos= new FileOutputStream("test.ser"); //utworzenie strumienia wyjściowego
oos = new ObjectOutputStream(fos); //utworzenie obiektu zapisującego do strumienia
oos.writeObject(a); //serializacja obiektu
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// zamykamy strumienie w finally
try {
if (oos != null) oos.close();
} catch (IOException e) {}
try {
if (fos != null) fos.close();
} catch (IOException e) {}
}
Odczytuje się tak samo, ale używa obiektów odczytu, a nie zapisu:
Account b = null;
/*
* Odczyt ze strumienia plikowego (ale może być dowolne)
*/
FileInputStream fis = null;
ObjectInputStream ois = null;
try {
fis = new FileInputStream("test.ser"); //utworzenie strumienia wejściowego
ois = new ObjectInputStream(fis); //utworzenie obiektu odczytującego obiekty ze strumienia
b = (Account) ois.readObject(); //deserializacja obiektu
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
// zasoby zwalniamy w finally
try {
if (ois != null) ois.close();
} catch (IOException e) {}
try {
if (fis != null) fis.close();
} catch (IOException e) {}
}
Zapis klasy do zewnętrznego pliku oraz odczyt z pliku można zrealizować przy użyciu modułu „pickle”.
import pickle
class Animal:
def __init__(self, attr="Horse"):
self.attr = attr
# serializacja – zapis do pliku
def test_serialize():
a = Animal()
print(a.attr)
with open("dump.dat", "wb") as f:
pickle.dump(a, f)
# deserializacja – odczyt z pliku
def test_deserialize():
with open("dump.dat", "rb") as f:
a = pickle.load(f)
print(a.attr)
def test():
test_serialize()
test_deserialize()
if __name__ == "__main__":
test()
Język C++ nie posiada wbudowanego wsparcia dla serializacji. Istnieją jednak przeznaczone do tego biblioteki, np. S11n.
Serializacja w mapowaniu obiektowo-relacyjnym
edytujCzasami w literaturze anglojęzycznej terminem serializacja (serialization) określa się zapis danych reprezentowanych przez dany obiekt do bazy danych[1].