blog

Javaのシリアライズとデシリアライズの基本原理

新しい技術の誕生は、一定の理由と背景を持って、例えば、データのJavaネイティブシリアライゼーションは、比較的大規模であり、伝送効率が低く、同時に、多くの人々がデータをシリアライズするXMLを使用する...

Nov 12, 2020 · 7 min. read

シリアライゼーションとデシリアライゼーション

シリアライゼーションとは、メモリに保存されたオブジェクトを、何らかのアルゴリズムを使って永続的な保存や通信に使用できる形に変換するプロセスです。

デシリアライゼーションとは、そのような永続的なストレージまたは通信データを、対応する解析アルゴリズムによってオブジェクトに戻すプロセスのことで、シリアライゼーションの逆です。

シリアライズの逆

フロントエンドがバックエンドのインターフェイスからデータを要求するとき、バックエンドはJSONデータを返す必要があります。これは、バックエンドがJavaヒープ内のオブジェクトをJSONデータにシリアライズしてフロントエンドに渡すことを意味し、フロントエンドはそれらを直接使用したり、自身のニーズに応じてJSオブジェクトにデシリアライズしたりすることができます。

{"id": 1, "name": "long"}RPC リモートコールの間、呼び出し側と呼び出し側はシリアライズとデシリアライズのアルゴリズムについて合意する必要があります。例えば、アプリケーション A はユーザーオブジェクトを JSON データとしてアプリケーション B にシリアライズします。もしアプリケーションBのデシリアライズアルゴリズムがXMLであれば、解析に失敗します。したがって、両者がXMLアルゴリズムを使用することに合意する必要があります。両者がJSONシリアライズアルゴリズムを使用することに合意すれば、JSON標準に基づいてUserオブジェクトを正常に解析できます。

Java での連載

transient

フィールドがJavaのデフォルトのシリアライズ機構によってシリアライズされたくない場合、フィールド

writeObject 和 readObject

カスタムな方法でアドレス・データをシリアライズしたい場合は、このフィールドを使用して、現在のフィールドをシリアライズする必要がないことを示すことができます。

writeObjectメソッドはシリアライズされたデータを書き込むときにリフレクションによって呼び出され、readObjectメソッドはデシリアライズするときにリフレクションによって呼び出されます。

serialVersionUID

カスタムserialVersionUIDがない場合、現在のクラスの情報に基づいて自動的に生成されます。現在のクラスが変更されていない場合、生成されるserialVersionUIDは同じですが、変更された場合、serialVersionUIDは変更され、デシリアライズできなくなります。

Javaシリアライズの原則

objectOutputStream.writeObject(user); writeObject0(obj, false); 最初に呼び出し、次に呼び出し このメソッドの内部では、次のようなコードの一部があります。

ここで、オブジェクトをシリアライズしたいのにそれがSerializableインターフェイスを実装していない場合、NotSerializableExceptionがスローされることがわかります。

まず、ストリームにTC_OBJECTオブジェクト・フラグを書き込み、書き込まれるデータがオブジェクトであることを識別します。

  1. 最初に得られるのはClassDataSlotで、これはオブジェクトをシリアライズする補助的な手段を提供すると考えることができます。

  2. ClassDataSlotは、シリアライズされたオブジェクトがwriteObjectメソッドを実装しているかどうかをチェックするために使用されます。これについては後で説明します。

  3. writeObjectが実装されていれば、リフレクションによって呼び出されます。

  4. 現在のクラスにwriteObjectメソッドがない場合、デフォルトのdefaultWriteFieldsを呼び出してデータを書き込みます。

では、このフィールドはどこで初期化されるのでしょうか? ObjectOutputStreamのwriteObject0メソッドに戻り、それに続くwriteOrderinaryObjectメソッドの呼び出しの中に、次のようなコードがあります。

次に、このコードが呼び出されます。
ObjectStreamClassオブジェクトを生成する過程で、リフレクションによって現在のクラスのメソッドを取得し、メソッド名writeObjectとパラメータObjectOutputStreamに従って、そのようなメソッドがあるかどうかを判断し、あればnullを返し、なければnullを返します。

その後、defaultWriteFields(Object obj, ObjectStreamClass desc)に戻り、引き続き以下を確認します。

  1. 書き込まれるデータの現在の長さを取得します。
  2. リフレクションを通じて、現在のデータの値を取得するには、ObjectStreamClassのdescこのオブジェクトは、Objectは、基本型など、この時点でUserオブジェクトを取得する可能性がありますので、デフォルト値の値は空です、それだけでデータの特定のフィールドを書き込むためにここにあるので
  3. リフレクションを通じて、現在のオブジェクトのすべての値を取得します。
  4. 特定の値を書き込むために1つずつwriteObject0を呼び出し、最初の呼び出しはIntegerであるため、ラッパークラスであり、IntegerはNumberを継承し、NumberクラスはSerializableを実装しているので、それは回数に到着するUserオブジェクトと同じプロセスを通過し、呼び出しに値を取り出すためにアンラップされます。
  5. その後、Stringの書き込みに入り、再びwriteObject0を呼び出し、ObjectOutputStreamのwriteObject0に到達しますが、ここでの書き込みの特定の型がStringであるため、違いがあります。
Integerはまた、Serializableを実装し、彼のために特別な治療はありませんので、それはwriteOrdinaryObjectに移動しますが、ここで文字列はwriteString呼び出しに行く必要性を判断するために
このメソッドはまた、比較的単純な最初の書き込みStringフラグビットであり、その後、特定のデータと長さを書き込み、他の型も同じですここで別の分岐に移動し、最終的にストリームにデータを書き込みます。
最後に、シリアライズ後のバイト数を見てみましょう。

Javaのデシリアライズの原理

あなたがシリアライズのキーコードを理解している場合は、デシリアライズは、実際にはシリアライズの逆のプロセスであり、その後、このプロセスでは、ペーストの分析を行うには、次のキーコードを見ることは難しいことではありません。

ここでは、デシリアライズされたオブジェクトの特定のタイプに応じて、異なる処理が行われることがわかりますが、現在のオブジェクトは、ユーザーオブジェクトですので、矢印で指すメソッドを入力します。

このメソッドではインスタンスオブジェクトが生成され、isInstantiableメソッドでコンストラクタが初期化されているかどうかが判定されると同時に、writeObjectメソッドとreadObjectメソッドがここで設定され、hasReadResolveMethodメソッドでreadObjectメソッドが実装されているかどうかが判定されます。メソッドが実装されていれば、readObjectメソッドがリフレクションによって呼び出されます。
readSerialDataメソッドが呼び出された後、defaultReadFieldsメソッドが呼び出されてフィールドの値が設定され、現在のObjはUserオブジェクトになります。
  1. 現在の Obj は User オブジェクトであり、null User オブジェクトであるため、データの長さは null です。
  2. Reflectionは、デシリアライズが必要なすべてのフィールドを取得し、対応する値を格納するための配列を作成します。
  3. 次に、readObject0 を再帰的に呼び出して、デシリアライズが必要なすべてのフィールド、たとえば現在の id と name を処理します。
    • 上記のシリアライズと同様に、id は Integer ラッパークラスなので、Object として認識されます。このメソッドに再び到達すると、最初のステップ primDataSize で、データの長さは 4 バイトになります。
    • 2番目のステップでは、Integer型なので、デシリアライズする必要のある他のフィールドはなく、それ自身のアンパックされた値だけを持っているので、idの現在の値を設定する4番目のステップに到達します。
  4. 現在の基本型フィールドの値を設定

String型の場合、最初のデシリアライズで、対応する値を読み取るために呼び出されます。

その他のシリアライズ・メソッド

新しい技術の誕生は、一定の理由と背景を持って、例えば、データのJavaネイティブシリアライズは、比較的大きく、伝送効率が低いと同時に、クロスリンガル通信することはできませんので、多くの人々がデータをシリアライズするXMLを使用することを選択し、XMLのシリアライズは、クロスリンガル通信の問題を解決することですが、それはネイティブデータよりもシリアライズされたデータも大きいですので、JSONの誕生シリアライゼーションは、彼はクロスランゲージをサポートし、シリアライズされたデータは、前の2つよりもはるかに小さく、最後に何人かの人々はさらに 圧縮の機能を持ってProtobufの導入のサイズを最適化したい、圧縮されたデータは、JSONシリアライズ後のデータよりも小さくなります。

その他のシリアライズ方法

  • XML
  • JSON
    • Jackson
    • FastJson
  • Hessian
  • thrift
  • protobuf
  • ...

他のシリアライゼーション手法については、そのパフォーマンスと基本原理を比較する記事を書くつもりです。

Read next

webpackのマルチエントリファイルパッケージングビルドのスタイルの損失。

元のプロジェクトでは、1つのエントリファイルapp.jsがあるだけで、今、我々は、ログイン認証のためのエントリファイルoAuth.jsを追加する必要があり、パッキングとビルドした後、我々は、oAuthページが正常に表示され、元のページのスタイルが失われていることがわかりました。 アプリとoAuthは同じログイン機能を持ち、どちらのファイルもloginコンポーネントを使用していますが、パッケージング後のapp.cssにはログインスタイルがありません...。

Nov 12, 2020 · 1 min read