Array List
Array List的底层原理
-
底层是数组:
ArrayList
是对数组进行了封装,底层就是数组。这使得它具有数组的优点和缺点。 -
默认初始化容量为10:在第一次创建集合并添加第一个元素时,在底层会创建一个默认长度为10的数组。当元素个数超过当前数组长度时,会触发扩容操作。
-
扩容机制:扩容时,会创建一个新的数组,长度通常是旧数组的1.5倍(即原容量的1.5倍),然后将原数组中的元素复制到新数组中。这种方式可以避免每次添加新元素都需要进行数组的扩容操作,提高了性能。
查询快
ArrayList
通过索引来直接访问元素,因为底层是数组,所以查询速度非常快,时间复杂度为O(1)。
增删慢
由于底层是数组,所以在插入或删除元素时,需要将插入点后的所有元素向后移动,这会导致较高的时间复杂度,特别是在数组的中间或开头插入或删除元素时。
线程不安全
ArrayList
是线程不安全的,这是因为其底层数组的非同步特性。在多线程环境下使用时,需要通过外部同步手段来保证线程安全性。
Linked List
Linked List的底层原理
-
双向链表结构:
LinkedList
的底层是双向链表结构,每个节点对象除了包含指向下一个节点的引用(next
)外,还包含指向前一个节点的引用(prev
)。
-
底层原理:动态地插入和删除节点,因为只需修改相邻节点的指针,而不需要移动大量元素。链表中的元素不需要连续的内存空间,因此插入和删除操作的效率相对较高。
- 查询慢:由于链表的特性,需要从头节点开始逐个遍历,因此随机访问性能较差。
- 增删快:
- 添加:当向
LinkedList
中添加元素时,会创建一个新的节点对象,并调整相邻节点的指针,将新节点插入到合适的位置。 - 删除:当从
LinkedList
中删除元素时,会找到目标节点并调整相邻节点的指针,然后释放目标节点的引用,使其可以被垃圾回收。
- 添加:当向
Hash Map
Hash Map的底层原理
在JDK 1.8之后,HashMap
是基于数组、链表和红黑树的组合实现的(在1.8之前,仅使用数组和链表)。HashMap
以键值对的方式进行存储。
底层原理
当调用new HashMap()
时,底层并不会立即创建数组。只有在首次调用put()
方法时,才会初始化一个长度为16的数组。初始数组长度和加载因子决定了HashMap
的扩容阈值(threshold),即数组中存储的元素数量超过阈值时会触发扩容。扩容时,数组容量扩大为原来的两倍,并通过rehash()
方法重新分配现有元素的位置。
- 默认数组长度:16
- 默认加载因子:0.75
扩容机制
扩容机制确保HashMap
在存储更多元素时仍然保持高效的性能。扩容通过以下步骤实现:
- 创建新数组:容量是旧数组的两倍。
- 重新计算哈希值:遍历旧数组中的所有元素,根据新的数组容量重新计算它们的索引位置。
- 移动元素:将每个元素移动到新数组中计算出的索引位置。
Hash Map的判断机制
- 链表转红黑树:当链表长度大于8时,如果哈希表的长度小于64,会选择扩容而不是转为红黑树。如果哈希表长度大于等于64,链表会转换为红黑树以提高查询效率。
- 红黑树退化为链表:当红黑树的节点数量小于6时,红黑树会退化为链表。这是因为在节点较少时,链表操作比红黑树更高效。
put()方法原理
- 计算哈希值:通过
key
的hashCode()
方法计算哈希值。 - 计算数组下标:通过哈希算法将哈希值转换为数组下标。
- 处理冲突:如果数组当前位置没有元素,直接插入。如果当前位置有链表,遍历链表进行
equals
比较:- 如果没有相等的
key
,将新元素追加到链表尾部。 - 如果找到相等的
key
,更新对应节点的值。 - 如果链表长度超过8且哈希表长度大于64,将链表转换为红黑树。
- 如果没有相等的
get()方法原理
- 计算哈希值:通过
key
的hashCode()
方法计算哈希值。 - 计算数组下标:通过哈希算法将哈希值转换为数组下标。
- 查找元素:如果数组当前位置没有元素,返回
null
。如果当前位置有链表,遍历链表进行equals
比较:- 如果没有相等的
key
,返回null
。 - 如果找到相等的
key
,返回对应的值。
- 如果没有相等的
前位置有链表,遍历链表进行equals
比较:
- 如果没有相等的
key
,返回null
。 - 如果找到相等的
key
,返回对应的值。