Java集合是咱们使用最频繁的工具,也是面试的热点,但咱们对它的理解仅限于使用上,并且大多数状况没有考虑过其使用规范。本系列文章将跟随源码的思路,分析实现的每一个细节,以期在使用时避免各类不规范的坑。在这里,咱们会惊艳于开发者优秀的设计,也会感激先辈们付出的艰辛努力,更重要的是知其因此然,少犯错误,写出优秀的代码。git
许多人对集合类的理解是暴力的,当须要保存对象时就使用ArrayList
,当须要保存键值对时就使用HashMap
,当须要不可重复时就使用HashSet
,等等。并且使用方式也比较单一:github
List<String> list = new ArrayList<>();
Map<String, String> map = new HashMap<>();
Set<String> set = new HashSet<>();
// ...
复制代码
这里咱们先不考虑多线程安全问题,这个问题一般有专门的类实现,或者能够经过Collections.synchronizedXXX
方法解决。除此以外,咱们真的能够如此简单的使用集合吗?面试
假如数据只有几百、几千个,那么使用何种方式实现差异并不大。但当咱们须要处理大数量级的数据时,采用不一样的方式效率可能相差百倍甚至更多,这种状况下性能将变得格外重要。例如分别存储于ArrayList
和LinkedList
的100万条数据,要获取位于位置 i 的元素,前者能够瞬间完成,后者则可能须要数秒。这时,使用哪一个集合类,怎样合理使用就是咱们必须掌握的技能了。数据库
若是你也像以上这般使用集合,或者不知道如何优化集合的使用,你都应该读本系列文章。若是你仅有一些点不清晰,也能够在这里找到答案。或者你只是不想阅读枯燥的源码,却对原理很好奇,你也能够阅读本系列文章。若是你只是想应付面试,我想当你坚持把这些文章读完后,你会以为面试好像也不那么重要了。编程
本系列文章立足于深入理解Java集合的原理与实现,读完这些文章后你将得到如下知识:数组
大量的数据结构知识。安全
ArrayList有那么多构造函数,使用不一样的构造函数会有区别吗?bash
ArrayList是如何扩容的?微信
LinkedList如何提供经过位置获取数据的功能的,它的查询效率真的很是低吗?数据结构
用数组能够实现队列吗?
影响HashMap性能的因素有哪些?
复杂的红黑树是如何实现的?
LRUCache的底层原理是什么?
……
对数据的操做,大抵就是增、删、改、查,以及在某些时候根据位置获取数据,有时可能还须要进行排序。改和查又能够理解为一致的操做,由于要修改一条数据须要先找到它,而后替换便可。接下来咱们就从增、删、查这三点简要分析下当前使用比较普遍的几种数据结构。
数组在内存中占据一段连续的内存,全部的数据在内存中连续排列。它的大小是固定的,这一特性使得数组对于插入操做并不友好,咱们分析ArrayList
时就会看到这种操做的复杂。但数组对于位置的访问是极其友好的,它支持所谓RandomAccess
特性,这使得基于位置的操做能够迅速完成,其时间复杂度为O(1)。数组的数据顺序与插入顺序一致,因此查询操做须要遍历,其时间复杂度为O(n)。
因此数组最大的优点在于基于位置的访问,在扩展性方面表现无力。
不一样于数组,链表是经过指针域来表示数据与数据之间的位置关系的,因此链表在头部或尾部插入数据的复杂度仅为O(1)。链表不具有RandomAccess
特性,因此没法提供基于位置的访问。其查询操做也必须从从到尾遍历,复杂度为O(n)。
因此链表最大的优点在于插入,而查询的表现很通常。
那有没有一种结构可以结合数组和链表的优势,使得查询和插入都具备优秀的表现呢?答案是确定的,这就是散列表。
散列表就是Hash Table,这种结构使用key-value
形式存储数据,咱们常用的HashMap
、HashTable
就基于它。
数组和链表在查询时表现通常的缘由在于它们并不记得数据的位置,因此只能用待查询的数据和存储的数据依次比对。散列表使用一种巧妙的方式来减小甚至避免这种依次比对,它的原理是经过一个函数把任何的key转为int,每次查找时只须要执行一次这个函数即可以迅速定位。这个过程是否是像查字典呢?
散列表并不像上述那般完美,由于并不会有一个函数,可以保证全部的key转换结果都不一样,也就是会发生所谓的哈希碰撞
,并且它必须依赖于其余的数据结构,这部分知识会在后续文章中详细介绍。
良好设计的散列表可使增、删、查等操做的时间复杂度均为O(1)。
二叉排序树是解决查询问题的另外一方案,若是数据在插入时是有序的,在查询时就可使用二分法。二分法的原理很简单,好比猜一个在0-100之间的数,第一次猜50就能够直接排除一半的数据,每次按照这个规则就能够很快的获取正确答案。二分法的时间复杂度为O(lg n)。
树的结构对二分法有自然的支持(但这不是树最重要的用途)。二叉排序树牺牲了一部分插入的时间,但提升了查询的速度,同时有序的数据也能够作些其余的操做。若是查询的操做重要性超过了插入,咱们应该考虑这种结构。二叉排序树也存在一些不平衡致使效率降低的问题,因此有了AVL树、红黑树,以及用于数据库索引的B树、B+树等概念,关于二叉排序树的知识也会在后续文章中介绍。
以上介绍的数据结构的知识是咱们理解Java集合类的基础,掌握这些核心原理,咱们分析集合类源码时才不会吃力,咱们会先对这些数据结构进行简要介绍,其余和本系列文章无关的概念不会涉及,你们能够查阅相关专业书籍进行系统学习。
因为集合类的源码十分庞大,从接口抽象设计到具体实现涉及到数十个类,咱们不可能每行代码都进行分析,一些在前面分析过的点在后续部分也会略过,但对于咱们应该注意的点都会详细解读。有一些过于复杂的代码,还会用图示进行直观的演示,以帮助理解整个运行机制。
文章中会不可避免地粘贴大量源码,但全部部分都会加上详细的中文注释。另外,粘贴的代码不会截取(某些不必的会删除),这样便于理解,而不用想看哪行代码再去源码中寻找了。
学习源码的实现仅是咱们的目的之一,咱们更应该掌握做者优秀的编程思想,理解这样作的初衷,站在更高的角度思考问题。
本系列文章的源码所有基于JDK1.8,不一样版本的实现代码可能稍有差异,但核心思想是一致的,但愿你们不要被具体的实现带偏了路。
Java集合类分为两大部分:Collection和Map。Collection又主要由List、Queue和Set三大模块组成。本系列文章也会基于这样的结构进行,咱们会先了解一些用到的数据结构,而后按照从接口抽象到具体实现的顺序来一步步揭开集合的神秘面纱。
因为Set的结构与Map彻底一致,且Set的内部都是基于对应的Map实现的,因此只须要知道Set是什么便可,其具体实现若是感兴趣能够自行阅读源码。
本系列文章不考虑多线程安全问题,与多线程相关的问题十分复杂,之后会对它专门研究。
本系列文章长达20多篇,所有读完须要必定的耐心,可是我相信读完对数据结构和集合必定会有更深的理解,在使用时须要注意哪些点也必定会成竹在胸。
另外因为我的能力有限,文章中如有表达不清晰或解释错误的部分,但愿各位看官可以给予批评指正。
本系列文章会按照下述结构搭建:
数据结构
Iterable概述
Collection概述
List系列分析
Queue系列分析
Map概述与系列分析
Set简介
Java集合源码分析之基础(五):平衡二叉树(AVL Tree)
Java集合源码分析之Queue(三):ArrayDeque
Java集合源码分析之Map(三):接口NavigableMap
Java集合源码分析之Map(六):LinkedHashMap
本系列文章所有更新完毕,感谢您的关注~
本文到此就结束了,若是您喜欢个人文章,能够关注个人微信公众号:大大纸飞机
或者扫描下方二维码直接添加:
您也能够关注个人github:https://github.com/LtLei/articles
编程之路,道阻且长。惟,路漫漫其修远兮,吾将上下而求索。