在具体说集合框架之前,要先补充一下关于数据结构的知识点。

##栈##

一种先入后出的线性表,存入元素为入栈,从底部开始存储,取出元素为出栈,从顶部开始取出。

##队列##

一种先入先出的线性表,从一侧入队,从另一侧出队,就像火车过山洞。

##数组##

固定长度的有序结构,在内存中开辟一段连续的空间,存储同一种类型的元素,每个元素空间大小一致并赋值索引,增删改查都是通过索引进行。

查询时性能较好,增加元素时直接在尾部添加,但是对于底层为数组的集合,在指定索引处新增或删除时,为了保证索引的连续性与其原顺序,会将指定索引之后的所有元素进行复制,等指定索引位置元素处理完毕后,再将之前复制的元素们对接上去,维护其有序特性。但是这样就造成了较大的内存开销,性能较慢。

##链表##

链表(linked list)的组成元素是节点(node),其主要结构有两部分,一部分是数据域,一部分是指针域。

如下图是单链表的结构,只有一个指针域,初始化数据的插入方式有两种,头插法和尾插法,head指针指向头节点,tail指针指向尾节点。




Java详情页展示上一条下一条 java上一条数据下一条数据_Java详情页展示上一条下一条


头插法是将第一个节点作为头节点,头节点的指针域指向后续新节点,从头节点向后增加,最后面的节点是尾节点,尾节点指针域置空。

尾插法是将第一个节点作为尾节点,指针与置空,后加的元素令其指针域指向尾节点,从后向前添加,最新的节点为头节点,其指针域指向次新的节点。

-查询慢:只能通过指针域,从头到尾逐个遍历,因此查询操作的性能低。

-增删元素快:

  • 增加元素:增加元素的本质就是修改插空位置相邻的指针域,具体怎么修改要看情况而定。

如果是在起始位置增加元素,只需要将新加元素的指针域指向原头节点,在将head指向新节点。

如果是在中间位置加元素,则要把原有的链接打断,先将新节点指针域指向后一个节点,再将前一个节点的指针域指向新的节点。

如果是在尾部位置加元素,先将原尾部节点指针域指向新元素,再将tail指向新节点同时置空新节点指针域。

  • 删除元素:删除元素与新增元素操作性质相同,都是修改调整指针域的指向,将指定节点从链表中去除。

如果是在起始位置删除元素,只需要将head指针指向下一个节点。

如果是在中间位置删除元素,修改前一个节点的指针域指向下一个节点。

如果是在结尾位置删除元素,将尾节点前一个节点指针域置空,并将tail指向新的尾节点。

【注意:删除只是将节点从链表里除去了,就是从head指针逐个遍历到tail指针,期间找不到被删除节点了,但是节点本身是存在的,这个应该是由jvm的垃圾处理机制处理的,后续再讨论。】

##红黑树##

树(tree)是一种非常重要的数据结构,其中规定每个节点不超过2的有序树称为 二叉树(binary tree)。

  • 二叉查找树(binary search tree,BST)的特性

1)左子树上所有节点的值均小于或等于它的根节点的值;

2)右子树上所有节点的值均大于或等于它的根节点的值;

3)左/右字数也分别为二叉查找树。


Java详情页展示上一条下一条 java上一条数据下一条数据_子树_02


这中二叉查找树的优点是,在查找时,利用二分法思想减少查找次数,其最大的查找次数为输的高度。比如上图的二叉查找树,要查找10这个值,首先判断根节点9<10,应在右子树,节点为13>10,应在13左子树,节点为11>10,应在11左子树,节点为10符合。总共的查找次数为4。

插入节点的时候,依旧按照这个规则,找到适合的插入位置。

但是二叉查找树的规则还是有漏洞的,当插入新元素时,当连续插入新节点时,容易出现不平衡的情况。

当在原有二叉树的基础上连续插入7,6,5,4,3,就会出现不平衡,如下图。为了解决这个问题,就有了红黑树。


Java详情页展示上一条下一条 java上一条数据下一条数据_子树_03


  • 红黑树

除了具备二叉查找树的特性外,新加了以下规则:

1.节点是红色或黑色。

2.根节点是黑色。

3.每个叶子节点都是黑色的空节点(NIL节点)。

4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)

5.从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。


Java详情页展示上一条下一条 java上一条数据下一条数据_红黑树_04


看起来挺复杂的规则,但这样保证了红黑树的自平衡,且从根到叶子的最长路径,不会超过最短路径的2倍。

红黑树的查询效率很高,插入效率按理说也高,但是由于其规则的限制,当插入新节点时,可能会出现破坏规则的情况,这时候有两种办法,一种是变色,就是为了符合规则, 变换原来部分节点的颜色,一般从下往上走,但当遇到根节点颜色冲突时,就要用另一种方法----旋转,来解决问题了。具体两种方法的执行过程比较复杂,尽量理解吧。

我们以插入节点21的情况为例:


Java详情页展示上一条下一条 java上一条数据下一条数据_java基础数据结构_05


当21为红色时,相邻的22也是红色,违规,这时候可以先变色处理,将22与27变黑,将25变红


Java详情页展示上一条下一条 java上一条数据下一条数据_子树_06


但是25与17又冲突,如果让17黑,13变红,就违规了,所以这里要用旋转,让17作为根节点,进行左旋转,且进行变色调整:


Java详情页展示上一条下一条 java上一条数据下一条数据_子树_07


还没完,这里每条路的黑色节点个数不是相同的,违规,要对8与13进行右旋转并变色。


Java详情页展示上一条下一条 java上一条数据下一条数据_查找树_08


总结下来,整个过程为:变色 -> 左旋转 -> 变色 -> 右旋转 -> 变色

这里旋转的操作其实就是父节点与其子节点其中之一换位置,父节点换到左子树就是左旋转,反之是右旋转。当位置互换后,各自原有的左右子树要以其本身为基点再次进行大小判断,分配到左右两侧。

这里只是一种情况,会出现的意外有很多,过程也有些复杂,不多说了。

  • 红黑树的应用

红黑树的应用很多,其中jdk的集合类TreeMap和TreeSet底层就是红黑树,java8中,HashMap也用到了红黑树。