# 第?11?章?序列化
### 目錄
* [11.1 概述](serialization.html#serialization_general)
* [11.2 歸檔](serialization.html#serialization_archive)
* [11.3 指針和引用](serialization.html#serialization_pointers_and_references)
* [11.4 對象類層次結構的序列化](serialization.html#serialization_class_hierarchies)
* [11.5 優化用封裝函數](serialization.html#serialization_wrappers)
* [11.6 練習](serialization.html#serialization_exercises)
[](http://creativecommons.org/licenses/by-nc-nd/3.0/de/deed.zh) 該書采用 [Creative Commons License](http://creativecommons.org/licenses/by-nc-nd/3.0/de/deed.zh) 授權
## 11.1.?概述
Boost C++ 的 [序列化](http://www.boost.org/libs/serialization/) 庫允許將 C++ 應用程序中的對象轉換為一個字節序列, 此序列可以被保存,并可在將來恢復對象的時候再次加載。 各種不同的數據格式,包括 XML,只要具有一定規則的數據格式,在序列化后都產生一個字節序列。所有 Boost.Serialization 支持的格式,在某些方面來說都是專有的。 比如 XML 格式不同用來和不是用 C++ Boost.Serialization 庫開發的應用程序交換數據。所有以 XML 格式存儲的數據適合于從之前存儲的數據上恢復同一個 C++ 對象。 XML 格式的唯一優點是序列化的 C++ 對象容易理解,這是很有用的,比如說在調試的時候。
## 11.2.?歸檔
Boost.Serialization 的主要概念是歸檔。 歸檔的文件是相當于序列化的 C++ 對象的一個字節流。 對象可以通過序列化添加到歸檔文件,相應地也可從歸檔文件中加載。 為了恢復和之前存儲相同的 C++ 對象,需假定數據類型是相同的。
下面看一個簡單的例子。
```
#include <boost/archive/text_oarchive.hpp>
#include <iostream>
int main()
{
boost::archive::text_oarchive oa(std::cout);
int i = 1;
oa << i;
}
```
* [下載源代碼](src/11.2.1/main.cpp)
Boost.Serialization 提供了多個歸檔類,如 `boost::archive::text_oarchive` 類,它定義在 `boost/archive/text_oarchive.hpp` 文件中。 `boost::archive::text_oarchive`,可將對象序列化為文本流。 上面的應用程序將 `22 serialization::archive 5 1` 寫出到標準輸出流。
可見, `boost::archive::text_oarchive` 類型的對象 `oa` 可以用來像流 (stream) 一樣通過 `<<` 來序列化對象。 盡管如此,歸檔也不能被認為是可以存儲任何數據的常規的流。 為了以后恢復數據,必須以相同的順序使用和先前存儲時用的一樣的數據類型。 下面的例子序列化和恢復了 `int` 類型的變量。
```
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <iostream>
#include <fstream>
void save()
{
std::ofstream file("archiv.txt");
boost::archive::text_oarchive oa(file);
int i = 1;
oa << i;
}
void load()
{
std::ifstream file("archiv.txt");
boost::archive::text_iarchive ia(file);
int i = 0;
ia >> i;
std::cout << i << std::endl;
}
int main()
{
save();
load();
}
```
* [下載源代碼](src/11.2.2/main.cpp)
當 `boost::archive::text_oarchive` 被用來把數據序列化為文本流, `boost::archive::text_iarchive` 就用來從文本流恢復數據。 為了使用這些類,必須包含 `boost/archive/text_iarchive.hpp` 頭文件。
歸檔的構造函數需要一個輸入或者輸出流作為參數。 流分別用來序列化或恢復數據。 雖然上面的應用程序使用了一個文件流,其他流,如 stringstream 流也是可以的。
```
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <iostream>
#include <sstream>
std::stringstream ss;
void save()
{
boost::archive::text_oarchive oa(ss);
int i = 1;
oa << i;
}
void load()
{
boost::archive::text_iarchive ia(ss);
int i = 0;
ia >> i;
std::cout << i << std::endl;
}
int main()
{
save();
load();
}
```
* [下載源代碼](src/11.2.3/main.cpp)
這個應用程序也向標準輸出流寫了 `1`。 然而,與前面的例子相比, 數據卻是用 stringstream 流序列化的。
到目前為止, 原始的數據類型已經被序列化了。 接下來的例子演示如何序列化用戶定義類型的對象。
```
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <iostream>
#include <sstream>
std::stringstream ss;
class person
{
public:
person()
{
}
person(int age)
: age_(age)
{
}
int age() const
{
return age_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & age_;
}
int age_;
};
void save()
{
boost::archive::text_oarchive oa(ss);
person p(31);
oa << p;
}
void load()
{
boost::archive::text_iarchive ia(ss);
person p;
ia >> p;
std::cout << p.age() << std::endl;
}
int main()
{
save();
load();
}
```
* [下載源代碼](src/11.2.4/main.cpp)
為了序列化用戶定義類型的對話, `serialize()` 函數必須定義,它在對象序列化或從字節流中恢復是被調用。 由于 `serialize ()` 函數既用來序列化又用來恢復數據, Boost.Serialization 除了 `<<` 和 `>>` 之外還提供了 `&` 操作符。如果使用這個操作符,就不再需要在 `serialize()` 函數中區分是序列化和恢復了。
`serialize ()` 在對象序列化或恢復時自動調用。它應從來不被明確地調用,所以應生命為私有的。 這樣的話, `boost::serialization::access` 類必須被聲明為友元,以允許 Boost.Serialization 能夠訪問到這個函數。
有些情況下需要添加 `serialize()` 函數卻不能修改現有的類。 比如,對于來自 C++ 標準庫或其他庫的類就是這樣的。
```
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <iostream>
#include <sstream>
std::stringstream ss;
class person
{
public:
person()
{
}
person(int age)
: age_(age)
{
}
int age() const
{
return age_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
friend void serialize(Archive &ar, person &p, const unsigned int version);
int age_;
};
template <typename Archive>
void serialize(Archive &ar, person &p, const unsigned int version)
{
ar & p.age_;
}
void save()
{
boost::archive::text_oarchive oa(ss);
person p(31);
oa << p;
}
void load()
{
boost::archive::text_iarchive ia(ss);
person p;
ia >> p;
std::cout << p.age() << std::endl;
}
int main()
{
save();
load();
}
```
* [下載源代碼](src/11.2.5/main.cpp)
為了序列化那些不能被修改的數據類型,要定義一個單獨的函數 `serialize()`,如上面的例子所示。 這個函數需要相應的數據類型的引用作為它的第二個參數。
如果要被序列化的數據類型中含有不能經由公有函數訪問的私有屬性,事情就變得復雜了。 在這種情況下,該數據列席就需要修改。 在上面應用程序中的 `serialize ()` 函數如果不聲明為 `friend` ,就不能訪問 `age_` 屬性。
不過還好,Boost.Serialization 為許多C++標準庫的類提供了 `serialize()` 函數。 為了序列化基于 C++ 標準庫的類,需要包含額外的頭文件。
```
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/string.hpp>
#include <iostream>
#include <sstream>
#include <string>
std::stringstream ss;
class person
{
public:
person()
{
}
person(int age, const std::string &name)
: age_(age), name_(name)
{
}
int age() const
{
return age_;
}
std::string name() const
{
return name_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
friend void serialize(Archive &ar, person &p, const unsigned int version);
int age_;
std::string name_;
};
template <typename Archive>
void serialize(Archive &ar, person &p, const unsigned int version)
{
ar & p.age_;
ar & p.name_;
}
void save()
{
boost::archive::text_oarchive oa(ss);
person p(31, "Boris");
oa << p;
}
void load()
{
boost::archive::text_iarchive ia(ss);
person p;
ia >> p;
std::cout << p.age() << std::endl;
std::cout << p.name() << std::endl;
}
int main()
{
save();
load();
}
```
* [下載源代碼](src/11.2.6/main.cpp)
這個例子擴展了 `person` 類,增加了 `std::string` 類型的名稱變量,為了序列化這個屬性property, the header file `boost/serialization/string.hpp` 頭文件必須包含,它提供了合適的單獨的 `serialize ()` 函數。
正如前面所提到的, Boost.Serialization 為許多 C++ 標準庫類定義了 `serialize ()` 函數。 這些都定義在和 C++ 標準庫頭文件名稱相對應的頭文件中。 為了序列化 `std::string` 類型的對象,必須包含 `boost/serialization/string.hpp` 頭文件。 為了序列化 `std::vector` 類型的對象,必須包含 `boost/serialization/vector.hpp` 頭文件。 于是在給定的場合中應該包含哪個頭文件就顯而易見了。
還有一個 `serialize ()`函數的參數,到目前為止我們一直忽略沒談到,那就是 `version` 。 如果歸檔需要向前兼容,以支持給定應用程序的未來版本,那么這個參數就是有意義的。 接下來的例子考慮到 `person` 類的歸檔需要向前兼容。由于 `person` 的原始版本沒有包含任何名稱,新版本的 `person` 應該能夠處理不帶名稱的舊的歸檔。
```
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/string.hpp>
#include <iostream>
#include <sstream>
#include <string>
std::stringstream ss;
class person
{
public:
person()
{
}
person(int age, const std::string &name)
: age_(age), name_(name)
{
}
int age() const
{
return age_;
}
std::string name() const
{
return name_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
friend void serialize(Archive &ar, person &p, const unsigned int version);
int age_;
std::string name_;
};
template <typename Archive>
void serialize(Archive &ar, person &p, const unsigned int version)
{
ar & p.age_;
if (version > 0)
ar & p.name_;
}
BOOST_CLASS_VERSION(person, 1)
void save()
{
boost::archive::text_oarchive oa(ss);
person p(31, "Boris");
oa << p;
}
void load()
{
boost::archive::text_iarchive ia(ss);
person p;
ia >> p;
std::cout << p.age() << std::endl;
std::cout << p.name() << std::endl;
}
int main()
{
save();
load();
}
```
* [下載源代碼](src/11.2.7/main.cpp)
`BOOST_CLASS_VERSION` 宏用來指定類的版本號。 上面例子中 `person` 類的版本號設置為1。 如果沒有使用 `BOOST_CLASS_VERSION` , 版本號缺省是0。
版本號存儲在歸檔文件中,因此也就是歸檔的一部份。 當一個特定類的版本號通過 `BOOST_CLASS_VERSION` 宏,在序列化時給定時, `serialize ()` 函數的 `version` 參數被設為給定值存儲在歸檔中。 如果新版本的 `person` 訪問一個包含舊版本序列化對象的歸檔時, `name_` 由于舊版本不含有這個屬性而不能恢復。 通過這種機制,Boost.Serialization 提供了向前兼容歸檔的支持。
## 11.3.?指針和引用
Boost.Serialization 還能序列化指針和引用。 由于指針存儲對象的地址,序列化對象的地址沒有什么意義,而是在序列化指針和引用時,對象的引用被自動地序列化。
```
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <iostream>
#include <sstream>
std::stringstream ss;
class person
{
public:
person()
{
}
person(int age)
: age_(age)
{
}
int age() const
{
return age_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & age_;
}
int age_;
};
void save()
{
boost::archive::text_oarchive oa(ss);
person *p = new person(31);
oa << p;
std::cout << std::hex << p << std::endl;
delete p;
}
void load()
{
boost::archive::text_iarchive ia(ss);
person *p;
ia >> p;
std::cout << std::hex << p << std::endl;
std::cout << p->age() << std::endl;
delete p;
}
int main()
{
save();
load();
}
```
* [下載源代碼](src/11.3.1/main.cpp)
上面的應用程序創建了一個新的 `person` 類型的對象,使用 `new` 創建并賦值給指針 `p` 。 是指針 - 而不是 `*p` - 被序列化了。Boost.Serialization 自動地通過 `p` 的引用序列化對象本身而不是對象的地址。
如果歸檔被恢復, `p` 不必指向相同的地址。 而是創建新對象并將它的地址賦值給 `p` 。 Boost.Serialization 只保證對象和之前序列化的對象相同,而不是地址相同。
由于新式的 C++ 在動態分配內存有關的地方使用 智能指針 (smart pointers) , Boost.Serialization 對此也提供了相應的支持。
```
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/scoped_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <iostream>
#include <sstream>
std::stringstream ss;
class person
{
public:
person()
{
}
person(int age)
: age_(age)
{
}
int age() const
{
return age_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & age_;
}
int age_;
};
void save()
{
boost::archive::text_oarchive oa(ss);
boost::scoped_ptr<person> p(new person(31));
oa << p;
}
void load()
{
boost::archive::text_iarchive ia(ss);
boost::scoped_ptr<person> p;
ia >> p;
std::cout << p->age() << std::endl;
}
int main()
{
save();
load();
}
```
* [下載源代碼](src/11.3.2/main.cpp)
例子中使用了智能指針 `boost::scoped_ptr` 來管理動態分配的 `person` 類型的對象。 為了序列化這樣的指針,必須包含 `boost/serialization/scoped_ptr.hpp` 頭文件。
在使用 `boost::shared_ptr` 類型的智能指針的時候需要序列化,那么必須包含 `boost/serialization/shared_ptr.hpp` 頭文件。
下面的應用程序使用引用替代了指針。
```
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <iostream>
#include <sstream>
std::stringstream ss;
class person
{
public:
person()
{
}
person(int age)
: age_(age)
{
}
int age() const
{
return age_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & age_;
}
int age_;
};
void save()
{
boost::archive::text_oarchive oa(ss);
person p(31);
person &pp = p;
oa << pp;
}
void load()
{
boost::archive::text_iarchive ia(ss);
person p;
person &pp = p;
ia >> pp;
std::cout << pp.age() << std::endl;
}
int main()
{
save();
load();
}
```
* [下載源代碼](src/11.3.3/main.cpp)
可見,Boost.Serialization 還能沒有任何問題地序列化引用。 就像指針一樣,引用對象被自動地序列化。
## 11.4.?對象類層次結構的序列化
為了序列化基于類層次結構的對象,子類必須在 `serialize ()`函數中訪問 `boost::serialization::base_object ()`。 此函數確保繼承自基類的屬性也能正確地序列化。 下面的例子演示了一個名為 `developer` 類,它繼承自類 `person` 。
```
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/string.hpp>
#include <iostream>
#include <sstream>
#include <string>
std::stringstream ss;
class person
{
public:
person()
{
}
person(int age)
: age_(age)
{
}
int age() const
{
return age_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & age_;
}
int age_;
};
class developer
: public person
{
public:
developer()
{
}
developer(int age, const std::string &language)
: person(age), language_(language)
{
}
std::string language() const
{
return language_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & boost::serialization::base_object<person>(*this);
ar & language_;
}
std::string language_;
};
void save()
{
boost::archive::text_oarchive oa(ss);
developer d(31, "C++");
oa << d;
}
void load()
{
boost::archive::text_iarchive ia(ss);
developer d;
ia >> d;
std::cout << d.age() << std::endl;
std::cout << d.language() << std::endl;
}
int main()
{
save();
load();
}
```
* [下載源代碼](src/11.4.1/main.cpp)
`person` 和 `developer` 這兩個類都包含有一個私有的 `serialize ()` 函數, 它使得基于其他類的對象能被序列化。 由于 `developer` 類繼承自 `person` 類, 所以它的 `serialize ()` 函數必須確保繼承自 `person` 屬性也能被序列化。
繼承自基類的屬性被序列化是通過在子類的 `serialize ()` 函數中用 `boost::serialization::base_object ()` 函數訪問基類實現的。 在例子中強制要求使用這個函數而不是 `static_cast` 是因為只有 `boost::serialization::base_object ()` 才能保證正確地序列化。
動態創建對象的地址可以被賦值給對應的基類類型的指針。 下面的例子演示了 Boost.Serialization 還能夠正確地序列化它們。
```
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/export.hpp>
#include <iostream>
#include <sstream>
#include <string>
std::stringstream ss;
class person
{
public:
person()
{
}
person(int age)
: age_(age)
{
}
virtual int age() const
{
return age_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & age_;
}
int age_;
};
class developer
: public person
{
public:
developer()
{
}
developer(int age, const std::string &language)
: person(age), language_(language)
{
}
std::string language() const
{
return language_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & boost::serialization::base_object<person>(*this);
ar & language_;
}
std::string language_;
};
BOOST_CLASS_EXPORT(developer)
void save()
{
boost::archive::text_oarchive oa(ss);
person *p = new developer(31, "C++");
oa << p;
delete p;
}
void load()
{
boost::archive::text_iarchive ia(ss);
person *p;
ia >> p;
std::cout << p->age() << std::endl;
delete p;
}
int main()
{
save();
load();
}
```
* [下載源代碼](src/11.4.2/main.cpp)
應用程序在 `save ()` 函數創建了 `developer` 類型的對象并賦值給 `person*` 類型的指針,接下來通過 `<<` 序列化。
正如在前面章節中提到的, 引用對象被自動地序列化。 為了讓 Boost.Serialization 識別將要序列化的 `developer` 類型的對象,即使指針是 `person*` 類型的對象。 `developer` 類需要相應的聲明。 這是通過這個 `BOOST_CLASS_EXPORT` 宏實現的,它定義在 `boost/serialization/export.hpp` 文件中。 因為 `developer` 這個數據類型沒有指針形式的定義,所以 Boost.Serialization 沒有這個宏就不能正確地序列化 `developer` 。
如果子類對象需要通過基類的指針序列化,那么 `BOOST_CLASS_EXPORT` 宏必須要用。
由于靜態注冊的原因, `BOOST_CLASS_EXPORT` 的一個缺點是可能有些注冊的類最后是不需要序列化的。 Boost.Serialization 為這種情況提供一種解決方案。
```
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/export.hpp>
#include <iostream>
#include <sstream>
#include <string>
std::stringstream ss;
class person
{
public:
person()
{
}
person(int age)
: age_(age)
{
}
virtual int age() const
{
return age_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & age_;
}
int age_;
};
class developer
: public person
{
public:
developer()
{
}
developer(int age, const std::string &language)
: person(age), language_(language)
{
}
std::string language() const
{
return language_;
}
private:
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive &ar, const unsigned int version)
{
ar & boost::serialization::base_object<person>(*this);
ar & language_;
}
std::string language_;
};
void save()
{
boost::archive::text_oarchive oa(ss);
oa.register_type<developer>();
person *p = new developer(31, "C++");
oa << p;
delete p;
}
void load()
{
boost::archive::text_iarchive ia(ss);
ia.register_type<developer>();
person *p;
ia >> p;
std::cout << p->age() << std::endl;
delete p;
}
int main()
{
save();
load();
}
```
* [下載源代碼](src/11.4.3/main.cpp)
上面的應用程序沒有使用 `BOOST_CLASS_EXPORT` 宏,而是調用了 `register_type ()` 模板函數。 需要注冊的類型作為模板參數傳入。
請注意 `register_type ()` 必須在 `save ()` 和 `load ()` 都要調用。
`register_type ()` 的優點是只有需要序列化的類才注冊。 比如在開發一個庫時,你不知道開發人員將來要序列化哪些類。 當然 `BOOST_CLASS_EXPORT` 宏用起來簡單,可它卻可能注冊那些不需要序列化的類型。
## 11.5.?優化用封裝函數
在理解了如何序列化對象之后,本節介紹用來優化序列化過程的封裝函數。 通過這個函數,對象被打上標記允許 Boost.Serialization 使用一些優化技術。
下面例子使用不帶封裝函數的 Boost.Serialization 。
```
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/array.hpp>
#include <iostream>
#include <sstream>
std::stringstream ss;
void save()
{
boost::archive::text_oarchive oa(ss);
boost::array<int, 3> a = { 0, 1, 2 };
oa << a;
}
void load()
{
boost::archive::text_iarchive ia(ss);
boost::array<int, 3> a;
ia >> a;
std::cout << a[0] << ", " << a[1] << ", " << a[2] << std::endl;
}
int main()
{
save();
load();
}
```
* [下載源代碼](src/11.5.1/main.cpp)
上面的應用程序創建一個文本流 `22 serialization::archive 5 0 0 3 0 1 2` 并將其寫到標準輸出流中。 使用封裝函數 `boost::serialization::make_array ()` ,輸出可以縮短到 `22 serialization::archive 5 0 1 2` 。
```
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/array.hpp>
#include <boost/array.hpp>
#include <iostream>
#include <sstream>
std::stringstream ss;
void save()
{
boost::archive::text_oarchive oa(ss);
boost::array<int, 3> a = { 0, 1, 2 };
oa << boost::serialization::make_array(a.data(), a.size());
}
void load()
{
boost::archive::text_iarchive ia(ss);
boost::array<int, 3> a;
ia >> boost::serialization::make_array(a.data(), a.size());
std::cout << a[0] << ", " << a[1] << ", " << a[2] << std::endl;
}
int main()
{
save();
load();
}
```
* [下載源代碼](src/11.5.2/main.cpp)
`boost::serialization::make_array ()` 函數需要地址和數組的長度。 由于長度是硬編碼的,所以它不需要作為 `boost::array` 類型的一部分序列化。任何時候,如果 `boost::array` 或 `std::vector` 包含一個可以直接序列化的數組,都可以使用這個函數。 其他一般需要序列化的屬性不能被序列化。
另一個 Boost.Serialization 提供的封裝函數是 `boost::serialization::make_binary_object ()` 。 與 `boost::serialization::make_array ()` 類似,它也需要地址和長度。 `boost::serialization::make_binary_object ()` 函數只是為了用來序列化沒有底層結構的二進制數據,而 `boost::serialization::make_array ()` 是用來序列化數組的。
## 11.6.?練習
You can buy [solutions to all exercises](http://en.highscore.de/shop/index.php?p=boost-solution) in this book as a ZIP file.
1. 開發一個應用程序,能夠將任意個數有名稱,部門和雇員唯一標識號構成的記錄, 序列化到文件并從中恢復。 記錄應該在恢復后在屏幕上顯示。 用樣本記錄測試應用程序。
2. 擴展上面的應用程序,為每個雇員存儲生日。 應用程序應該還可以恢復 在上面的練習創建的的舊版本的記錄。