集合的底层原理

ArrayList 是一个动态数组,实现了 List 接口以及 list 相关的所有方法,它允许所有元素的插入,包括 null。另外,ArrayList 和 Vector 除 了线程不同步之外,大致相等
ArrayList:
无参构造,默认长度为0,只有当使用的时候,扩容到默认大小10,add方法开始前,会判断原数组长度是否扩容(所谓扩容也仅仅是创建原数组长度1.5倍的新数组(通过System.arraycopy()方法创建新的数组),并复制原数组含有的所有存的内容),若扩容就调用grow()方法。remove 方法和 add()方法类似,通过调用System.arraycopy()方法,截取一部分数组。为何需要调用arraycopy方法?注意通用性,主观想法是只删除一个元素,但是对于通用性来说,还可以删除一段元素。同时如果删除中间一段元素的话,若在原数组上面改动,耗费效率过高,还不如创建一个新的数组。注意:源代码有一个细节点,使用arraycopy()之后,将 elementData[–size] = null; 此语句能够让gc回收原数组。
Vector :
很多方法都跟 ArrayList 一样,只是多加了个 synchronized 来保证线程安全罢了。
不同点:
Vector 创建时的默认大小为 10。 Vector 每次扩容都以当前数组大小的 2 倍去扩容。当指定了 capacityIncrement 之 后,每次扩容仅在原先基础上增加 capacityIncrement 个单位空间。
ArrayList 是非线程安全的,Vector 是线程安全的。
LinkedList:底层结构为双向链表 ,同时,它是线程不同步的
其中搜索方法有一个非常重要的元素,首先判断目标位置在整个链表位置的中心的前面还是后面,再决定是从头遍历还是从尾便利
相比数组,链表的特点就是在指定位置插入和删除元素的效率较高,但是查找的 效率就不如数组那么高了
HashMap 的实现原理
get()方法实现原理:
1、通过 hash 值获取该 key 映射到的桶。
2、桶上的 key 就是要查找的 key,则直接命中。
3、桶上的 key 不是要查找的 key,则查看后续节点: (1)如果后续节点是树节点,通过调用树的方法查找该 key。 (2)如果后续节点是链式节点,则通过循环遍历链查找该 key
put 方法比较复杂,实现步骤大致如下: 1、先通过 hash 值计算出 key 映射到哪个桶。 2、如果桶上没有碰撞冲突,则直接插入。 3、如果出现碰撞冲突了,则需要处理冲突: (1)如果该桶使用红黑树处理冲突,则调用红黑树的方法插入。 (2)否则采用传统的链式方法插入。如果链的长度到达临界值,则把链转变为红 黑树。 4、如果桶中存在重复的键,则为该键替换新值。 5、如果 size 大于阈值,则进行扩容。 loadFactor是装载因子,表示HashMap满的程度,默认值为0.75f ,阈值默认为(0.75*hashmap容量),太小会造成资源的浪费,太大会造成hash collision 太多,使hashmap的效率降低,当size大于阈值,就会发生扩容,不管是用红黑树,还是链表都是为了处理hash collision容量:是指桶的个数,size是指该hashmap里元素的多少
hash()方法实现:
这个 hash 方法先通过 key 的 hashCode 方法获取一个哈希值,再拿这个哈希值与它 的高 16 位的哈希值做一个异或操作来得到最后的哈希值,计算过程可以参考下图。 为啥要这样做呢?注释中是这样解释的:如果当 n 很小,假设为 64 的话,那么 n-1 即为 63(0x111111),这样的值跟 hashCode()直接做与操作,实际上只使用了哈希 值的后 6 位。如果当哈希值的高位变化很大,低位变化很小,这样就很容易造成冲 突了,所以这里把高低位都利用起来,从而解决了这个问题
resize 方法 HashMap 在进行扩容时,使用的 rehash 方式非常巧妙,因为每次扩容都是翻倍,与 原来计算(n-1)&hash 的结果相比,只是多了一个 bit 位,所以节点要么就在原来 的位置,要么就被分配到“原位置+旧容量”这个位置。 例如,原来的容量为 32,那么应该拿 hash 跟 31(0x11111)做与操作;在扩容扩到 了 64 的容量之后,应该拿 hash 跟 63(0x111111)做与操作。新容量跟原来相比只 是多了一个 bit 位,假设原来的位置在 23,那么当新增的那个 bit 位的计算结果为 0 时,那么该节点还是在 23;相反,计算结果为 1 时,则该节点会被分配到 23+31 的 桶上。 正是因为这样巧妙的 rehash 方式,保证了 rehash 之后每个桶上的节点数必定小于等 于原来桶上的节点数,即保证了 rehash 之后不会出现更严重的冲突。
总结: 按照原来的拉链法来解决冲突,如果一个桶上的冲突很严重的话,是会导致哈希表 的效率降低至 O(n),而通过红黑树的方式,可以把效率改进至 O(logn)。相比 链式结构的节点,树型结构的节点会占用比较多的空间,所以这是一种以空间换时 间的改进方式
HashSet:
对于 HashSet 而言,它是基于 HashMap 实现的,HashSet 底层使用 HashMap 来保存 所有元素,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底 层 HashMap 的相关方法来完成。

猜你喜欢

转载自blog.csdn.net/qq_52696089/article/details/121201082