ホームページ  >  記事  >  Java  >  Java のシリアル化と逆シリアル化の詳細な分析

Java のシリアル化と逆シリアル化の詳細な分析

WBOY
WBOY転載
2022-11-14 16:14:521564ブラウズ

この記事では、java に関する関連知識を提供します。主にシリアル化と逆シリアル化に関する関連内容を紹介します。シリアル化はオブジェクトの永続化の手段です。以下を見てみましょう。みんな。

Java のシリアル化と逆シリアル化の詳細な分析

推奨学習: 「java ビデオ チュートリアル

Java オブジェクトのシリアル化

Java プラットフォームを使用すると、再利用可能な Java オブジェクトをメモリ内に作成しますが、通常、これらのオブジェクトは JVM の実行中にのみ存在します。つまり、これらのオブジェクトのライフ サイクルは JVM のライフ サイクルよりも長くなりません。しかし、実際のアプリケーションでは、JVM の実行停止後に指定されたオブジェクトを保存 (永続化) し、保存されたオブジェクトを将来再読み取りする必要がある場合があります。 Java オブジェクトのシリアル化は、この機能の実現に役立ちます。

Java オブジェクトのシリアル化を使用すると、オブジェクトを保存するときに、その状態が一連のバイトとして保存され、将来的にはこれらのバイトがオブジェクトに組み立てられます。オブジェクトのシリアル化では、オブジェクトの「状態」、つまりそのメンバー変数が保存されることに注意してください。 オブジェクトのシリアル化では、クラス内の静的変数 が考慮されないことがわかります。

オブジェクトのシリアル化は、オブジェクトを永続化するときに使用されるだけでなく、RMI (リモート メソッド呼び出し) を使用するときやネットワーク経由でオブジェクトを転送するときにも使用されます。 Java Serialization API は、オブジェクトのシリアル化を処理するための標準メカニズムを提供しており、シンプルで使いやすい API です。

Java オブジェクトをシリアル化および逆シリアル化する方法

Java では、クラスが java.io.Serializable インターフェイスを実装している限り、シリアル化できます。コードは次のとおりです。

コード 1 シリアル化および逆シリアル化用のユーザー クラスの作成

package com.hollis;import java.io.Serializable;import java.util.Date;/**
 * Created by hollis on 16/2/2.
 */public class User implements Serializable{    private String name;    private int age;    private Date birthday;    private transient String gender;    private static final long serialVersionUID = -6849794470754667710L;    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;
    }    public Date getBirthday() {        return birthday;
    }    public void setBirthday(Date birthday) {        this.birthday = birthday;
    }    public String getGender() {        return gender;
    }    public void setGender(String gender) {        this.gender = gender;
    }    @Override
    public String toString() {        return "User{" +                "name='" + name + ''' +
                ", age=" + age +
                ", gender=" + gender +
                ", birthday=" + birthday +
                '}';
    }
}

コード 2 ユーザーのシリアル化および逆シリアル化のデモ

package com.hollis;import org.apache.commons.io.FileUtils;import org.apache.commons.io.IOUtils;import java.io.*;import java.util.Date;/**
 * Created by hollis on 16/2/2.
 */
public class SerializableDemo {

    public static void main(String[] args) {
        //Initializes The Object
        User user = new User();
        user.setName("hollis");
        user.setGender("male");
        user.setAge(23);
        user.setBirthday(new Date());
        System.out.println(user);

        //Write Obj to File
        ObjectOutputStream oos = null;
        try {            oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
            oos.writeObject(user);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(oos);
        }

        //Read Obj from File
        File file = new File("tempFile");
        ObjectInputStream ois = null;
        try {            ois = new ObjectInputStream(new FileInputStream(file));
            User newUser = (User) ois.readObject();
            System.out.println(newUser);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(ois);
            try {
                FileUtils.forceDelete(file);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}
//output 
//User{name='hollis', age=23, gender=male, birthday=Tue Feb 02 17:37:38 CST 2016}
//User{name='hollis', age=23, gender=null, birthday=Tue Feb 02 17:37:38 CST 2016}

シリアル化と逆シリアル化に関する知識deserialization

1. Java では、クラスが java.io.Serializable インターフェイスを実装している限り、クラスをシリアル化できます。

2. ObjectOutputStream および ObjectInputStream

を介してオブジェクトをシリアル化および逆シリアル化します3. 仮想マシンが逆シリアル化を許可するかどうかは、クラスがパスと関数のコードは一貫しています。非常に重要な点は、2 つのクラスのシリアル化 ID が一貫しているかどうかです (つまり、private staticfinallongserialVersionUID)

4. シリアル化とマージ Static変数は保存されません。

5. 親クラスのオブジェクトをシリアル化したい場合は、親クラスに Serializable インターフェイスも実装させる必要があります。

6. Transient キーワードの機能は、変数のシリアル化を制御することです。変数宣言の前にこのキーワードを追加すると、変数がファイルにシリアル化されるのを防ぐことができます。逆シリアル化された後、一時変数の値は次のとおりです。初期値 (int 型の場合は 0、オブジェクト型の場合は null など) に設定されます。

7. サーバーは、シリアル化されたオブジェクト データをクライアントに送信します。オブジェクト内の一部のデータは、パスワード文字列など、機密です。パスワード フィールドはシリアル化中に暗号化されることが望まれ、クライアントは、復号化キー、パスワードはクライアントが逆シリアル化するときにのみ読み取ることができるため、シリアル化されたオブジェクトのデータ セキュリティをある程度確保できます。

ArrayList のシリアル化

ArrayList のシリアル化を紹介する前に、次の質問について考えてみましょう:

シリアル化および逆シリアル化戦略をカスタマイズする方法

この質問では、java.util.ArrayList

コード 3

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{    private static final long serialVersionUID = 8683452581122892189L;
    transient Object[] elementData; // non-private to simplify nested class access
    private int size;
}

のソース コードを見てみましょう。上記のコードから、作成者は他のメンバー変数を省略しました。 ArrayList が java.io.Serializable インターフェイスを実装していることがわかれば、それをシリアル化および逆シリアル化できます。 elementData は transient であるため、このメンバー変数はシリアル化されて保持されないと考えられます。私たちのアイデアを検証するためにデモを書いてみましょう:

code 4

public static void main(String[] args) throws IOException, ClassNotFoundException {
        List<String> stringList = new ArrayList<String>();
        stringList.add("hello");
        stringList.add("world");
        stringList.add("hollis");
        stringList.add("chuang");
        System.out.println("init StringList" + stringList);
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("stringlist"));
        objectOutputStream.writeObject(stringList);

        IOUtils.close(objectOutputStream);
        File file = new File("stringlist");
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
        List<String> newStringList = (List<String>)objectInputStream.readObject();
        IOUtils.close(objectInputStream);
        if(file.exists()){
            file.delete();
        }
        System.out.println("new StringList" + newStringList);
    }
//init StringList[hello, world, hollis, chuang]//new StringList[hello, world, hollis, chuang]

ArrayList を知っている人なら誰でも、ArrayList の最下層が配列を通じて実装されていることを知っています。次に、arrayelementData が実際にリスト内の要素を保存するために使用されます。この属性の宣言方法から、シリアル化を通じて永続化できないことがわかります。では、コード 4 の結果では、シリアル化と逆シリアル化を通じてリスト内の要素が保持されるのはなぜでしょうか?

writeObject メソッドと readObject メソッド

は、ArrayList に writeObjectreadObject という 2 つのメソッドを定義します。

結論は次のとおりです。

シリアル化プロセス中に、writeObject メソッドと readObject メソッドがシリアル化されたクラスで定義されている場合、仮想マシンは、オブジェクト クラス。ユーザー定義のシリアル化と逆シリアル化を実行します。

そのようなメソッドがない場合、デフォルトの呼び出しは ObjectOutputStream のdefaultWriteObject メソッドと ObjectInputStream のdefaultReadObject メソッドです。

用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。

来看一下这两个方法的具体实现:

code 5

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;        // Read in size, and any hidden stuff
        s.defaultReadObject();        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {            // be like clone(), allocate array based upon size not capacity
            ensureCapacityInternal(size);            Object[] a = elementData;            // Read in all elements in the proper order.
            for (int i=0; i<size; i++) {                a[i] = s.readObject();
            }
        }
    }

code 6

private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }        if (modCount != expectedModCount) {            throw new ConcurrentModificationException();
        }
    }

那么为什么ArrayList要用这种方式来实现序列化呢?

why transient

ArrayList实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,而实际只放了一个元素,那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化,ArrayList把元素数组设置为transient。

why writeObject and readObject

前面说过,为了防止一个包含大量空对象的数组被序列化,为了优化存储,所以,ArrayList使用transient来声明elementData。 但是,作为一个集合,在序列化过程中还必须保证其中的元素可以被持久化下来,所以,通过重写writeObjectreadObject方法的方式把其中的元素保留下来。

writeObject方法把elementData数组中的元素遍历的保存到输出流(ObjectOutputStream)中。

readObject方法从输入流(ObjectInputStream)中读出对象并保存赋值到elementData数组中。

至此,我们先试着来回答刚刚提出的问题:

如何自定义的序列化和反序列化策略

答:可以通过在被序列化的类中增加writeObject 和 readObject方法。那么问题又来了:

虽然ArrayList中写了writeObject 和 readObject 方法,但是这两个方法并没有显示的被调用啊。

那么如果一个类中包含writeObject 和 readObject 方法,那么这两个方法是怎么被调用的呢?

ObjectOutputStream

从code 4中,我们可以看出,对象的序列化过程通过ObjectOutputStream和ObjectInputputStream来实现的,那么带着刚刚的问题,我们来分析一下ArrayList中的writeObject 和 readObject 方法到底是如何被调用的呢?

为了节省篇幅,这里给出ObjectOutputStream的writeObject的调用栈:

writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObject<p>这里看一下invokeWriteObject:</p><pre class="brush:php;toolbar:false">void invokeWriteObject(Object obj, ObjectOutputStream out)         throws IOException, UnsupportedOperationException     {        if (writeObjectMethod != null) {            try {                 writeObjectMethod.invoke(obj, new Object[]{ out });             } catch (InvocationTargetException ex) {                Throwable th = ex.getTargetException();                if (th instanceof IOException) {                    throw (IOException) th;                 } else {                     throwMiscException(th);                 }             } catch (IllegalAccessException ex) {                // should not occur, as access checks have been suppressed                 throw new InternalError(ex);             }         } else {            throw new UnsupportedOperationException();         }     }</pre><p>其中<code>writeObjectMethod.invoke(obj, new Object[]{ out });是关键,通过反射的方式调用writeObjectMethod方法。官方是这么解释这个writeObjectMethod的:

class-defined writeObject method, or null if none

在我们的例子中,这个方法就是我们在ArrayList中定义的writeObject方法。通过反射的方式被调用了。

至此,我们先试着来回答刚刚提出的问题:

如果一个类中包含writeObject 和 readObject 方法,那么这两个方法是怎么被调用的?

答:在使用ObjectOutputStream的writeObject方法和ObjectInputStream的readObject方法时,会通过反射的方式调用。

至此,我们已经介绍完了ArrayList的序列化方式。那么,不知道有没有人提出这样的疑问:

Serializable明明就是一个空的接口,它是怎么保证只有实现了该接口的方法才能进行序列化与反序列化的呢?

Serializable接口的定义:

public interface Serializable {
}

读者可以尝试把code 1中的继承Serializable的代码去掉,再执行code 2,会抛出java.io.NotSerializableException

其实这个问题也很好回答,我们再回到刚刚ObjectOutputStream的writeObject的调用栈:

writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObject

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());
                }
            }

在进行序列化操作时,会判断要被序列化的类是否是Enum、Array和Serializable类型,如果不是则直接抛出NotSerializableException

总结

1、如果一个类想被序列化,需要实现Serializable接口。否则将抛出NotSerializableException异常,这是因为,在序列化操作过程中会对类型进行检查,要求被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种。

2、在变量声明前加上该关键字,可以阻止该变量被序列化到文件中。

3、在类中增加writeObject 和 readObject 方法可以实现自定义序列化策略

推荐学习:《java视频教程

以上がJava のシリアル化と逆シリアル化の詳細な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.imで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。