概述
在Java中,一个类只要实现Serializable接口,这个类的对象就可以被序列化,这种序列化模式为开发者提供了很多便利,我们可以不必关心具体序列化的过程,只要这个类实现了Serializable接口,这个类的所有属性都会自动序列化。但是有时我们需要让类的某些属性不被序列化,如密码这类信息,为了安全起见,不希望在网络操作中被传输或者持久化到本地。只要在相应的属性前加上transient关键字,就可以实现部分属性不被序列化,该属性的生命周期仅存于调用者的内存中而不会写入到磁盘持久化。
transient的使用
public class TransientTest {public static void main(String[] args) {User user = new User();user.setUsername("Github");user.setPassword("123456");System.out.println("read before Serializable: ");System.out.println("username: " + user.getUsername());System.err.println("password: " + user.getPassword());try {ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("user.txt"));os.writeObject(user); // 将User对象写进文件os.flush();os.close();} catch (IOException e) {e.printStackTrace();}try {ObjectInputStream is = new ObjectInputStream(new FileInputStream("user.txt"));user = (User) is.readObject(); // 从流中读取User的数据is.close();System.out.println("\nread after Serializable: ");System.out.println("username: " + user.getUsername());System.err.println("password: " + user.getPassword());} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}}}public class User implements Serializable {private static final long serialVersionUID = 1234567890L;private String username;private transient String password;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}}
transient修饰静态变量
public class TransientTest {public static void main(String[] args) {User user = new User();user.setUsername("Github");user.setPassword("123456");System.out.println("read before Serializable: ");System.out.println("username: " + user.getUsername());System.err.println("password: " + user.getPassword());try {ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("user.txt"));os.writeObject(user); // 将User对象写进文件os.flush();os.close();} catch (IOException e) {e.printStackTrace();}try {// 在反序列化前盖板username的值user.setUsername("Tom");ObjectInputStream is = new ObjectInputStream(new FileInputStream("user.txt"));user = (User) is.readObject(); // 从流中读取User的数据is.close();System.out.println("\nread after Serializable: ");System.out.println("username: " + user.getUsername());System.err.println("password: " + user.getPassword());} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}}}public class User implements Serializable {private static final long serialVersionUID = 1234567890L;private static String username;private transient String password;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}}
案例研究:HashMap如何使用transient关键字?
到目前为止,我们一直在讨论与transient关键字相关的概念,这些概念基本上都是理论性的。让我们了解一下在HashMap类中逻辑地使用transient的正确用法。它将使您更好地了解java中transient关键字的实际用法。
在理解使用transient创建的解决方案之前,让我们先确定问题本身。
HashMap用于存储键-值对,这一点我们都知道。我们还知道HashMap中键的位置是根据键实例的哈希码计算的。现在,当我们序列化一个HashMap时,这意味着HashMap中的所有键以及与键相关的所有值也将被序列化。序列化之后,当我们反序列化HashMap实例时,所有关键实例也将被反序列化。我们知道在这个序列化/反序列化过程中,可能会丢失信息(用于计算hashcode),最重要的是它本身是一个新实例。
在java中,任何两个实例(甚至是相同类的实例)都不能有相同的hashcode。这是一个大问题,因为应该根据新的hashcode放置键的位置不正确。当检索键的值时,您将在这个新的HashMap中引用错误的索引。
因此,当一个哈希表被序列化时,它意味着哈希索引,和表的顺序不再有效,不应该被保留。这是问题陈述。
现在看看如何在HashMap类中解决这个问题。如果通过HashMap.java的源代码。你会发现下面的声明:
transient Node<K,V>[] table;transient Set<Map.Entry<K,V>> entrySet;transient int size;transient int modCount;
所有重要字段都标记为transient(所有字段实际上都是在运行时计算/更改的),因此它们不是序列化HashMap实例的一部分。为了再次填充这个重要的信息,HashMap类使用writeObject()和readObject()方法,如下所示:
private void writeObject(java.io.ObjectOutputStream s)throws IOException {int buckets = capacity();// Write out the threshold, loadfactor, and any hidden stuffs.defaultWriteObject();s.writeInt(buckets);s.writeInt(size);internalWriteEntries(s);}void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {Node<K,V>[] tab;if (size > 0 && (tab = table) != null) {for (Node<K,V> e : tab) {for (; e != null; e = e.next) {s.writeObject(e.key);s.writeObject(e.value);}}}}
private void readObject(java.io.ObjectInputStream s)throws IOException, ClassNotFoundException {// Read in the threshold (ignored), loadfactor, and any hidden stuffs.defaultReadObject();reinitialize();if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new InvalidObjectException("Illegal load factor: " +loadFactor);s.readInt(); // Read and ignore number of bucketsint mappings = s.readInt(); // Read number of mappings (size)if (mappings < 0)throw new InvalidObjectException("Illegal mappings count: " +mappings);else if (mappings > 0) { // (if zero, use defaults)// Size the table using given load factor only if within// range of 0.25...4.0float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);float fc = (float)mappings / lf + 1.0f;int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?DEFAULT_INITIAL_CAPACITY :(fc >= MAXIMUM_CAPACITY) ?MAXIMUM_CAPACITY :tableSizeFor((int)fc));float ft = (float)cap * lf;threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?(int)ft : Integer.MAX_VALUE);// Check Map.Entry[].class since it's the nearest public type to// what we're actually creating.SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Map.Entry[].class, cap);@SuppressWarnings({"rawtypes","unchecked"})Node<K,V>[] tab = (Node<K,V>[])new Node[cap];table = tab;// Read the keys and values, and put the mappings in the HashMapfor (int i = 0; i < mappings; i++) {@SuppressWarnings("unchecked")K key = (K) s.readObject();@SuppressWarnings("unchecked")V value = (V) s.readObject();putVal(hash(key), key, value, false, false);}}}
使用上面的代码,HashMap仍然允许像通常那样处理非transient字段,但是它们在字节数组的末尾一个接一个地写存储的键-值对。在反序列化时,它允许默认情况下处理的非transient变量,然后逐个读取键-值对。对于每个键,哈希值和索引将被再次计算,并被插入到表中的正确位置,以便再次检索时不会出现任何错误。
上面使用transient关键字就是一个很好的例子。您应该记住它,并在下一次java面试问题中提到它。
总结
- 一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
- transient关键字只能修饰变量,而不能修饰方法和类。本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
- 一个静态变量不管是否被transient修饰,均不能被序列化。
- 无论何时将任何final字段/引用计算为“常量表达式”,JVM都会对其进行序列化,忽略transient关键字的存在。
参考链接
https://zhuanlan.zhihu.com/p/51980884
https://www.jianshu.com/p/2911e5946d5c
