ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Java基础数据结构核心解析:从ArrayList到HashMap的面试与实战

2026/10/7 21:42:44 拓冰建站 浏览量
Java基础数据结构核心解析:从ArrayList到HashMap的面试与实战 写这篇文章的起因很简单我最近在帮团队做技术面试复盘发现不少候选人写得出Spring Boot的服务却说不清ArrayList和LinkedList到底该选哪个能一口气背出HashMap的put流程但一追问“为什么链表长度超过8就要转红黑树”就卡壳。而这些问题归根到底都指向同一个基础——Java基础数据结构。如果你正在准备Java面试或者刚写完Java基础语法、准备进入集合框架这块那“基础数据结构”就是绕不开的一座山。它是“java八股文”里最大的一块也是日常开发里最容易被忽视、却最能拉开水平的知识点。这篇博文我打算从面试和实战两个角度把数组、链表、栈、队列、哈希表、树、堆这些基础数据结构在Java里的形态、实现逻辑、选型依据、高频考题全部盘一遍尽量用大白话讲清楚再给出一份可以直接照着练的代码和踩坑记录希望能给正要复习的人一条清晰的路线。1. 整体设计与思路拆解1.1 为什么说数据结构是Java的必修课很多初学者容易陷入一个误区Java最重要的不是框架吗Spring Boot一把梭MyBatis生成器一拖CRUD就完事了还要学数据结构干嘛这个想法我太熟了因为我刚开始工作时也是这么想的直到踩了几个性能问题的坑才翻回头补课。实际上框架解决的是“怎么组织代码”的问题数据结构解决的是“怎么组织数据”的问题。数据库查出来的100万行记录要放到内存里做聚合怎么存才能快定时任务要把一批待处理的任务按优先级排队用什么容器用户请求过来要做接口幂等需要短时间内判断某个订单号是否已存在用什么结构最省时间这些问题没有框架能替你回答全靠数据结构功底。在Java生态里数据结构的基础性体现得非常直接集合框架Java Collections Framework本身就是数据结构的工程实现。你天天用的ArrayList是动态数组LinkedList是双向链表HashMap是数组加链表加红黑树TreeMap是红黑树PriorityQueue是堆。理解了数据结构等于把JDK源码的骨架看穿了一半面试时讲源码、平时做性能调优心里都有底。从学习路线上看数据结构还是承接语法和算法的一座桥。学完循环、数组、类下一步自然要接触集合而排序、查找、递归这些算法全都依赖数据结构来承载。没有这座桥你看算法的题解就像看天书。这也是为什么几乎所有Java学习路线图都把“集合框架与数据结构”放在并发编程和JVM之前。1.2 从数据结构角度重新认识集合框架我最推荐的学习方式是先抛开Java语法把数据结构本身当成一门“逻辑课”来学然后再回来看JDK源码把每个类对应到具体的数据结构上。思路清晰了代码只是实现细节而已。按大类分常见基础数据结构可以归成四类。第一类是线性结构包括数组、链表、栈、队列特点是数据排成一列有前驱和后继第二类是树形结构包括二叉树、二叉搜索树、红黑树、堆特点是分叉组织适合做查找和排序第三类是散列结构代表就是哈希表用哈希函数把键映射到桶里追求平均O(1)的读写第四类是图结构Java里没有内置图但实际工程里依赖图的地方并不少比如任务编排的有向无环图。把这四类映射到Java集合框架对应关系就很清晰了数据结构Java接口/实现类底层实现动态数组List : ArrayListObject[]数组双向链表List : LinkedListNode前后指针链栈Deque : ArrayDeque循环数组队列Queue : LinkedList/ArrayDeque链表或循环数组哈希表Map : HashMap / Set : HashSet数组链表红黑树有序映射Map : TreeMap红黑树优先队列Queue : PriorityQueue二叉堆有序集合Set : TreeSet红黑树这份表建议直接刻在脑子里。面试问“HashSet底层是什么”答案不是“一个不允许重复元素的集合”而是“底层就是HashMap把元素放在key的位置value统一放一个空对象”。这样回答面试官就知道你是真的懂而不是背概念。1.3 选型思路业务场景决定结构而不是习惯决定结构很多开发写代码选容器全凭习惯列表一律ArrayList去重一律HashSet搞不定就LinkedHashMap。习惯不是不行但至少要清楚自己为什么这么选以及代价是什么。我把常用选择场景总结成几条经验供你参考。如果只做按索引访问几乎没有增删操作选ArrayList。它底层是连续数组CPU缓存友好随机访问O(1)遍历速度在大部分场景下吊打LinkedList。如果数据在头部或中间频繁插入删除才有理由考虑LinkedList但即便如此我也建议你先想想能不能用ArrayList加倒序处理来替代因为LinkedList的节点分散在堆内存遍历时缓存命中率很低实际性能往往没有想象中好。如果要做键值映射默认优先用HashMap。它单线程下综合性能极好但要注意它不是有序的。如果需要按插入顺序遍历用LinkedHashMap需要按键排序用TreeMap需要线程安全用ConcurrentHashMap而不是HashtableHashtable整表加锁并发度太低。如果要做栈或队列优先选择ArrayDeque而不是Stack或LinkedList。Java官方文档里都建议用ArrayDeque替代Stack因为Stack继承Vector所有方法都加锁性能差而且遗留风格较重LinkedList虽然也实现了Deque但节点分散加上频繁扩容不如ArrayDeque的循环数组干净利落。PriorityQueue则适合“每次都要取最大或最小”的场景比如定时任务的优先调度、TopK问题。2. 核心细节解析与实操要点2.1 数组所有容器模型的起点数组是Java里最基础的数据结构其他大部分结构都是在数组上演化出来的。它的核心特点有两个一是内存连续二是一经创建长度固定。内存连续带来的最大优势是随机访问效率高。数组通过下标找元素的原理是纯地址计算首地址加上下标乘以元素大小一步算出目标位置所以访问任意元素都是O(1)。这也是ArrayList读多写少的场景下特别快的原因。但连续的代价是插入和删除麻烦在中间插一个元素后面的所有元素都要往后挪删除则是往前挪时间复杂度O(n)。长度固定这个特点决定了动态数组必须实现扩容。以ArrayList为例每次add发现数组满了会新创建一个原来1.5倍的数组再把旧数据用System.arraycopy搬过去。这里有个很有价值的细节如果你能预估数据量最好在构造时传入初始容量比如new ArrayList(10000)避免频繁扩容带来的数组复制开销。我在做大批量数据处理时就习惯先估算容量再建列表实测能省不少时间。数组在Java里还有一个容易忽略的应用多维数组其实是一维数组的嵌套。int[][]在内存里是“数组的数组”每一行是独立的一维数组对象所以行数可以参差不齐这就是“锯齿数组”。面试偶尔会问能答出来会加分。2.2 链表非连续存储的典型代表链表和数组正好站在对立面不要求内存连续每个节点包含数据和指向下一个节点的引用通过指针把这些节点串起来。Java里LinkedList是双向链表节点内部有prev和next两个指针。链表的优势在插入和删除。只要拿到了目标节点的引用理论上插入删除都是O(1)因为你只需要改指针不需要搬动其他元素。但这个优势有个前置条件——你得先找到那个节点。LinkedList在按索引访问时要从头或从尾逐个遍历时间复杂度O(n)所以它“插入快”只在“已经定位到位置”的前提下成立否则定位成本分分钟抵消插入优势。链表的劣势不容忽视每个节点都要额外存两个指针内存开销比数组大节点散落在堆内存各处遍历时CPU缓存命中率低实际速度往往不如数组。我在一次数据量5万左右的链表遍历测试里LinkedList比ArrayList慢了将近三倍这还是在无锁单线程环境下。所以你看到网上有些“面试八股”说LinkedList插入快别全信要会分场景批判。链表也是很多高级结构的基石。HashMap在哈希冲突时用链表挂载数据红黑树的旋转操作也会借助类似指针的思路。建议初学者至少能手写一个单链表节点的插入和删除这对理解指针、引用和内存模型都很有帮助。2.3 栈与队列两个被低估的“小结构”栈和队列在Java集合框架里没有独立的类它们是以接口形式存在的Deque接口同时定义了栈和双端队列的操作。让我重点说一句别再new Stack了用ArrayDeque。栈的特点是后进先出LIFO典型场景是函数调用栈、括号匹配、表达式求值、撤销操作。Java的JVM虚拟机栈本身就是栈结构所以“栈”这个概念你会在JVM里再次遇到。用ArrayDeque做栈时push对应压栈pop对应出栈peek看栈顶。它内部用循环数组实现扩容均摊后性能很好。队列的特点是先进先出FIFO典型场景是任务排队、消息队列的生产消费、BFS广度优先搜索。ArrayDeque实现队列时offer入队、poll出队。如果是需要线程安全的生产消费场景我一般用LinkedBlockingQueue或ArrayBlockingQueue那是并发包里的阻塞队列底层逻辑仍然离不开链表或数组。栈和队列的正确使用在算法题里特别关键。我辅导过的不少人写BFS就卡在“不知道该用队列”写括号匹配卡在“不知道用栈”。这两个结构虽然简单但它们是很多算法模板的骨架。建议你把Deque接口的常用方法逐个敲一遍搞清楚add/offer、remove/poll、element/peek这三组方法的区别前一组失败抛异常后一组返回特殊值。2.4 哈希表HashMap底层逻辑深度拆解HashMap是整个Java集合框架里最值得深挖的类没有之一。面试考它从“数组链表红黑树”这句能延伸到无限深度。我把它的核心逻辑拆成三段讲。第一段是hash过程。put时会先对key的hashCode做一次扰动让高位也参与低位运算减少哈希碰撞概率然后通过hash (len-1)得到桶的数组下标。这里有个细节数组长度是2的幂时取模可以用位运算代替效率更高所以HashMap要求容量必须是2的幂即使你构造函数传了一个不是2的幂的容量它也会帮你向上取整成最近的2的幂。第二段是put流程。算出下标后如果数组那个位置是空的直接新建节点放入如果不空说明发生了哈希碰撞此时会遍历该桶下的链表如果找到了key相同的节点就覆盖value否则在链表尾部插入新节点。JDK8之后采用尾插法主要是为了解决JDK7头插法在并发扩容时可能产生环形链表的问题。当链表长度超过8并且数组容量大于等于64时链表会转成红黑树把查找复杂度从O(n)降到O(log n)。第三段是扩容机制。默认负载因子是0.75意思是当元素个数超过容量的75%时触发扩容新容量是旧容量的两倍。0.75这个值是空间和时间的一个折中太小浪费空间太大容易频繁冲突。扩容会重新计算每个元素的位置所以代价很高这也是为什么我前面建议预估容量。多线程环境下HashMap的put操作会导致数据覆盖甚至JDK8里虽然没有了环形链表的死循环问题但数据丢失、size不准确等问题依然存在所以并发场景必须用ConcurrentHashMap。2.5 树与堆从二叉树到红黑树树结构在Java里最直接的体现是TreeMap和TreeSet它们底层是红黑树。红黑树是一种自平衡的二叉搜索树能保证最坏情况下增删查都是O(log n)。为什么需要“自平衡”因为普通的二叉搜索树如果插入顺序恰好有序会退化成链表查询变成O(n)红黑树通过节点颜色约束和旋转操作避免这种退化。面试官如果追问“红黑树性质”你要能背出这几条节点非红即黑根节点是黑的叶节点NIL是黑的红色节点的子节点必须是黑的不能出现连续红节点从任一节点到其每个叶子的所有路径都包含相同数目的黑节点。最后一条保证了最长路径不会超过最短路径的两倍这是它“近似平衡”的本质。背完性质最好还能说一句“Java的TreeMap在插入删除后通过左旋右旋和变色来修复平衡”这句话说明你深入过源码而不是背题。堆在Java里对应PriorityQueue底层是二叉堆通常是最小堆。它的应用场景是“动态取极值”每次加入或删除元素后都能用O(log n)拿到最小或最大元素。典型例子是求海量数据里的TopK维护一个大小为K的最小堆遍历数据时如果当前元素比堆顶大就移除堆顶、插入新元素最后堆里就是最大的K个。这套思路在实时排行榜、任务优先级调度里都很实用。要注意PriorityQueue默认是小顶堆想用大顶堆需要传入Comparator.reverseOrder()。3. 实操过程与核心环节实现3.1 手写一个单链表把引用机制吃透理论盘完之后必须动手。很多人觉得“手写链表”是学生时代的事但面试手撕代码时它出现的频率非常高。我建议从单链表的节点插入和删除开始练习这段代码能帮你彻底理解Java的引用传递。public class MyLinkedList { private Node head; private int size; private static class Node { int val; Node next; Node(int val) { this.val val; } } // 头插法 public void addFirst(int val) { Node newNode new Node(val); newNode.next head; head newNode; size; } // 在第 index 个位置插入 public void add(int index, int val) { if (index 0 || index size) { throw new IndexOutOfBoundsException(index: index); } if (index 0) { addFirst(val); return; } Node prev head; for (int i 0; i index - 1; i) { prev prev.next; } Node newNode new Node(val); newNode.next prev.next; prev.next newNode; size; } // 删除第一个值为 val 的节点 public boolean remove(int val) { if (head null) { return false; } if (head.val val) { head head.next; size--; return true; } Node prev head; while (prev.next ! null) { if (prev.next.val val) { prev.next prev.next.next; size--; return true; } prev prev.next; } return false; } }这段代码里的关键点有两个。一是插入时“先接后断”的顺序newNode.next prev.next 必须先执行再把 prev.next 指向 newNode。如果你把顺序反了会丢失后面的链表这是最常见的Bug之一。二是删除时要找前驱节点而不是当前节点因为单链表没有回头指针。理解了这两点链表的代码基本就通了。写完单链表之后我建议你对照着看LinkedList的源码看它是怎么用first和last两个哨兵节点实现双向链表的。哨兵节点dummy node在链表操作里是很好的优化手段可以省去大量“判断是否为空”的分支逻辑面试时提到这点会加分。3.2 排序算法到底怎么考怎么用热搜词里“java排序”“冒泡排序java”出现频率很高可见排序是基础数据结构绕不开的实践场景。排序算法本身是算法课的内容但它依赖数组、链表这种结构来承载所以面试经常把两者放在一起考。先把最常考的三种手写排序练熟我给出可以直接跑的版本。// 冒泡排序稳定O(n^2) public static void bubbleSort(int[] arr) { int n arr.length; for (int i 0; i n - 1; i) { boolean swapped false; for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int tmp arr[j]; arr[j] arr[j 1]; arr[j 1] tmp; swapped true; } } if (!swapped) break; // 优化本轮没有交换说明已有序 } } // 插入排序稳定适合近乎有序的数据O(n^2) public static void insertionSort(int[] arr) { for (int i 1; i arr.length; i) { int cur arr[i]; int j i - 1; while (j 0 arr[j] cur) { arr[j 1] arr[j]; j--; } arr[j 1] cur; } } // 快速排序不稳定平均O(n log n) public static void quickSort(int[] arr, int left, int right) { if (left right) return; int pivot arr[left]; int i left; int j right; while (i j) { while (i j arr[j] pivot) j--; arr[i] arr[j]; while (i j arr[i] pivot) i; arr[j] arr[i]; } arr[i] pivot; quickSort(arr, left, i - 1); quickSort(arr, i 1, right); }面试时除了手写还要能回答两个问题稳定性是什么意思哪些算法稳定哪些不稳定。冒泡排序和插入排序是稳定的快速排序和堆排序不稳定因为交换时可能把相等元素的相对顺序打乱。还有一个细节很多人忽略Arrays.sort底层用的并不是单一的排序算法。对基本类型数组它用双轴快速排序DualPivotQuicksort对对象数组它用TimSort——一种改进的归并排序稳定且能利用数据原有的有序性。所以如果你面试时说“Arrays.sort用的是快速排序”严格讲只对了一半对象数组用的是TimSort。3.3 不只是面试数据结构在业务代码里的落地说句实在话日常业务开发里不太可能让你手写红黑树但用好数据结构能实打实解决性能问题。我举三个真实场景。第一个场景接口幂等判断。订单系统要判断一个订单号是不是已经处理过如果把所有订单号放进ArrayList再contains数据量一上来就是O(n)扫描接口直接超时。正确的做法是把已处理订单号放进HashSet或基于ConcurrentHashMap实现的并发Set查询是O(1)。这就是哈希表在业务里的典型落地。第二个场景“最近被使用”的缓存淘汰。要记录用户最近浏览的10个商品并且新浏览时要移除最旧的同时要快速判断某个商品是否已存在。这个需求用LinkedHashMap最合适它继承HashMap且维护了插入顺序重写removeEldestEntry方法还能实现简单的LRU缓存十几行代码搞定。第三个场景多关键词搜索结果的合并排序。比如在商城搜索里多个条件各自查到一批ID最后要做去重、交集、排序。这时候用TreeSet可以保证元素有序并且自动去重比手动排序加大循环省事得多。把这些容器用对了很多业务代码不仅更短而且数据量大时性能差别肉眼可见。3.4 常用工具类与库函数盘点Java的java.util包其实已经把基础数据结构的常用操作封装得很好了关键是要知道有哪些工具以及它们适合什么场景。我用一张表把高频的库函数整理出来方便你快速查阅。工具类/方法作用注意事项Arrays.sort(T[] a)数组排序基本类型用双轴快排对象用TimSortArrays.binarySearch(int[] a, key)二分查找数组必须事先有序否则结果无意义Arrays.copyOf / System.arraycopy数组复制前者适合扩缩容后者可复制指定范围Collections.reverse(List list)反转列表对ArrayList和LinkedList均有效Collections.shuffle(List list)随机打乱可用于抽奖、随机出题Collections.min / max求极值集合需要实现Comparable或传ComparatorCollections.frequency(coll, obj)统计出现次数本质是遍历频繁调用注意性能Collections.synchronizedList(list)包装线程安全列表不建议新代码使用优先并发集合Collections.unmodifiableList(list)返回只读视图修改会抛异常适合防止意外改动还有一点容易踩坑Arrays.asList返回的List是定长的它直接包装原数组不能调用add和remove。很多人把Arrays.asList的结果当ArrayList用一调add就抛UnsupportedOperationException。如果需要真正的可变列表要写成new ArrayList(Arrays.asList(...))。Comparator和Comparable也是基础数据结构里绕不开的工具。尤其是lambda出现之后自定义排序变得非常简洁list.sort((a, b) - a.getAge() - b.getAge())。不过要注意int相减可能溢出最稳妥的写法是Integer.compare(a.getAge(), b.getAge())或者Comparator.comparing(User::getAge)。这些细节虽然小但面试和代码评审里很加分。4. 常见问题与排查技巧实录4.1 面试高频问题速查表我常跟学员说Java面试里关于数据结构的题问来问去就那些但你得自己会分辨深浅。这里我整理一份速查表每一行都建议你能展开讲到至少三句话。高频问题核心要点常见误区ArrayList和LinkedList区别底层数组vs双向链表随机访问O(1) vs O(n)插入删除的相对性盲目认为LinkedList插入一定快HashMap底层原理数组链表红黑树hash扰动尾插法负载因子0.75只背概念不会画put流程HashMap和Hashtable区别HashMap允许null键值、非线程安全Hashtable线程安全但性能差以为Hashtable可用实际上可用ConcurrentHashMapHashMap为什么用红黑树链表太长查找变慢树化阈值8退化阈值6记不住阈值和条件容量64HashSet底层是什么包装HashMapvalue为固定空对象以为HashSet独有一套存储逻辑TreeMap怎么保证有序红黑树中序遍历key需实现Comparable不知道Comparator和Comparable区别栈和队列用哪个类推荐ArrayDeque而非Stack/LinkedList还在写new Stack()HashMap初始容量为什么是2的幂便于位运算取模扩容方便不理解与扩容的联动Iterator和Iterable区别Iterable返回迭代器迭代器遍历时不能直接list.remove遍历时修改集合抛ConcurrentModificationException哪些排序稳定冒泡、插入、归并稳定快排、堆排不稳定回答时漏掉“比较器相等时顺序保持”这个前提真正准备时不要满足于把答案背下来我建议每个问题都打开IDE写一个Demo验证。比如写一段代码证明ArrayList中间插入比LinkedList慢还是快写一段代码观察HashMap扩容前后的容量变化这些实验做一遍记忆深度比背十遍都强。4.2 开发中最常见的五个踩坑点踩坑是学习的捷径。我在带新人和自己写代码的过程中总结了几个基础数据结构最常见的问题每条都来自真实事故希望你别再踩一遍。坑一遍历时直接删除集合元素。经典的错误写法是在for循环里调用list.remove(i)这样会导致元素移位漏删或越界。推荐用迭代器的Iterator.remove()或者Java8以后的list.removeIf(predicate)一行搞定。HashMap同理要遍历删除就map.entrySet().removeIf(...)。坑二把数组当集合用把集合当数组用。对基本类型数组调用Arrays.asList时泛型推断会把整个数组当成一个元素得到的是Listint[]而不是List 。这个问题的原因是Java泛型不支持基本类型。处理办法是用包装类型数组或者用Arrays.stream(arr).boxed().collect(Collectors.toList())。坑三用比较Integer。在-128到127之间的Integer会走缓存池用比较返回true超过这个范围就是false。判断相等必须用equals。这个坑和数据结构本身无关但极常见尤其在从HashMap里取出的Integer做比较时容易中招。坑四LinkedList当队列用还觉得它快。LinkedList确实实现了Queue接口但做高频入队出队时它的节点频繁创建和回收GC压力大性能不如ArrayDeque。如果还要考虑线程安全直接上ConcurrentLinkedQueue。选型时不要只看“它实现了哪个接口”要看底层结构适合什么操作模式。坑五自定义对象放进HashSet/HashMap不改hashCode。如果你不重写hashCode两个“逻辑上相等”的对象会得到不同的哈希值导致Set里出现重复元素Map里get不到之前put的值。重写hashCode的同时必须重写equals并在对象作为key期间不要修改影响hashCode的字段。这个错误非常隐蔽出了问题还很难排查。4.3 用好数据结构优化性能一个完整排查案例分享一个我做过的真实性能排查非常典型。一个统计报表接口接收一批用户ID要返回这些用户的订单汇总信息。上线后发现用户ID数量到几千时接口耗时飙到5秒以上。初步排查发现代码里有这么一段逻辑遍历用户ID对每个ID都调用一次订单表查询然后把结果存入ArrayList。这个写法问题很明显一条SQL查一次的N1次查询数据库往返都耗在网络和SQL解析上。修复方式是改批量查询一次把几千个ID都查出来。但修完还是慢。继续看发现批量查询的结果被放进ArrayList后后续要频繁判断“某个订单属于哪个用户”代码用了一个双层循环嵌套也就是对每个订单遍历一遍用户列表复杂度O(n*m)几千乘几千就是几百万次比较能不慢吗。修复方式就是把这个用户列表放进HashMap以userId为key用户对象为value然后用订单里的userId直接get复杂度降到O(m)。就这么一个小改动接口耗时从5秒降到了200毫秒以内。这个案例说明有时候性能瓶颈不是SQL也不是框架就是容器选错、复杂度算错。这个案例每次讲给学员听我都强调一句话写代码之前先算一下这个操作的复杂度O(n)和O(n^2)在小数据量时看不出来数据量一大就是天壤之别。HashMap、HashSet这类哈希结构用空间换时间是日常优化性价比最高的手段之一。5. 一条务实的学习路线和踩坑心得5.1 从零到面试通过我推荐这样学如果你现在刚开始准备Java基础数据结构我建议按下面这个顺序走每步都配合动手写代码。第一步先把数组彻底搞懂。数组是后面所有结构的地基。你可以写一个动态数组的模拟类实现自动扩容这个练习能帮你理解ArrayList源码里最关键的那部分逻辑。第二步学链表。手写一遍单链表的增删再看LinkedList源码理解双向链表。链表是后面栈、队列、哈希表、树的公共基础指针的“指向”概念一定要亲手敲出来才有感觉。第三步学栈和队列。用Deque接口写几个经典题有效的括号、用队列实现栈、用栈实现队列。这几个题做完你对两个结构的特性就了然于胸。第四步学哈希表。这个阶段不建议一上来就啃HashMap源码而是先用HashMap解决一些业务模拟问题比如统计词频、两数之和。之后再逐步深入源码看hash扰动、put流程、扩容。第五步学树和堆。先学二叉树遍历前序、中序、后序、层序然后了解二叉搜索树最后理解红黑树的约束条件。堆的练习可以从PriorityQueue开始做几道TopK和合并K个有序链表的题。整个流程走下来我的建议是给自己定一个期限三到四周比较合理。不要追求一次吃透数据结构是螺旋上升的先建立框架再逐步加深。每次做算法题碰到不熟悉的结构就回头翻对应的源码反复几次自然就熟了。5.2 我在实际学习中的几个体会最后分享几个这些年我切身感受到的体会都是很难从课本里学到的。体会最深的一点是不要用“会不会调用API”代替“懂不懂底层”。你调了三年HashMap不如把它的put流程完整读一遍。面对线上棘手问题的时候能快速想到用Heap做优先级、用Hash做去重、用Tree做排序靠的是底层原理不是API熟练度。第二点是算法题不刷不行但刷题不等于学数据结构。数据结构是“骨架”算法是“操作手法”。很多人陷入刷题焦虑疯狂刷了500道却连ArrayList扩容机制都说不清楚这等于盖楼不打地基。建议刷题归刷题源码阅读归源码阅读两条腿走路。第三点是每个新手都会有一段“会用但不懂”的时期不用焦虑。我当年用HashMap写业务写了快一年才真正理解为什么它无序为什么会有红黑树。那些你现在觉得晦涩的源码细节随着项目经验积累会在某一天突然豁然开朗。如果你现在正卡在某些概念上别急先把基本用法用熟带着问题再回头学效果反而更好。基础数据结构这条路说长不长说短不短。把数组、链表、栈、队列、哈希表、树、堆这些结构彻底吃透你后面学并发、学JVM、学框架源码都会轻松很多。希望这篇博文能帮你把这条路的起点铺平剩下的就靠你亲手敲代码去验证了。