Добавлен Msey,
опубликован
В этой статье будет подробно разобрана сериализация/десериализация объектов, ее предназначение, форматы и случаи, где какой формат сериализации использовать.
В наше время нередко приходится сталкиваться с такими ситуациями, когда на запоминающем устройстве нужно сохранить необходимую информацию, где оная может принимать вид некоторых структур данных. Структуры данных могут быть представлены как в виде простых объектов с парой параметров, так и сложных в виде многочисленных иерархий объектов. При становлении вопроса о сохранении этих данных, у вас либо какая-то агрессия и зубы скрипят, либо вы вспоминаете то, что здесь было изложено и, по выполнении задачи, продолжаете радоваться жизни.
С этой проблемой призван справится механизм сериализации, где сериализация еть процесс преобразования какой-либо сущности в поток байтов. После преобразования мы можем этот поток байтов или записать на диск в необходимом формате или сохранить его временно в памяти. А при необходимости можно выполнить обратный процесс - десериализацию, то есть, получить из потока байтов ранее сохраненный объект и привести в изначальный вид.
Перечислю несколько распространенных форматов сериализации/десериализации, где каждый из перечисленных имеет свои преимущества.
Форматы и проведенный бенчмарк:
- Xml-сериализация
- Json-сериализация
| + исходный файл меньше весит при больших и малых объемах данных
Идеально подходит для кратковременной сериализации объектов Unity.
- Бинарная-сериализация
| + поддерживает больше типов для де/сериализации
Подходит к долговременной сериализации объектов Unity, где подразумевается сокрытие данных от "взломщиков".
| большим объемом считается количество более 1500 объектов
| малым объемом считается количество менее 500 объектов
| малым объемом считается количество менее 500 объектов
XML сериализация
XML сериализация сериализует только публичные поля и свойства
XML сериализация должна должна на этапе компиляции располагать информацией о типах, которые сериализует
Сериализуемые объекты должны иметь беспараметрический конструктор
Свойства с модификатором readonly не сериализуются
XML сериализация должна должна на этапе компиляции располагать информацией о типах, которые сериализует
Сериализуемые объекты должны иметь беспараметрический конструктор
Свойства с модификатором readonly не сериализуются
Для начала напишем класс-шаблон для сведений об игровом состоянии. Выглядеть он будет следующим образом:
public class GameState
{
public int Money { get; set; }
public int Lives { get; set; }
}
И метод для сериализации наших данных.
GameState state = new GameState() // создаем объект с данными, базируясь на классе-шаблоне
{
Money = 1488,
Lives = 228
};
XmlSerializer serializer = new XmlSerializer(typeof(GameState)); // создаем сериализатор и сообщаем ему о том, какой тип сериализуем
using (TextWriter writer = new StreamWriter(@"C:\Users\Msey\Desktop\GameState.xml")) // если вкратце, то здесь мы создаем модуль, позволяющий записывать символы по указанной директории
{
serializer.Serialize(writer, state); // сериализуем данные
}
Выходные данные будут выглядеть следующим образом:
<?xml version="1.0" encoding="utf-8"?>
<GameState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Money>1488</Money>
<Lives>228</Lives>
</GameState>
Теперь отредактируем наш класс-шаблон так, чтобы убедиться, что приватные поля не сериализуются и что с непараметрическим конструктором все работает в полной мере.
public class GameState
{
public int money;
public int lives;
private int weed;
public GameState()
{
money = 1111;
lives = 2222;
weed = 3333; // не будет сериализоваться, тк поле weed с модификатором доступа private
}
}
Результат очевиден:
<?xml version="1.0" encoding="utf-8"?>
<GameState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<money>1111</money>
<lives>2222</lives>
</GameState>
Если мы заменим пустой конструктор на конструктор с параметрами, то в процессе выполнения программы мы получим ошибку выполнения на этапе создания сериализатора:
public GameState(int i =1111, int j =2222) // так нельзя - конструктор с параметрами
{
money = i;
lives = j;
}
public GameState(int i , int j) // так тоже нельзя - конструктор продолжает быть параметрическим (Ваш кэп ©)
{
money = i;
lives = j;
}
Рассмотрим распространенные атрибуты XML-сериализации:
- [XmlElement]: поле будет сериализовано в качестве элемента
- [XmlAttribute]: поле будет сериализовано в качестве атрибута
- [XmlIgnore]: поле будет пропущено во время сериализации
- [XmlRoot]: задает корневой элемент при сериализации
Рассмотрим атрибут XmlElement:
public class GameState
{
[XmlElement("no_money")]
public int money; // независимо от того, как называется поле, атрибут XmlElement указывает, что представляет элемент строкой ниже и сериализует/десериализует его под именем, указанном в этом атрибуте
[XmlElement("no_lives")]
public int lives;
public GameState()
{
money = 1111;
lives = 2222;
}
}
Результат:
<?xml version="1.0" encoding="utf-8"?>
<GameState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<no_money>1111</no_money>
<no_lives>2222</no_lives>
</GameState>
Также атрибут [XmlElement("no_money")] можно записать в виде [XmlElement(ElementName ="no_money")]. Отличие заключается в том, что в атрибуте [XmlElement(ElementName ="no_money")] можно записать несколько параметров:
[XmlElement(ElementName ="no_money", Namespace = "nmspc")]
Здесь мы добавили пространство имен, и тогда данный элемент в файле принимает следующий вид:
<no_money xmlns="nmspc">1111</no_money>
Теперь обратим внимание на поведение при аттрибуте XmlAttribute:
public class GameState
{
[XmlAttribute("MoneyAttribute")]
public int money;
[XmlAttribute("LivesAttribute")]
public int lives;
public GameState()
{
money = 2222;
lives = 2222;
}
}
Результат:
<?xml version="1.0" encoding="utf-8"?>
<GameState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" MoneyAttribute="2222" LivesAttribute="2222" />
Аттрибут XmlIgnore позволяет игнорировать поле во время сериализации. Заменим класс:
public class GameState
{
[XmlIgnore]
public int money;
[XmlIgnore]
public int lives;
public GameState()
{
money = 1111;
lives = 2222;
}
}
И наблюдаем результат:
<?xml version="1.0" encoding="utf-8"?>
<GameState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
В сгенерированном XML файле отсутствуют элементы.
Атрибут XmlRoot применяется в качестве указания корневого каталога его содержимого. Корневыми элементами могут быть структуры, перечисления, классы и интерфейсы.
[XmlRoot("Root_GameState")]
public class GameState
{
public int money;
public int lives;
public GameState()
{
money = 1111;
lives = 2222;
}
}
Результат:
<?xml version="1.0" encoding="utf-8"?>
<Root_GameState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<money>1111</money>
<lives>2222</lives>
</Root_GameState>
Десериализация для класса GameState не слишком сильно отличается от сериализации:
GameState state;
XmlSerializer deserializer = new XmlSerializer(typeof(GameState));
using (TextReader reader = new StreamReader(@"C:\Users\Msey\Desktop\GameState.xml"))
{
state = (GameState) deserializer.Deserialize(reader);
}
Json сериализация
Сериализация в json имеет схожий с XML процесс сериализации:
GameState state = new GameState();
JsonSerializer serializer = new JsonSerializer();
using (StreamWriter sw = new StreamWriter(@"C:\Users\Msey\Desktop\GameState.json"))
using (JsonWriter writer = new JsonTextWriter(sw))
{
serializer.Serialize(writer, state);
}
Перечислю, пожалуй, основные атрибуты для сериализации в json, которые вам могут пригодиться в дальнейшем:
- [JsonObjectAttribute] - атрибут, который используется для задания поведения класса при сериализации
- [JsonPropertyAttribute] - атрибут, который используется для задания поведения свойств и полей при сериализации
- [JsonIgnore] - атрибут, позволяющий игнорировать поле или свойство при сериализации
Десериализуем теперь json обратно в объект:
GameState state;
JsonSerializer serializer = new JsonSerializer();
using (StreamReader sw = new StreamReader(@"C:\Users\Msey\Desktop\GameState.json"))
using (JsonReader writer = new JsonTextReader(sw))
{
state = (GameState) serializer.Deserialize(writer);
}
Бинарная сериализация
Процесс бинарной сериализации выглядит так:
GameState state = new GameState();
BinaryFormatter formatter = new BinaryFormatter();
using (FileStream stream = new FileStream(@"C:\Users\Msey\Desktop\GameState.dat", FileMode.Create))
{
formatter.Serialize(stream, state);
}
Однако стоит заметить, что конструктор BinaryFormatter не принимает тип сериализуемого объекта, а это значит, что на этапе компиляции formatter не будет ничего знать о нашем классе (типе) с игровым состоянием и в процессе выполнения выдаст ошибку, поэтому добавим к нему атрибут [Serializable]:
[Serializable]
public class GameState
{
public int money;
public int lives;
public GameState()
{
money = 1111;
lives = 2222;
}
}
В случае с десериализацией можно пренебречь атрибутом [Serializable], однако я рекомендую этот атрибут использовать всегда для сериализуемых объектов как для читаемости, так и "кросссериализуемости".
Процесс десериализации:
Процесс десериализации:
GameState state;
BinaryFormatter formatter = new BinaryFormatter();
using (FileStream stream = new FileStream("C:\Users\Msey\Desktop\GameState.dat", FileMode.Open))
{
state = (GameState)formatter.Deserialize(stream);
}
Статья будет дополняться.
`
ОЖИДАНИЕ РЕКЛАМЫ...
Чтобы оставить комментарий, пожалуйста, войдите на сайт.
Отредактирован Msey
Отредактирован Doc
money 999.99
lives 12
money 999.99 Since 1.2
lives 12
Отредактирован Doc
Отредактирован AsagiriGen
Мои причины использования json очень простые: она почти ничем не хуже твоего метода; часто можно не писать парсер, а использовать работающую либу; этот формат известен всем в отличие от своего персонального.
Но твою позицию со своим форматом пониманию.
Отредактирован AsagiriGen