インタビューが不可欠:シリアル化された包括的な分析

序文

日常の開発では、Javaオブジェクトが「直列化可能を実装」していることがよくあると思います。それで、それは何に役立ちますか?この記事では、この知識を次の観点から分析します〜

  • Javaシリアライゼーションとは何ですか?
  • なぜシリアル化が必要なのですか?
  • シリアル化の目的
  • 一般的に使用されるJavaシリアライゼーションAPI
  • シリアライゼーションの使用
  • 最下層をシリアル化
  • 日々の開発の連載に関する注意事項
  • 一般的なインタビューの質問のシリアル化

1. Javaシリアライゼーションとは何ですか?

  • シリアライゼーション:Javaオブジェクトをバイトシーケンスに変換するプロセス
  • 逆シーケンス:バイトシーケンスをJavaオブジェクトに復元するプロセス

次に、なぜシリアル化が必要なのでしょうか。

JavaオブジェクトはJVMのヒープメモリで実行されています。JVMが停止すると、JVMの寿命が突然停止します。

これらのオブジェクトをディスクに保存したり、JVMの停止後にネットワーク経由で別のリモートマシンに転送したりする場合はどうでしょうか。ディスクハードウェアはJavaオブジェクトを認識せず、バイナリマシン言語のみを認識するため、これらのオブジェクトをバイト配列に変換する必要があります。このプロセスはシリアル化です〜

たとえを言えば、大都市を漂流する農民として、動くことは当たり前のことです。デスクを移動すると、テーブルが大きすぎて小さいドアを通過できないため、テーブルを分解して移動する必要があります。テーブルを分解するプロセスは、シリアル化です。デスクを復元(インストール)するプロセスは、逆シリアル化です。

3、シリアル化の目的

シリアル化により、プログラムの実行とは関係なくオブジェクトを存在させることができます。

  • 1)シリアル化メカニズムにより、オブジェクトをハードディスクに保存できるため、メモリの負荷が軽減されますが、永続的な役割も果たします。

たとえば、Webサーバーのセッションオブジェクトでは、100,000人のユーザーが同時にアクセスすると、100,000人のセッションオブジェクトが存在する可能性があり、メモリが消化できないため、Webコンテナが最初にハードディスクへの参照をシリアル化します。使用後、ハードディスクに保存されたオブジェクトをメモリに復元します。

  • 2)シリアライゼーションメカニズムにより、ネットワーク上でのJavaオブジェクトの送信はもはやファンタジーではなくなりました。

Dubboを使用してサービスフレームワークをリモートで呼び出す場合、送信されたJavaオブジェクトのSerializableインターフェイスを実装する必要があります。つまり、Javaオブジェクトをシリアル化します。これにより、オブジェクトをネットワーク上で送信できるようになります。

4、一般的に使用されるJavaシリアライゼーションAPI

java.io.ObjectOutputStream
java.io.ObjectInputStream
java.io.Serializable
java.io.Externalizable
复制代码

シリアライズ可能なインターフェース

Serializableインターフェースは、メソッドやフィールドのないマークアップインターフェースです。このインターフェイスが実装されると、このクラスのオブジェクトがシリアル化可能であることを示します。

public interface Serializable {
}
复制代码

外部化可能なインターフェース

ExternalizableはSerializableインターフェースを継承し、2つの抽象メソッドwriteExternal()とreadExternal()も定義します。開発者がExternalizableを使用して直列化と直列化復元を実装する場合は、writeExternal()メソッドとreadExternal()メソッドを書き直す必要があります

public interface Externalizable extends java.io.Serializable {
    void writeExternal(ObjectOutput out) throws IOException;
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
复制代码

java.io.ObjectOutputStream类

オブジェクト出力ストリームを表し、そのwriteObject(Object obj)メソッドは、指定されたobjオブジェクトパラメータをシリアル化して、結果のバイトシーケンスをターゲット出力ストリームに書き込むことができます。

java.io.ObjectInputStream

オブジェクト入力ストリームを表します。そのreadObject()メソッドは、入力ストリームからバイトシーケンスを読み取り、それをオブジェクトに逆シリアル化して、最後に返します。

5、シリアル化の使用

シリアライゼーションを使用するには?シリアライゼーションの使用に関するいくつかの重要なポイントを見てください。

  • エンティティークラスを宣言してSerializableインターフェースを実装する
  • ObjectOutputStreamクラスのwriteObjectメソッドを使用してシリアル化を実現する
  • ObjectInputStreamクラスのreadObjectメソッドを使用して、逆シリアル化を実現する

Studentクラスを宣言し、Serializableを実装する

public class Student implements Serializable {

    private Integer age;
    private String name;

    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
复制代码

ObjectOutputStreamクラスのwriteObjectメソッドを使用して、Studentオブジェクトをシリアル化します。

Studentオブジェクトの値を設定した後、ファイルに書き込みます。つまり、シリアル化です。

ObjectOutputStream objectOutputStream = new ObjectOutputStream( new FileOutputStream("D:\\text.out"));
Student student = new Student();
student.setAge(25);
student.setName("jayWei");
objectOutputStream.writeObject(student);

objectOutputStream.flush();
objectOutputStream.close();
复制代码

シリアライズのキュートな外観を見てみましょう。test.outファイルの内容は次のとおりです(UltraEditで開いた場合)。

ObjectInputStreamクラスのreadObjectメソッドを使用して、逆シリアル化を実行し、生徒オブジェクトを再生成します。

次にtest.outファイルを読み取り、それをStudentオブジェクトに逆シリアル化します

ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\text.out"));
Student student = (Student) objectInputStream.readObject();
System.out.println("name="+student.getName());
复制代码

6、下をシリアル化する

シリアライズ可能

Serializableインターフェースは、メソッドやフィールドのない単なる空のインターフェースですが、実装時にオブジェクトをシリアル化できるほど魔法のように見えるのはなぜですか?

public interface Serializable {
}
复制代码

Serializableの機能を確認するには、上記のデモのStudentオブジェクトを削除し、Serializableインターフェースを実装します。

シリアル化プロセス中に例外がスローされました。スタック情報は次のとおりです:

Exception in thread "main" java.io.NotSerializableException: com.example.demo.Student
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.example.demo.Test.main(Test.java:13)
复制代码

スタック情報を見ると、以下のような大きな発見があります〜

元の最下層は次のとおりです 。ObjectOutputStreamがシリアル化されると、シリアル化されるオブジェクトのタイプはStringですか?配列?列挙型?それでもシリアル化可能である場合は、NotSerializableExceptionをスローします。つまり、 Serializableは実際には単なるサインであり、シリアル化されたサインです

writeObject(オブジェクト)

シリアル化メソッドはwriteObjectです。上記のデモに基づいて、コアメソッド呼び出しチェーンの波形を分析しましょう〜(興味がある場合は、デバッグに移動してこのメ​​ソッドを確認することもお勧めします)

writeObjectは直接writeObject0()メソッドを呼び出し、

public final void writeObject(Object obj) throws IOException {
    ......
    writeObject0(obj, false);
    ......
}
复制代码

writeObject0主な実装は異なるタイプのオブジェクトであり、異なるメソッドを呼び出してシリアル化されたデータを書き込みます。オブジェクトがSerializableインターフェースを実装している場合は、writeOrdinaryObject()メソッドを呼び出します〜

private void writeObject0(Object obj, boolean unshared)
        throws IOException
    {
    ......
   //String类型
    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);
   //Serializable实现序列化接口
    } 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());
        }
    }
    ......
复制代码

writeOrdinaryObject()は、最初にwriteClassDesc(desc)を呼び出し、このクラスの生成された情報を書き込んでから、writeSerialDataメソッドを呼び出してシリアル化されたデータを書き込みます

    private void writeOrdinaryObject(Object obj,
                                     ObjectStreamClass desc,
                                     boolean unshared)
        throws IOException
    {
            ......
            //调用ObjectStreamClass的写入方法
            writeClassDesc(desc, false);
            // 判断是否实现了Externalizable接口
            if (desc.isExternalizable() && !desc.isProxy()) {
                writeExternalData((Externalizable) obj);
            } else {
                //写入序列化数据
                writeSerialData(obj, desc);
            }
            .....
    }
复制代码

writeSerialData()は、シリアル化されたオブジェクトのフィールドデータを書き込みます

  private void writeSerialData(Object obj, ObjectStreamClass desc)
        throws IOException
    {
        for (int i = 0; i < slots.length; i++) {
            if (slotDesc.hasWriteObjectMethod()) {
                   //如果被序列化的对象自定义实现了writeObject()方法,则执行这个代码块
                    slotDesc.invokeWriteObject(obj, this);
            } else {
                // 调用默认的方法写入实例数据
                defaultWriteFields(obj, slotDesc);
            }
        }
    }
复制代码

defaultWriteFields()メソッド。クラスの基本データ型データを取得するには、基になるバイトコンテナーに直接書き込み、クラスのobj型データを取得するには、再帰的にwriteObject0()メソッドを呼び出し、データを書き込みます。

   private void defaultWriteFields(Object obj, ObjectStreamClass desc)
        throws IOException
    {   
        // 获取类的基本数据类型数据,保存到primVals字节数组
        desc.getPrimFieldValues(obj, primVals);
        //primVals的基本类型数据写到底层字节容器
        bout.write(primVals, 0, primDataSize, false);

        // 获取对应类的所有字段对象
        ObjectStreamField[] fields = desc.getFields(false);
        Object[] objVals = new Object[desc.getNumObjFields()];
        int numPrimFields = fields.length - objVals.length;
        // 获取类的obj类型数据,保存到objVals字节数组
        desc.getObjFieldValues(obj, objVals);
        //对所有Object类型的字段,循环
        for (int i = 0; i < objVals.length; i++) {
            ......
              //递归调用writeObject0()方法,写入对应的数据
            writeObject0(objVals[i],
                             fields[numPrimFields + i].isUnshared());
            ......
        }
    }
复制代码

7.毎日の開発のシリアル化に関するいくつかのポイント

  • 静的静的変数と一時的に変更されたフィールドはシリアル化されません
  • serialVersionUIDの問題
  • シリアル化されたクラスのメンバー変数がオブジェクト型の場合、そのオブジェクト型のクラスはシリアル化を実装する必要があります
  • 子クラスはシリアル化され、親クラスはシリアル化されておらず、親クラスのフィールドがありません

静的静的変数と一時的に変更されたフィールドはシリアル化されません

静的静的変数と一時的に変更されたフィールドはシリアル化されません。波を分析する例を見てみましょう〜Studentクラスは、クラス変数の性別と一時的に変更されたフィールドの専門分野を追加します

public class Student implements Serializable {

    private Integer age;
    private String name;

    public static String gender = "男";
    transient  String specialty = "计算机专业";

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    @Override
    public String toString() {
        return "Student{" +"age=" + age + ", name='" + name + '\'' + ", gender='" + gender + '\'' + ", specialty='" + specialty + '\'' +
                '}';
    }
    ......
复制代码

Studentオブジェクトを出力し、ファイルにシリアル化してから、静的変数の値を変更してから、逆シリアル化して、逆シリアル化されたオブジェクトを出力します〜

演算結果:

序列化前Student{age=25, name='jayWei', gender='男', specialty='计算机专业'}
序列化后Student{age=25, name='jayWei', gender='女', specialty='null'}
复制代码

比較結果は次のとおりです。

  • 1)セックスの順序が明らかにされる前に静的変数のデシリアライズがなった後、変更後のプログラムで連載、「男性」 '女性のA、明らかに、この静的プロパティはシリアル化されていません。実際、静的(静的)メンバー変数はクラスレベルに属し、オブジェクトのシリアル化は〜なので、シリアル化できません
  • 2)シリアライゼーションおよびデシリアライゼーションプロセスの後、専門分野変数の値が「コンピューターメジャー」から空に変更されます。なぜですか?実際、transientキーワードが原因で、変更されたフィールドがファイルにシリアル化されないようにすることができます。逆シリアル化された後、transientフィールドの値は初期値に設定されます。たとえば、int型の値は0、オブジェクトタイプに設定されます。初期値はnullに設定されます。

serialVersionUIDの問題

serialVersionUIDは、シリアル化されたバージョン番号IDを意味します。実際、Serializableインターフェースを実装するすべてのクラスには、シリアル化されたバージョン識別子を表す静的変数があるか、デフォルトで1Lに等しいか、オブジェクトのハッシュコードに等しいです。

private static final long serialVersionUID = -6384871967268653799L;
复制代码

serialVersionUIDの用途は何ですか?

JAVAシリアライゼーションのメカニズムは、クラスのserialVersionUIDを判断して、バージョンが一貫しているかどうかを確認することです。逆シリアル化中に、JVMは着信バイトストリームのserialVersionUIDを対応するローカルエンティティクラスのserialVersionUIDと比較します。同じである場合、逆シリアル化は成功します。同じでない場合、InvalidClassExceptionがスローされます。

次に、それを確認し、Studentクラスを変更してから、操作を逆シリアル化します

Exception in thread "main" java.io.InvalidClassException: com.example.demo.Student;
local class incompatible: stream classdesc serialVersionUID = 3096644667492403394,
local class serialVersionUID = 4429793331949928814
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1876)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1745)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2033)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1567)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:427)
	at com.example.demo.Test.main(Test.java:20)
复制代码

ログスタックの例外情報からわかるように、ファイルストリームのクラスは現在のクラスパスのクラスとは異なり、それらのserialVersionUIDは同じではないため、逆シリアル化によりInvalidClassException例外がスローされます。では、実際にStudentクラスを変更する必要があり、正常に逆シリアル化したい場合はどうでしょうか。serialVersionUIDの値を手動で指定できます。通常は1Lに設定するか、またはエディターIDEに生成させます。

private static final long serialVersionUID = -6564022808907262054L;
复制代码

実際、Ali開発マニュアルでは、シリアル化クラスに新しい属性を追加する必要がある場合、serialVersionUIDフィールドを変更できないようにする必要があります〜

シリアル化されたクラスのメンバー変数がオブジェクト型の場合、そのオブジェクト型のクラスはシリアル化を実装する必要があります

Teacher型のメンバー変数をStudentクラスに追加します。Teacherはシリアル化インターフェイスを実装していません。

public class Student implements Serializable {
    
    private Integer age;
    private String name;
    private Teacher teacher;
    ...
}
//Teacher 没有实现
public class Teacher  {
......
}
复制代码

シリアル化されると、NotSerializableExceptionが報告されます。

Exception in thread "main" java.io.NotSerializableException: com.example.demo.Teacher
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.example.demo.Test.main(Test.java:16)
复制代码

実際、これは前のセクションの基礎となるソースコード分析で確認できます。オブジェクトのシリアル化プロセスでは、オブジェクトタイプフィールドが循環的に呼び出され、再帰的にシリアル化が呼び出されます。つまり、Studentクラスがシリアル化されると、Teacherクラスは次のようになります。シリアル化、ただしTeacherのシリアル化インターフェイスを実装していないため、NotSerializableException例外をスローします。したがって、インスタンス化されたクラスのメンバー変数がオブジェクト型である場合、そのオブジェクト型のクラスをシリアル化する必要があります

サブクラスがSerializableを実装している場合、親クラスがSerializableインターフェースを実装していない場合、親クラスはシリアル化されません。

サブクラスStudentはSerializableインターフェースを実装し、親クラスUserはSerializableインターフェースを実装しません

//父类实现了Serializable接口
public class Student  extends User implements Serializable {

    private Integer age;
    private String name;
}
//父类没有实现Serializable接口
public class User {
    String userId;
}

Student student = new Student();
student.setAge(25);
student.setName("jayWei");
student.setUserId("1");

ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\text.out"));
objectOutputStream.writeObject(student);

objectOutputStream.flush();
objectOutputStream.close();

//反序列化结果
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\text.out"));
Student student1 = (Student) objectInputStream.readObject();
System.out.println(student1.getUserId());
//output
/** 
 * null
 */
复制代码

逆シリアル化の結果から、親クラスの属性値が失われていることがわかります。したがって、サブクラスはSerializableインターフェースを実装しますが、親クラスがSerializableインターフェースを実装しない場合、親クラスはシリアル化されません。

8つのシリアル化された一般的なインタビューの質問

  • 基本的なシリアル化はどのように実装されますか?
  • シリアル化するときに、特定のメンバーがシリアル化されないようにするにはどうすればよいですか?
  • JavaのSerializableとExternalizableの違いは何ですか
  • serialVersionUIDの用途は何ですか?
  • シリアル化プロセスをカスタマイズしたり、Javaのデフォルトのシリアル化プロセスをオーバーライドしたりできますか?
  • Javaシリアル化中に、どの変数がシリアル化されないのですか?

1.基礎となるシリアル化はどのように実装されていますか?

この記事の6番目のセクションでは、Serializableキーワードの役割、シリアル化フラグ、ソースコードでの役割など、この質問に答えることができます。また、基本型に直接書き込む、obj型を取得するなど、writeObjectのいくつかのコアメソッドに答えることもできます。再帰的に書かれたデータ、はは〜

2.シリアル化するときに、特定のメンバーがシリアル化されないようにするにはどうすればよいですか?

これは、transientキーワードを使用して変更できます。これにより、変更されたフィールドがファイルにシリアル化されないようにすることができます。逆シリアル化された後、transientフィールドの値は初期値に設定されます。たとえば、int型の値は0、オブジェクトタイプに設定されます。初期値はnullに設定されます。

3. Javaでは、SerializableとExternalizableの違いは何ですか

ExternalizableはSerializableを継承し、writeExternal()メソッドとreadExternal()メソッドを提供して、Javaのデフォルトのシリアル化に依存せずにJavaのシリアル化メカニズムを制御できるようにします。Externalizableインターフェースの正しい実装は、アプリケーションのパフォーマンスを大幅に向上させることができます。

4. serialVersionUIDの用途は何ですか?

この記事の7番目のセクションを振り返ると、JAVAシリアライゼーションメカニズムは、クラスのserialVersionUIDを判断して、バージョンが一貫しているかどうかを確認します。逆シリアル化中に、JVMは着信バイトストリームのserialVersionUIDを対応するローカルエンティティクラスのserialVersionUIDと比較します。同じである場合、逆シリアル化は成功します。同じでない場合、InvalidClassExceptionがスローされます。

5.シリアル化プロセスをカスタマイズすることは可能ですか、それともJavaのデフォルトのシリアル化プロセスをオーバーライドできますか?

ええ オブジェクトをシリアル化するには、ObjectOutputStream.writeObject(saveThisObject)を呼び出し、ObjectInputStream.readObject()を使用してオブジェクトを読み取る必要があることは誰もが知っていますが、Java仮想マシンが提供するもう1つのことは、これら2つのメソッドを定義することです。これらの2つのメソッドがクラスで定義されている場合、JVMはデフォルトのシリアル化メカニズムを適用する代わりにこれらの2つのメソッドを呼び出します。同時に、これらのメソッドをプライベートメソッドとして宣言して、継承、書き換え、またはオーバーロードを回避できます。

6. Javaシリアル化中に、どの変数がシリアル化されないのですか?

静的静的変数と一時変更フィールドはシリアル化されません。静的(静的)メンバー変数はクラスレベルに属し、シリアル化はオブジェクト指向です。temporaryキーワードは、フィールドの装飾を変更して、フィールドがファイルにシリアル化されないようにします。

参考と感謝

個人公開口座

  • いいね+注意を払うために良い仲間を書いてみたいです、ありがとう〜
  • 間違って書かれている箇所がある場合は、指摘して感謝してください。
  • 同時に、私は私の公開番号に注意を払うために私の友人を非常に楽しみにしており、後でゆっくりとより良いドライグッズを紹介します〜hee hee

おすすめ

転載: juejin.im/post/5e7f150d51882573b3309ceb