Для сериализации в Java я застрял на самом простом познании — реализовать сериализуемый классSerializbale
Интерфейс в порядке. Я не хочу проводить более глубокие исследования, потому что я могу это использовать.
Но со временем, увидевSerializbale
Все чаще и чаще я проявлял к ней сильный интерес. Время потратить некоторое время на исследования.
01. Начнем с теории
Сериализация Java — это новаторский набор функций, представленных в JDK 1.1 для преобразования объектов Java в массивы байтов для удобного хранения или передачи. После этого массив байтов все еще может быть преобразован обратно в исходное состояние объекта Java.
Идея сериализации заключается в том, чтобы «заморозить» состояние объекта, а затем записать его на диск или передать по сети; идея десериализации заключается в том, чтобы «разморозить» состояние объекта и восстановить доступное Java-объект.
Давайте посмотрим на сериализациюSerializbale
Определение интерфейса:
public interface Serializable {
}
Очевидно, что пустой интерфейс может гарантировать, что реализующий его «объект класса» будет сериализован и десериализован?
02. Чуть более практичный бой
Прежде чем ответить на приведенные выше вопросы, давайте создадим класс (только с двумя полями и соответствующимиgetter/setter
) для сериализации и десериализации.
class Wanger {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Затем создайте тестовый класс, пройдитеObjectOutputStream
Запись «18-летнего Ван Эра» в файл на самом деле является процессом сериализации;ObjectInputStream
Чтение «18-летнего Ван Эра» из файла на самом деле является процессом десериализации.
public class Test {
public static void main(String[] args) {
// 初始化
Wanger wanger = new Wanger();
wanger.setName("王二");
wanger.setAge(18);
System.out.println(wanger);
// 把对象写到文件中
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("chenmo"));){
oos.writeObject(wanger);
} catch (IOException e) {
e.printStackTrace();
}
// 从文件中读出对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("chenmo")));){
Wanger wanger1 = (Wanger) ois.readObject();
System.out.println(wanger1);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Однако из-заWanger
не реализованыSerializbale
интерфейс, поэтому при запуске тестового класса будет выдано исключение.Информация о стеке выглядит следующим образом:
java.io.NotSerializableException: com.cmower.java_demo.xuliehua.Wanger
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at com.cmower.java_demo.xuliehua.Test.main(Test.java:21)
Следуя информации о стеке, давайте посмотримObjectOutputStream
изwriteObject0()
метод. Часть его исходного кода выглядит следующим образом:
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
Это,ObjectOutputStream
При сериализации он будет определять, какой тип объекта будет сериализован, string? множество? перечислить? все ещеSerializable
, если ни один из них, броситьNotSerializableException
.
еслиWanger
ДостигнутоSerializable
интерфейс, вы можете сериализовать и десериализовать.
class Wanger implements Serializable{
private static final long serialVersionUID = -2095916884810199532L;
private String name;
private int age;
}
Как его сериализовать?
отObjectOutputStream
Например, он будет вызываться последовательно при сериализации.writeObject()
→writeObject0()
→writeOrdinaryObject()
→writeSerialData()
→invokeWriteObject()
→defaultWriteFields()
.
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
throws IOException
{
Class<?> cl = desc.forClass();
desc.checkDefaultSerialize();
int primDataSize = desc.getPrimDataSize();
desc.getPrimFieldValues(obj, primVals);
bout.write(primVals, 0, primDataSize, false);
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
desc.getObjFieldValues(obj, objVals);
for (int i = 0; i < objVals.length; i++) {
try {
writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared());
}
}
}
Как его десериализовать?
отObjectInputStream
Например, он будет вызываться по очереди при десериализацииreadObject()
→readObject0()
→readOrdinaryObject()
→readSerialData()
→defaultReadFields()
.
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
throws IOException
{
Class<?> cl = desc.forClass();
desc.checkDefaultSerialize();
int primDataSize = desc.getPrimDataSize();
desc.getPrimFieldValues(obj, primVals);
bout.write(primVals, 0, primDataSize, false);
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
desc.getObjFieldValues(obj, objVals);
for (int i = 0; i < objVals.length; i++) {
try {
writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared());
}
}
}
Я хочу увидеть это, вы должны вдруг осознать "о".Serializable
Причина, по которой интерфейс определяется как пустой, заключается в том, что он служит только идентификатором, сообщая программе, что объект, который его реализует, может быть сериализован, но настоящие операции сериализации и десериализации не требуют его завершения.
03. Еще немного внимания
Давайте сразу к делу,static
иtransient
Измененные поля не сериализуются.
Зачем? Сначала докажем, а потом объясним почему.
Первый вWanger
Добавьте два поля в класс.
class Wanger implements Serializable {
private static final long serialVersionUID = -2095916884810199532L;
private String name;
private int age;
public static String pre = "沉默";
transient String meizi = "王三";
@Override
public String toString() {
return "Wanger{" + "name=" + name + ",age=" + age + ",pre=" + pre + ",meizi=" + meizi + "}";
}
}
Во-вторых, распечатайте объект перед сериализацией и десериализацией в тестовом классе и измените его после сериализации и до десериализации.static
значение поля. Конкретный код выглядит следующим образом:
// 初始化
Wanger wanger = new Wanger();
wanger.setName("王二");
wanger.setAge(18);
System.out.println(wanger);
// 把对象写到文件中
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("chenmo"));){
oos.writeObject(wanger);
} catch (IOException e) {
e.printStackTrace();
}
// 改变 static 字段的值
Wanger.pre ="不沉默";
// 从文件中读出对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("chenmo")));){
Wanger wanger1 = (Wanger) ois.readObject();
System.out.println(wanger1);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
// Wanger{name=王二,age=18,pre=沉默,meizi=王三}
// Wanger{name=王二,age=18,pre=不沉默,meizi=null}
Сравнивая результаты, мы можем обнаружить, что:
1) Перед сериализациейpre
Значение "silent" после сериализации,pre
Значение изменено на «не молчать» после десериализации,pre
Значение «не тихо», а не состояние «молчание» до сериализации.
Зачем? Поскольку сериализация сохраняет состояние объекта, аstatic
Декорированное поле принадлежит состоянию класса, поэтому можно доказать, что сериализация не сохраняетstatic
Измененное поле.
2) Перед сериализациейmeizi
Значение «Ван Сан» после десериализацииmeizi
значениеnull
, вместо состояния "король три" перед сериализацией.
Зачем?transient
Китайское значение слова «временный» (о важности английского языка), оно может предотвратить сериализацию полей в файл после десериализации,transient
Значение поля устанавливается равным начальному значению, напримерint
Начальное значение типа равно 0, начальное значение типа объекта равно 0null
.
Если вы хотите углубиться в исходный код, вы можете перейти кObjectStreamClass
Нашел следующий код в:
private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
Field[] clFields = cl.getDeclaredFields();
ArrayList<ObjectStreamField> list = new ArrayList<>();
int mask = Modifier.STATIC | Modifier.TRANSIENT;
int size = list.size();
return (size == 0) ? NO_FIELDS :
list.toArray(new ObjectStreamField[size]);
}
ВидетьModifier.STATIC | Modifier.TRANSIENT
, стало лучше?
04. Еще немного галантереи
КромеSerializable
Кроме того, Java также предоставляет интерфейс сериализации.Externalizable
(звучит немного неудобно читать).
Есть ли разница между двумя интерфейсами? Просто попробуйте и узнайте.
Во-первых, поставитьWanger
Интерфейс, который реализует классSerializable
заменитьExternalizable
.
class Wanger implements Externalizable {
private String name;
private int age;
public Wanger() {
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Wanger{" + "name=" + name + ",age=" + age + "}";
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
}
выполнитьExternalizable
интерфейсWanger
класс и реализацияSerializable
интерфейсWanger
Классы немного отличаются:
1) Добавлен конструктор без параметров.
использоватьExternalizable
При десериализации вызывается конструктор без аргументов сериализованного класса для создания нового объекта, а затем копируются значения полей сохраненного объекта. В противном случае будет выдано следующее исключение:
java.io.InvalidClassException: com.cmower.java_demo.xuliehua1.Wanger; no valid constructor
at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:150)
at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:790)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1782)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
at com.cmower.java_demo.xuliehua1.Test.main(Test.java:27)
2) Добавлено два новых методаwriteExternal()
иreadExternal()
,выполнитьExternalizable
требует интерфейс.
Затем мы печатаем предварительно сериализованные и десериализованные объекты в тестовом классе.
// 初始化
Wanger wanger = new Wanger();
wanger.setName("王二");
wanger.setAge(18);
System.out.println(wanger);
// 把对象写到文件中
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("chenmo"));) {
oos.writeObject(wanger);
} catch (IOException e) {
e.printStackTrace();
}
// 从文件中读出对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("chenmo")));) {
Wanger wanger1 = (Wanger) ois.readObject();
System.out.println(wanger1);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
// Wanger{name=王二,age=18}
// Wanger{name=null,age=0}
Из результатов вывода поля объекта, полученные после десериализации, стали значениями по умолчанию, то есть состояние объекта до сериализации не было «заморожено».
Зачем? потому что мы неWanger
класс переопределяет конкретныйwriteExternal()
иreadExternal()
метод. Как его переписать?
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = (String) in.readObject();
age = in.readInt();
}
1) позвонитьObjectOutput
изwriteObject()
метод преобразует строковый типname
записать в выходной поток;
2) позвонитьObjectOutput
изwriteInt()
метод преобразует целое числоage
записать в выходной поток;
3) позвонитьObjectInput
изreadObject()
метод преобразует строковый типname
читать во входной поток;
4) позвонитьObjectInput
изreadInt()
метод преобразует строковый типage
читать во входной поток;
Запустите тестовый класс еще раз, и вы увидите, что объект сериализуется и десериализуется нормально.
До сериализации: Вангер{name=Ван Эр,возраст=18} После сериализации: Вангер{name=Ван Эр,возраст=18}
05. Больше десерта
позвольте мне сначала спросить вас, вы знаетеprivate static final long serialVersionUID = -2095916884810199532L;
Этот код работает?
В порядке......
serialVersionUID
Известный как идентификатор сериализации, он является важным фактором, определяющим возможность успешной десериализации объекта Java. При десериализации виртуальная машина Java будетserialVersionUID
с сериализованным классомserialVersionUID
Если они совпадают, их можно десериализовать, иначе будет выдано исключение несогласованной сериализованной версии.
Когда класс реализуетSerializable
После интерфейса IDE напомнит классу, что лучше сгенерировать сериализованный идентификатор, например:
1) Добавьте версию идентификатора сериализации по умолчанию:
private static final long serialVersionUID = 1L。
2) Добавьте случайно сгенерированный неповторяющийся сериализованный идентификатор.
private static final long serialVersionUID = -2095916884810199532L;
3) Добавить@SuppressWarnings
аннотация.
@SuppressWarnings("serial")
Как выбрать?
Во-первых, мы используем второй подход, добавляя случайно сгенерированный идентификатор сериализации в сериализованный класс.
class Wanger implements Serializable {
private static final long serialVersionUID = -2095916884810199532L;
private String name;
private int age;
// 其他代码忽略
}
Затем сериализоватьWanger
возражать против файла.
// 初始化
Wanger wanger = new Wanger();
wanger.setName("王二");
wanger.setAge(18);
System.out.println(wanger);
// 把对象写到文件中
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("chenmo"));) {
oos.writeObject(wanger);
} catch (IOException e) {
e.printStackTrace();
}
В это время мы тихоWanger
Сериализованный идентификатор класса — это кража, хе-хе.
// private static final long serialVersionUID = -2095916884810199532L;
private static final long serialVersionUID = -2095916884810199533L;
Хорошо, готов к десериализации.
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("chenmo")));) {
Wanger wanger = (Wanger) ois.readObject();
System.out.println(wanger);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
Упс! Что-то пошло не так.
java.io.InvalidClassException: local class incompatible: stream classdesc
serialVersionUID = -2095916884810199532,
local class serialVersionUID = -2095916884810199533
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
at com.cmower.java_demo.xuliehua1.Test.main(Test.java:27)
Информация стека исключений сообщает нам, что сериализованный идентификатор, считанный из постоянного файла, несовместим с локальным сериализованным идентификатором и не может быть десериализован.
Тогда, если мы используем третий метод,Wanger
добавить класс@SuppressWarnings("serial")
А аннотации?
@SuppressWarnings("serial")
class Wanger3 implements Serializable {
// 省略其他代码
}
Что ж, давайте снова десериализуем. К сожалению, все равно выдает ошибку.
java.io.InvalidClassException: local class incompatible: stream classdesc
serialVersionUID = -2095916884810199532,
local class serialVersionUID = -3818877437117647968
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
at com.cmower.java_demo.xuliehua1.Test.main(Test.java:27)
Информация о стеке исключений сообщает нам, что локальный идентификатор сериализации равен -3818877437117647968, что по-прежнему несовместимо с идентификатором сериализации, прочитанным в постоянном файле, и не может быть десериализовано. На что это указывает? использовать@SuppressWarnings("serial")
При аннотации аннотация автоматически генерирует случайный идентификатор сериализации для сериализованного класса.
Можно доказать, что возможность десериализации в виртуальной машине Java зависит не только от согласованности пути к классам и кода функции, но также очень важным фактором является согласованность идентификатора сериализации.
То есть, если нет особых требований, можно использовать идентификатор сериализации по умолчанию (1L), который может гарантировать, что десериализация будет успешной, когда код непротиворечив.
class Wanger implements Serializable {
private static final long serialVersionUID = 1L;
// 省略其他代码
}
06. Небольшое резюме
Перед написанием этой статьи я действительно не ожидал: "пустое тело"Serializable
Столько всего нужно изучить!
После написания этой статьи я не мог не вспомнить фразу, сказанную Цао Линьцзином, защитником науки: «Независимо от того, насколько мала проблема в обучении, вы должны обобщать каждый пункт знаний» - это правда и правда Да верно!