戴老师的博客 - java源码分析
type: channel
link: https://1diff.fun/tag/java%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/
items: Java源码分析：深入探讨Iterator模式——迭代器模式
items: AbstractQueuedSynchronizer源码解析之ReentrantLock
items: JDK源码分析——研究 Hash 存储机制
items: LinkedHashMap的实现原理
items: HashMap源码分析（基于JDK1.6）
items: JDK源码分析之Set类详解——适配器模式的应用

Java源码分析：深入探讨Iterator模式——迭代器模式
type: item
date: 2026-03-30T09:40:40+00:00
description: 引言java.util 包中包含了一系列重要的集合类。本文将从源码分析入手，深入研究集合类的内部结构，以及遍历集合的迭代器模式（Iterator Pattern）的源码实现内幕。我们将先讨论根接口 Collection，然后分析抽象类 AbstractList 及其对应的 Iterator 接口，仔细研究迭代器模式的实现原理。说明：本文讨论的源代码版本基于 JDK 1.4.2。JDK 1.5 及后续版本在 java.util 中引入了泛型（Generics），为了简化问题并聚焦核心原理，本文主要讨论 1.4 版本的代码逻辑，但核心设计思想在后续版本中依然适用。集合类的根接口 CollectionCollection 接口是所有集合类的根类型。它的一个主要接口方法如下：boolean add(Object o)add() 方法用于添加一个新元素。注意该方法返回一个 boolean 值，但返回值不是表示添加成功与否。仔细阅读文档可以看到，Collection 规定：如果一个集合拒绝添加这个元素（无论任何原因），都必须抛出异常。返回值表示的意义是：add() 方法执行后，集合的内容是否改变了（即元素有无数量、位置等变化），具体行为由实现类决定。即：如果方法出错，总会抛出异常；返回值仅仅表示该方法执行后这个 Collection 的内容有无变化。类似的方法还有：boolean addAll(Collection c); boolean remove(Object o); boolean removeAll(Collection c); boolean retainAll(Collection c);Object[] toArray() 方法很简单，把集合转换成数组返回。Object[] toArray(Object[] a) 方法则稍微复杂一些：返回的 Object[] 仍然是把集合的所有元素变成的数组，但是类型和参数 a 的类型是相同的。例如执行：String[] o = (String[])c.toArray(new String[0]);得到的 o 实际类型是 String[]。如果参数 a 的大小装不下集合的所有元素，返回的将是一个新的数组。如果参数 a 的大小能装下集合的所有元素，则返回的还是 a，但 a 的内容用集合的元素来填充。尤其要注意的是，如果 a 的大小比集合元素的个数还多，a 后面的部分全部被置为 null。最后一个最重要的方法是 iterator()，返回一个 Iterator（迭代器），用于遍历集合的所有元素。使用 Iterator 模式遍历集合Iterator 模式是用于遍历集合类的标准访问方法。它可以把访问逻辑从不同类型的集合类中抽象出来，从而避免向客户端暴露集合的内部结构。例如，如果没有使用 Iterator，遍历一个数组的方法是使用索引：for(int i = 0; i < array.size(); i++) { // TODO: get(i) ... }而访问一个链表（LinkedList）又必须使用 while 循环：while((e = e.next()) != null) { // TODO: e.data() ... }以上两种方法客户端都必须事先知道集合的内部结构，访问代码和集合本身是紧耦合，无法将访问逻辑从集合类和客户端代码中分离出来。每一种集合对应一种遍历方法，客户端代码无法复用。更严重的是，如果以后需要把 ArrayList 更换为 LinkedList，则原来的客户端代码必须全部重写。为解决以上问题，Iterator 模式总是用同一种逻辑来遍历集合：for(Iterator it = c.iterator(); it.hasNext(); ) { // ... }奥秘在于客户端自身不维护遍历集合的“指针”，所有的内部状态（如当前元素位置，是否有下一个元素）都由 Iterator 来维护。而这个 Iterator 由集合类通过工厂方法生成，因此，它知道如何遍历整个集合。客户端从不直接和集合类打交道，它总是控制 Iterator，向它发送“向前”、“向后”、“取当前元素”的命令，就可以间接遍历整个集合。首先看看 java.util.Iterator 接口的定义：public interface Iterator { boolean hasNext(); Object next(); void remove(); }依赖前两个方法就能完成遍历，典型的代码如下：for(Iterator it = c.iterator(); it.hasNext(); ) { Object o = it.next(); // 对 o 的操作... }在 JDK 1.5 中，还对上面的代码在语法上作了简化（…

AbstractQueuedSynchronizer源码解析之ReentrantLock
type: item
date: 2026-03-30T09:37:37+00:00
description: 前言在 JDK 1.8 中，AbstractQueuedSynchronizer（简称 AQS）拥有众多子类，如下图所示：可以看到，公平锁与非公平锁（包括 ReentrantLock）都是基于 AQS 实现的。公平锁（Fair Lock）：线程抢占锁的顺序严格按照调用 lock 方法的先后顺序，依次获取锁。非公平锁（Unfair Lock）：线程抢占锁的顺序不定。线程尝试获取锁时，无论是否有其他线程在等待，都先尝试直接获取。若失败则加入等待队列。这意味着后来者可能“插队”成功（但因为抢占锁失败而加入到等待队列的线程不能参与下一次抢占，直到被 unpark）。AQS 核心结构AQS 通过内置的 FIFO 同步队列来完成资源获取线程的排队工作。如果当前线程获取同步状态失败（即获取锁失败），AQS 会将当前线程及等待状态等信息构造成一个节点（Node）并加入同步队列，同时阻塞当前线程。当同步状态释放时，则会把节点中的线程唤醒，使其再次尝试获取同步状态。类结构public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { protected AbstractQueuedSynchronizer() { } // 同步器队列头结点 private transient volatile Node head; // 同步器队列尾结点 private transient volatile Node tail; // 同步状态（当 state 为 0 时，无锁；当 state > 0 时，说明有锁） private volatile int state; // 获取锁状态 protected final int getState() { return state; } // 设置锁状态 protected final void setState(int newState) { state = newState; } // ... 其他方法 }通过 AQS 的类结构可以看到，其内部核心包含一个队列和一个 state 变量：队列：通过双向链表实现，用于存储等待获取锁的线程。state：锁的状态标志。volatile 关键字：head、tail 和 state 都是 volatile 类型的变量，保证了多线程环境下的内存可见性。同步队列的基本结构如下：Node 节点结构static final class Node { static final Node SHARED = new Node(); static final Node EXCLUSIVE = null; // 表示当前的线程被取消 static final int CANCELLED = 1; // 表示当前节点的后继节点包含的线程需要运行，也就是 unpark static final int SIGNAL = -1; // 表示当前节点在等待 condition，也就是在 condition 队列中 static final int CONDITION = -2; // 表示当前场景下后续的 acquireShared 能够得以执行 static final int PROPAGATE = -3; // 表示节点的状态。默认为 0，表示当前节点在 sync 队列中，等待着获取锁 volatile int waitStatus; // 前驱节点 volatile Node prev; // 后继节点 volatile Node next; // 获取锁的线程 volatile Thread thread; // 存储 condition 队列中的后继节点 Node nextWaiter; }Node 节点中除了存储当前线程、节点类型、队列中前后元素的引用外，还有一个关键变量 waitStatus。该变量用于描述节点的状态。由于 AQS 队列中存在并发，队列中会存取一定数量的节点，每个节点代表了一个线程的状态。有的线程可能因超时或中断需要放弃竞争（取消），有的线程在等待条件满足（Condition）。因此需要一个变量来描述这些状态，waitStatus 主要有以下几种状态：static final int CANCELLED = 1; static final int SIGNAL = -1; static final int CONDITION = -2; static final int PROPAGATE = -3;CANCELLED (1)：因为超时或者中断，节点会被设置为取消状态。被取消状态的节点不应再去竞…

JDK源码分析——研究 Hash 存储机制
type: item
date: 2026-03-30T09:33:38+00:00
description: 通过 HashMap、HashSet 的源代码分析其 Hash 存储机制集合和引用就像引用类型的数组一样，当我们把 Java 对象放入数组时，并不是真正把 Java 对象放入数组中，只是把对象的引用放入数组中，每个数组元素都是一个引用变量。实际上，HashSet 和 HashMap 之间有很多相似之处。对于 HashSet 而言，系统采用 Hash 算法决定集合元素的存储位置，这样可以保证能快速存、取集合元素；对于 HashMap 而言，系统将 key-value 当成一个整体进行处理，系统总是根据 Hash 算法来计算 key-value 的存储位置，这样可以保证能快速存、取 Map 的 key-value 对。在介绍集合存储之前需要指出一点：虽然集合号称存储的是 Java 对象，但实际上并不会真正将 Java 对象放入 Set 集合中，只是在 Set 集合中保留这些对象的引用。也就是说：Java 集合实际上是多个引用变量所组成的集合，这些引用变量指向实际的 Java 对象。HashMap 的存储实现当程序试图将多个 key-value 放入 HashMap 中时，以如下代码片段为例：HashMap<String, Double> map = new HashMap<String, Double>(); map.put("语文", 80.0); map.put("数学", 89.0); map.put("英语", 78.2);HashMap 采用一种所谓的"Hash 算法”来决定每个元素的存储位置。当程序执行 map.put("语文", 80.0); 时，系统将调用"语文"的 hashCode() 方法得到其 hashCode 值——每个 Java 对象都有 hashCode() 方法，都可通过该方法获得它的 hashCode 值。得到这个对象的 hashCode 值之后，系统会根据该 hashCode 值来决定该元素的存储位置。我们可以看 HashMap 类的 put(K key, V value) 方法的源代码：public V put(K key, V value) { // 如果 key 为 null，调用 putForNullKey 方法进行处理 if (key == null) return putForNullKey(value); // 根据 key 的 hashCode 计算 Hash 值 int hash = hash(key.hashCode()); // 搜索指定 hash 值在对应 table 中的索引 int i = indexFor(hash, table.length); // 如果 i 索引处的 Entry 不为 null，通过循环不断遍历 e 元素的下一个元素 for (Entry<K, V> e = table[i]; e != null; e = e.next) { Object k; // 找到指定 key 与需要放入的 key 相等（hash 值相同 // 通过 equals 比较返回 true） if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } // 如果 i 索引处的 Entry 为 null，表明此处还没有 Entry modCount++; // 将 key、value 添加到 i 索引处 addEntry(hash, key, value, i); return null; }JDK 源码在 JDK 安装目录下可以找到一个 src.zip 压缩文件，该文件里包含了 Java 基础类库的所有源文件。只要读者有学习兴趣，随时可以打开这份压缩文件来阅读 Java 类库的源代码，这对提高读者的编程能力是非常有帮助的。需要指出的是：src.zip 中包含的源代码并没有包含像上文中的中文注释，这些注释是笔者自己添加进去的。上面程序中用到了一个重要的内部接口：Map.Entry，每个 Map.Entry 其实就是一个 key-value 对。从上面程序中可以看出：当系统决定存储 HashMap 中的 key-value 对时，完全没有考虑 Entry 中的 value，仅仅只是根据 key 来计算并决定每个 Entry 的存储位置。这也说明了前面的结论：我们完全可以把 Map 集合中的 value 当成 key 的附属，当系统决定了 key 的存储位置之后，value 随之保存在那里即可。上面方法提供了一个根据 hashCode() 返回值来计算 H…

LinkedHashMap的实现原理
type: item
date: 2026-03-30T09:30:46+00:00
description: LinkedHashMap 的实现原理LinkedHashMap 概述LinkedHashMap 是 Map 接口的哈希表和链接列表实现，具有可预知的迭代顺序。此实现提供所有可选的映射操作，并允许使用 null 值和 null 键。与 HashMap 不同，LinkedHashMap 维护着一个运行于所有条目的双向链接列表，此链接列表定义了迭代顺序，该迭代顺序可以是插入顺序或者是访问顺序。注意，此实现不是同步的。如果多个线程同时访问链接的哈希映射，而其中至少一个线程从结构上修改了该映射，则它必须保持外部同步。LinkedHashMap 的实现原理对于 LinkedHashMap 而言，它继承自 HashMap，底层使用哈希表与双向链表来保存所有元素。其基本操作与父类 HashMap 相似，它通过重写父类相关的方法，来实现自己的链接列表特性。下面我们来分析 LinkedHashMap 的源代码。Entry 元素LinkedHashMap 采用的 hash 算法和 HashMap 相同，但是它重新定义了数组中保存的元素 Entry。该 Entry 除了保存当前对象的引用外，还保存了其上一个元素 before 和下一个元素 after 的引用，从而在哈希表的基础上又构成了双向链接列表。看源代码：/** * 双向链表的表头元素。 */ private transient Entry<K,V> header; /** * LinkedHashMap 的 Entry 元素。 * 继承 HashMap 的 Entry 元素，又保存了其上一个元素 before 和下一个元素 after 的引用。 */ private static class Entry<K,V> extends HashMap.Entry<K,V> { Entry<K,V> before, after; // ... } 初始化通过源代码可以看出，在 LinkedHashMap 的构造方法中，实际调用了父类 HashMap 的相关构造方法来构造一个底层存放的 table 数组。例如：public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false; } HashMap 中的相关构造方法：public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); // Find a power of 2 >= initialCapacity int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; this.loadFactor = loadFactor; threshold = (int)(capacity * loadFactor); table = new Entry[capacity]; init(); } 我们已经知道 LinkedHashMap 的 Entry 元素继承 HashMap 的 Entry，提供了双向链表的功能。在上述 HashMap 的构造器中，最后会调用 init() 方法，进行相关的初始化。这个方法在 HashMap 的实现中并无意义，只是提供给子类实现相关的初始化调用。LinkedHashMap 重写了 init() 方法，在调用父类的构造方法完成构造后，进一步实现了对其元素 Entry 的初始化操作：void init() { header = new Entry<K,V>(-1, null, null, null); header.before = header.after = header; } 存储操作LinkedHashMap 并未重写父类 HashMap 的 put 方法，而是重写了父类 HashMap 的 put 方法调用的子方法 void add…

HashMap源码分析（基于JDK1.6）
type: item
date: 2026-03-30T09:28:47+00:00
description: HashMap 源码分析（基于 JDK 1.6）编者注：本文为历史博文归档，主要基于 JDK 1.6 版本源码进行分析。涉及 JDK、框架与工具链版本请以当前官方文档为准。引用外链图片可能失效，阅读时请注意时效性。在 Java 集合类中，除了 ArrayList 之外，最常用的莫过于 HashMap 了。本文旨在尽可能详细地解析 HashMap 的源码实现。水平有限，若有不足之处，欢迎指正，定当及时修正。在深入 HashMap 源码之前，我们先简要复习一下相关的数据结构。数据结构基础Java 最基本的数据结构包括数组（Array）和链表（Linked List）。数组：空间连续（大小固定）、寻址迅速。但插入和删除时需要移动元素，因此查询快，增删慢。链表：可动态增加或减少空间以适应元素变化。但查找时只能顺着节点逐个查找，因此增删快，查询慢。有没有一种结构能综合数组和链表的优点呢？当然有，那就是哈希表（Hash Table）。虽然说是综合优点，但实际上查找速度肯定没有数组快，插入删除也没有链表快，这是一种折中的方案。哈希表一般采用拉链法（Chaining）实现。（图片源自网络，多处文章均有引用，因无法确认出处未申明作者）计算机相关专业通常都会学习这些内容，此处不再赘述。铺垫了数组、链表、哈希表和拉链法，接下来进入正题：我们在什么时候用到了这些内容？具体是如何实现的？其实我们一直在使用（想必大家对 HashMap 都不陌生），可能未曾深究其内部实现。下面主要分析 HashMap 的源码。类定义与继承结构HashMap 继承自 AbstractMap，实现了 Map 接口（相关内容可参考 《Java 集合类》）。类的定义如下：public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, SerializableMap 接口定义了所有 Map 子类必须实现的方法。Map 接口中还定义了一个内部接口 Entry（为何设计成内部接口值得探讨，此处暂不展开），Entry 将在后文详细介绍。AbstractMap 也实现了 Map 接口，并且提供了两个实现 Entry 的内部类：SimpleEntry 和 SimpleImmutableEntry。定义了接口，接口中又有内部接口，然后有抽象类实现接口，抽象类里面又有内部类实现接口的内部接口，结构略显复杂。暂且不管设计缘由，先看 HashMap 的具体实现。核心属性HashMap 中定义的关键属性如下（部分注释为原文保留）：/** * 默认的初始容量，必须是 2 的幂。 */ static final int DEFAULT_INITIAL_CAPACITY = 16; /** * 最大容量（必须是 2 的幂且小于 2 的 30 次方，传入容量过大将被这个值替换） */ static final int MAXIMUM_CAPACITY = 1 << 30; /** * 默认装载因子，后面会做解释 */ static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * 存储数据的 Entry 数组，长度是 2 的幂。 * 看到数组的内容了，接着看数组中存的内容就明白为什么博文开头先复习数据结构了 */ transient Entry[] table; /** * map 中保存的键值对的数量 */ transient int size; /** * 需要调整大小的极限值（容量 * 装载因子） */ int threshold; /** * 装载因子 */ final float loadFactor; /** * map 结构被改变的次数 */ transient volatile int modCount;构造方法接着是 HashMap 的构造方法：/** * 使用默认的容量及装载因子构造一个空的 HashMap */ public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); // 计算下次需要调整大小的极限值 table = new Entry[DEFAULT_INITIAL_CAPACITY]; // 根据默认容量（16）初始化 table init(); } /** * 根据给定的初始容量和装载因子创建一个空的 HashMap * 初始容量小于 0 或装载因子小于等于 0 将报异常 */ public HashMap(int initialCapacity,…

JDK源码分析之Set类详解——适配器模式的应用
type: item
date: 2026-03-30T09:28:23+00:00
description: JDK 源码分析之 Set 类详解——适配器模式的应用编者注：本文为历史博文归档；涉及 JDK、框架与工具链版本请以当前官方文档为准。引用外链图片可能失效，阅读时请注意时效性。JDK 源码中的 Set 类是开发过程中经常使用的集合类型。本文将对 JDK 源码中 Set 类的构造进行深入分析，帮助开发者在编程中更高效地应用。Set 类的核心特性与实现原理在 JDK 源码中，Set 类最常用的特性是不允许包含重复元素。那么，判断元素是否相同是如何实现的呢？我们可以通过下面的类图来直观理解：观察上述类图，我们可以发现一个经典设计模式的应用，那就是适配器模式（Adapter Pattern）。源码将 Map 接口的对象包装成为了 Set 接口。下面我们通过代码具体分析这一实现机制。HashSet 源码分析首先查看 HashSet 的核心成员变量：private transient HashMap<E, Object> map; // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object();可见，HashSet 内部适配了 HashMap。那么它的功能是如何委托给 HashMap 结构的呢？关键在于 add 方法的实现：public boolean add(E e) { return map.put(e, PRESENT) == null; }在 HashMap 中，我们大多数时候关注的是 value，但是在 Set 的实现中，却很好地利用了已有类 HashMap 的特性。它利用了 HashMap 的 key 的唯一性来保证存储在 Set 中的元素的唯一性。private static final Object PRESENT = new Object();上述 PRESENT 对象是 HashMap 所有 key 对应的 value，它只是一个形式占位符，而我们真正的数据是存储在 key 中的资源。此外，HashSet 拿到的迭代器也是基于 Map 的 keySet：public Iterator<E> iterator() { return map.keySet().iterator(); }即直接返回 Map 的 keySet 的迭代器。LinkedHashSet 源码分析同理，我们看看 LinkedHashSet 的实现。它提供了多个构造方法：public LinkedHashSet(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor, true); } /** * Constructs a new, empty linked hash set with the specified initial * capacity and the default load factor (0.75). * * @param initialCapacity the initial capacity of the LinkedHashSet * @throws IllegalArgumentException if the initial capacity is less * than zero */ public LinkedHashSet(int initialCapacity) { super(initialCapacity, .75f, true); } /** * Constructs a new, empty linked hash set with the default initial * capacity (16) and load factor (0.75). */ public LinkedHashSet() { super(16, .75f, true); }这些构造方法均调用了父类（HashSet）的构造函数。父类中对应的构造函数如下：HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); }由此可见，LinkedHashSet 内部实例化了 LinkedHashMap，从而保持了元素的插入顺序。TreeSet 源码分析同理，我们亦可见到 TreeMap 的实现影子。在 TreeSet 中，底层存储结构为：private transient NavigableMap<E, Obje…