2431 字
12 分钟
java知识1
2026-09-21
  • 是否借助ai

1.java中的序列化和反序列化#

序列化就是把内存中的 Java 对象转成二进制字节流,这样才能存到硬盘或者通过网络发给别的机器。反序列化就是反过来,把字节流还原成 Java 对象。硬盘和网络只认 0 和 1,不认 Java 对象,所以必须有这么一层转换。

序列化的内容一般是可保存、可传输、可打印的形式。对于密码等敏感字段,要防止序列化。

1)怎么做序列化:类必须实现 Serializable 接口,这玩意儿就是个许可证,没它 JDK 序列化机制压根不让你序列化

2)怎么防序列化:敏感字段比如密码,加个 transient 关键字,序列化的时候自动跳过

3)怎么稳(保持兼容性):显式定义 serialVersionUID,相当于给类打个版本戳,防止改了类结构后反序列化旧数据时报错(自动转换走默认值或默认处理)

public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private transient String password; // 不参与序列化
private int age;
}

对象不能直接传输:#

  • 对象在 JVM 里是”立体”的,到处都是引用关系。比如某个字段指向服务器内存地址 0x1234 处的另一个对象,这地址只在当前 JVM 有效,传到网络另一端,人家客户端的 0x1234 地址上根本不是同一个东西。

  • 序列化要干的活就是把这些引用关系”压扁”,把整个对象图递归地转成一段自包含的字节序列。接收端拿到后,反序列化时在自己的堆上重建整个对象图,引用关系照样能还原回来。

静态变量(static)不参与序列化,#

序列化存的是对象的状态。静态变量属于类。

JDK自带序列化的问题#

  • 性能差,字节流过大,速度慢
  • 跨语言不友好,py,go读不懂,无法互通 常用JSON序列化解决,虽然性能一般,但可读性好。RPC框架用Protobuf、Hessian等

面试官追问#

提问:transient 和 static 修饰的字段都不会被序列化,它们有什么区别?

回答:本质不同。static 字段属于类,压根就不是对象状态的一部分,所以不存在”序列化不序列化”的问题,它就不在序列化的范畴内。transient 是专门告诉序列化机制”这个实例字段我不想存”,比如密码、缓存这类不适合持久化的数据。一个是”不归你管”,一个是”归你管但我不让你存”。

提问:如果父类没有实现 Serializable,子类实现了,序列化子类对象时父类的字段会怎样?

回答:父类字段不会被序列化。反序列化时,子类字段正常恢复,但父类字段会调用父类的无参构造器重新初始化。如果父类没有无参构造器,反序列化直接报错。所以如果需要父类字段也参与序列化,父类也得实现 Serializable。

提问:Externalizable 接口和 Serializable 有什么区别?

回答:Serializable 是自动序列化,JDK 帮你搞定所有字段。Externalizable 是手动挡,必须自己实现 writeExternal 和 readExternal 方法,一个字段一个字段地写出去、读进来。好处是完全可控,可以做定制优化;坏处是麻烦,漏写一个字段就丢数据。性能要求高的场景可以考虑,一般业务代码用 Serializable 就够了。

提问:为什么说反序列化是 Java 安全漏洞的重灾区?

回答:反序列化会触发对象的构造过程,如果恶意构造的字节流能让某些类的特定方法被执行,就可能造成远程代码执行。典型的就是利用 Commons-Collections、Fastjson 这些库里的 gadget chain,攻击者只要找到一条能从反序列化入口通往危险方法的调用链,就能在服务器上执行任意代码。防御手段包括:升级有漏洞的依赖库、配置反序列化白名单、用 JSON 替代 Java 原生序列化。

解释:反序列化不只是“把数据读回来”,它还会根据字节流里的类信息,去创建对象、调用对象的方法。攻击者一旦能控制这段字节流,就可能让服务器执行他想要的代码。攻击者可以伪造一段序列化数据,里面指定:

  • 要反序列化成哪个类;
  • 类的字段值是什么;
  • 这些字段又引用了哪些其他对象。

2.java中HashMap的原理#

HashMap 底层就是一个数组,结合链表和红黑树来解决冲突。

它的工作原理其实很简单,主要说这 3 点:

1)怎么存

存一个 Key-Value 时,先算 Key 的 hashCode,然后用 (table.length-1) & hash 算出应该放在数组的哪个下标位置。

2)冲突了怎么办

两个不同的 Key 算出来下标一样,就叫哈希冲突。HashMap 的解决办法是用链表把它们串起来,JDK 8 做了优化,如果同一个下标下的链表超过 8 个节点(且数组长度大于等于 64),就转成红黑树,查找速度从 O(n) 变成 O(logn)。

3)扩容机制

数组长度是有限的,存多了会挤。HashMap 有个负载因子默认 0.75,数组用了 75% 就自动扩容,把数组大小翻倍,然后把所有数据重新算位置放到新数组里。这个操作叫 rehash,比较耗性能,所以初始化时最好给个预估容量。

HashMap 是非线程安全的#

多线程环境下可能出问题,比如数据覆盖或者死循环,这时候应该用 ConcurrentHashMap。

hashCode 和 equals 的重要性#

HashMap 的 key 必须正确实现 hashCode() 和 equals() 方法。hashCode() 决定元素存在哪个桶,equals() 决定两个 key 是不是同一个。

put 的时候,如果两个 key 的 hashCode() 相同但 equals() 返回 false,它们会被当成不同的 key 存在同一个桶的不同位置。如果 hashCode() 实现得不好导致分布不均,或者 equals() 写错了,会导致元素找不到或者重复插入。 跳转到解释equals和hashcode的关系

hashmap的扩容机制#

默认容量16

HashMap 的扩容由负载因子控制,默认是 0.75。意思就是当元素数量超过 容量 × 0.75 时触发扩容。比如初始容量 16,阈值就是 12,存第 13 个元素时就会扩容。

扩容的规则很简单:容量直接翻倍,从 16 变 32,32 变 64,始终保持 2 的幂次。扩容后所有元素要重新分配位置,这个过程叫 rehashing。即每个元素的存储位置会根据新容量的大小重新计算,并移动到新的数组中。

面试官追问#

提问:为什么链表转红黑树的阈值是 8,不是 6 或者 10? 回答:根据泊松分布计算,在负载因子 0.75 的情况下,一个桶里元素个数达到 8 的概率已经非常低了,大概是千万分之六。选 8 是因为这个概率下转红黑树基本只会在 hash 分布极差的时候发生,正常情况几乎不会触发,既保证了极端情况的性能,又避免了频繁转换的开销。

提问:JDK 8 改成尾插法就完全线程安全了吗? 回答:没有。尾插法只是解决了扩容时链表成环的问题,但 HashMap 本身还是非线程安全的。并发 put 的时候还是可能丢数据,比如两个线程同时往一个空桶插入,后插入的会覆盖先插入的。多线程场景必须用 ConcurrentHashMap。

提问:HashMap 的 key 可以用 null 吗? 回答:可以,HashMap 允许一个 null key。存的时候会把 null key 的 hash 值当成 0,固定放在数组下标 0 的位置。但 ConcurrentHashMap 不允许 null key,因为多线程场景下无法区分”key 不存在”和”key 存在但 value 是 null”。

3.解释equals和hashcode的关系#

契约规定:equals 相等的对象,hashCode 必须相等;但hashCode 相等的对象,equals 不一定相等。先比较hashcode后比较equals。

这条规则在使用 HashMap、HashSet 这类基于哈希的集合时特别重要。这些集合判断两个对象是否相同,先比 hashCode,hashCode 不等直接认为是两个不同对象,根本不会调 equals。所以如果只重写 equals 不重写 hashCode,集合就废了。

默认的hashcode返回对象的内存地址,equals默认比较的是引用地址,默认是==

重写可以用lombok自动生成。

提问:HashMap 的 key 用可变对象有什么风险? 回答:如果 key 是可变对象,存进去之后又改了参与 hashCode 计算的字段,那这个 entry 就再也找不到了。因为修改后 hashCode 变了,HashMap 会到新的槽位去找,但数据还在老槽位,get 返回 null,remove 也删不掉,造成内存泄漏。所以 HashMap 的 key 最好用不可变对象,String、Integer 这些都可以,自定义类当 key 要特别小心。

重写 equals 不重写 hashCode,会出现equals相同但hashcode不同,可能会导致业务逻辑上重复的元素被重复添加,查找找不到,例如key是对象的情况,查找的凭据对象是新new的。

参考资料#

java知识1
http://72.155.89.172/posts/java知识1/
作者
涌现
发布于
2026-09-21
许可协议
CC BY-NC-SA 4.0