transient HashMap使用目的分析

看HashSet源码有这么一句:

private transient HashMap<E,Object> map;

再看HashSet的Add方法:

实际上HashSet是复用HashMap了。

而我们去看看HashMap也会发现一样使用了transient

而不管是HashSet还是HashMapdou都要求是Serializable的:

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable

而transient实际上是不序列化的,这就好像有点“矛盾”,再回答这个问题之前先看看transient。

测试代码如下:

import java.io.Serializable;

public class EmployeeTransient implements Serializable {

    public String getConfidentialInfo() {
        return confidentialInfo;
    }

    public void setConfidentialInfo(String confidentialInfo) {
        this.confidentialInfo = confidentialInfo;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    private transient String confidentialInfo;
    private String firstName;
    private String lastName;
}

 现在让我们先序列化再反序列化回java对象,并验证是否保存了“ confidentialInfo ”?

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class TransientTest {
    public static void main(String args[]) {

        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("empInfo.ser"));
            EmployeeTransient emp = new EmployeeTransient();
            emp.setFirstName("Lokesh");
            emp.setLastName("Gupta");
            emp.setConfidentialInfo("password");
            //Serialize the object
            oos.writeObject(emp);
            oos.close();
        } catch (Exception e) {
            System.out.println(e);
        }

        try {
            ObjectInputStream ooi = new ObjectInputStream(new FileInputStream("empInfo.ser"));
            //Read the object back
            EmployeeTransient readEmpInfo = (EmployeeTransient) ooi.readObject();
            System.out.println(readEmpInfo.getFirstName());
            System.out.println(readEmpInfo.getLastName());
            System.out.println(readEmpInfo.getConfidentialInfo());
            ooi.close();
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

很明显,“ confidentialInfo ”在序列化时没有保存到持久状态,这正是我们在java中使用“ transient ”关键字的原因。 

什么时候应该在java中使用transient关键字?

现在我们对“ transient  ”关键字非常了解。让我们通过确定您需要使用transient关键字的情况来扩展理解。

  1. 第一个也是非常合乎逻辑的情况是,您可能拥有类实例中的其他字段派生/计算的字段。应该每次都以编程方式计算它们,而不是通过序列化来保持状态。一个例子可以是基于时间戳的价值; 例如人的年龄或时间戳和当前时间戳之间的持续时间。在这两种情况下,您将根据当前系统时间而不是序列化实例来计算变量的值。
  2. 第二个逻辑示例可以是任何不应以任何形式泄漏到JVM外部的安全信息(在数据库或字节流中)。
  3. 另一个例子可能是在JDK或应用程序代码中未标记为“Serializable”的字段。未实现Serializable接口并在任何可序列化类中引用的类无法序列化; 并将抛出“java.io.NotSerializableException”异常。在序列化主类之前,应将这些不可序列化的引用标记为“transient ”。
  4. 最后,有时候序列化某些字段根本没有意义。期。例如,在任何类中,如果添加了记录器引用,那么序列化该记录器实例的用途是什么。绝对没用。您可以逻辑地序列化表示实例状态的信息。Loggers永远不要分享实例的状态。它们只是用于编程/调试目的的实用程序。类似的例子可以是一个Thread类的引用。线程表示任何给定时间点的进程状态,并且没有用于将线程状态存储到您的实例中; 仅仅因为它们不构成你班级实例的状态。

 

//final field 1
public final transient String confidentialInfo = "password";

 现在当我再次运行序列化(写/读)时,会输出出password。

原因是,只要任何最终字段/引用被评估为“ 常量表达式 ”,它就会被JVM序列化,忽略transient关键字的存在。

如果要保持非可序列化字段的状态,请使用readObject()和writeObject()方法。writeObject()/ readObject()通常在内部链接到序列化/反序列化机制,因此自动调用。 阅读更多:java中的SerialVersionUID及相关

 

HashMap如何使用transient关键字?

HashMap用于存储键值对,我们都知道。并且我们还知道内部密钥的位置HashMap是基于例如密钥获得的哈希码来计算的。现在当我们序列化一个HashMap意味着内部的所有键HashMap和键的各个值也将被序列化。序列化后,当我们反序列化HashMap实例时,所有键实例也将被反序列化。我们知道在这个序列化/反序列化过程中,可能会丢失信息(用于计算哈希码),最重要的是它本身就是一个新的实例。

在java中,任何两个实例(即使是同一个类)都不能具有相同的哈希码。这是一个很大的问题,因为根据新的哈希码应该放置键的位置不在正确的位置。检索键的值时,您将在此新HashMap中引用错误的索引。

阅读更多:在java中使用hashCode和equals方法

因此,当序列化哈希映射时,这意味着哈希索引,因此表的顺序不再有效,不应保留。这是问题陈述。

HashMap类使用writeObject()readObject()方法,如下所示

/**
     * Save the state of the <tt>HashMap</tt> instance to a stream (i.e.,
     * serialize it).
     *
     * @serialData The <i>capacity</i> of the HashMap (the length of the
     *             bucket array) is emitted (int), followed by the
     *             <i>size</i> (an int, the number of key-value
     *             mappings), followed by the key (Object) and value (Object)
     *             for each key-value mapping.  The key-value mappings are
     *             emitted in no particular order.
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws IOException {
        int buckets = capacity();
        // Write out the threshold, loadfactor, and any hidden stuff
        s.defaultWriteObject();
        s.writeInt(buckets);
        s.writeInt(size);
        internalWriteEntries(s);
    }
// Callbacks to allow LinkedHashMap post-actions
    void afterNodeAccess(Node<K,V> p) { }
    void afterNodeInsertion(boolean evict) { }
    void afterNodeRemoval(Node<K,V> p) { }

    // Called only from writeObject, to ensure compatible ordering.
    void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {
        Node<K,V>[] tab;
        if (size > 0 && (tab = table) != null) {
            for (int i = 0; i < tab.length; ++i) {
                for (Node<K,V> e = tab[i]; e != null; e = e.next) {
                    s.writeObject(e.key);
                    s.writeObject(e.value);
                }
            }
        }
    }

下面是HashSet中的实现,思路都是一样的。都是把key,value都写到序列化里去,

/**
     * Save the state of this <tt>HashSet</tt> instance to a stream (that is,
     * serialize it).
     *
     * @serialData The capacity of the backing <tt>HashMap</tt> instance
     *             (int), and its load factor (float) are emitted, followed by
     *             the size of the set (the number of elements it contains)
     *             (int), followed by all of its elements (each an Object) in
     *             no particular order.
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // Write out any hidden serialization magic
        s.defaultWriteObject();

        // Write out HashMap capacity and load factor
        s.writeInt(map.capacity());
        s.writeFloat(map.loadFactor());

        // Write out size
        s.writeInt(map.size());

        // Write out all elements in the proper order.
        for (E e : map.keySet())
            s.writeObject(e);
    }

然后反序列化的时候就是重新hash一次的过程

下面是HashMap的readObject:

下面是HashSet的readObject:

使用上面的代码,HashMap仍然可以像通常那样处理非transient字段,但是它们一个接一个地在字节数组的末尾写入存储的键值对。在反序列化时,它会通过默认的反序列化过程处理非transient变量,然后逐个读取键值对。对于每个键,哈希和索引再次计算并插入到表中的正确位置,以便可以再次检索它而不会出现任何错误。 

参考:Java transient关键字示例 

扩展阅读:

为什么HashMap的哈希表标记为transient,尽管该类是可序列化的

图解集合4:HashMap 

发布了1595 篇原创文章 · 获赞 1155 · 访问量 1212万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 深蓝海洋 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览