文章目录
本文是我在浏览了常用容器的源码后,对他们的底层数据结构、重要属性、扩容流程,增加元素,获取元素,删除元素的方法进行的一个梳理。
List
ArrayList
1.底层数据结构 + 成员变量
elementData(Object[]):存放数据
size(int):当前集合元素个数
elementData.length(int):当前集合容量
DEFAULT_CAPACITY(int):集合默认最小容量为 10
2.添加元素 + 扩容
扩容:(1).计算当前需要的最小容量,与数组长度比较。
(2).大于就扩容,小于就不扩容。
(3).需要扩容:默认扩容为之前的1.5倍, 如果当前需要的最小容量大于默认扩容后的大小
则扩容为当前需要的最小容量, 最后调用Arrays.copyOf方法将老数组拷
贝到新数组。
添加元素:(4).在最后一个元素后面添加一个元素
3.获取元素
直接通过数组下标返回元素
4.删除元素
(1).要删除的元素为 null
(2).要删除的元素不为 null
删除过程:确定删除元素的下标,然后通过
System.arrayCopy(src数组,srcPos,dest数组,desPos,移动个数)将后部分元素移动上来,
覆盖要删除的元素,完成删除
5.遍历容器
for循环遍历
Vector
Vector的底层结构也是数组,它的添加、获取、移除元素的操作与ArrayList基本相同。
他们的不同点在与Vector的方法基本都是由Synchronized关键字修饰
LinkedList
LinkedList
底层数据结构:双向链表
链表节点为 LinkedList 的静态内部类 Node<T>
linkedList 会维护链表的头和尾节点
变量:
head:头节点
last:尾节点
size:链表当前长度
LinkedList 的应用主要分为两个部分
作为List:
没有指定操作的index时
1.添加元素:链接在链表末尾
2.获取元素:(必须指定index)
3.移除元素:从头到尾遍历链表,寻找和传入元素相同值的节点,相同则删除
指定操作的index
这里比较特别的是通过index去寻找节点时(node(index (int))方法),会通过size先判断index是在链表的前半段和后半段,
如果在前半段就从前向后找,后半段就从后向前找。
找到节点后,就进行相应的添加,删除,获取元素操作
作为Deque:就是简单的节点插入、移除、获取头部尾部节点的操作
peek
peekFirst
peekLast
作为stack
push
pop
作为queue
offer
poll
作为双向队列
offerFirst
offerLast
pollFirst
pollLast
CopyOnWriteArrayList
CopyOnWriteArrayList 是一个并发安全的ArrayList
它的底层也是一个数组(Object[]),通过ReentrantLock来保证并发安全
它没有size属性来表示长度,它的元素个数和数组长度始终保持一致
它如何保证并发安全?
他是通过写时复制来实现并发安全的
1.对于add,remove等会改变数据的操作,会先用ReentrantLock加锁,然后由原数组拷贝一个新数组出来
在新数组上进行操作,然后再将新数组赋值给原数组,这样就可以保证每次只有一个线程在对数组进行增删数据
,不会出现并发问题
2.对于get,直接对当前数组进行读取,由于写时复制,不会受到add,remove操作的影响
Queue
PriorityQueue
Set
HashSet
LinkedHashSet
TreeSet
Map
HashMap
/**
* HashMap:
* 底层数据结构:
* 数组+链表+红黑树(当 capacity >= 64 && 该节点位置的数量达到树化阈值)
* 主要成员变量:
* table(Node<K,V>[]):Node数组(默认容量16)
* size(int):HashMap当前节点数量
* loadFactor:负载因子(默认0.75),当前map节点数量达到capacity的0.75时就会扩容
* threshold:等于capacity * loadFactor,当map的节点数量达到threshold就会扩容
*
* get:
* 1.计算传入key的hash(方法:高16位和低16位进行异或)
* 2.通过table.length & hash找到节点可能存在的桶位
* 3.先比较首节点是不是要找的节点(同时比较 hash和 key)
* 4.如果不是就遍历这个桶位上的节点,
* 1.该桶位是链表
* 2.该桶位是红黑树
* 5.找到就返回value,没有找到就返回null
*
* put:
* 1.如果table没有初始化就先初始化
* 2.计算key对应的桶位,如果桶位为空就直接创建新节点放入
* 3.如果对应桶位是红黑树且无节点的key和hash与要插入的节点相同,找到插入位置创建新节点插入
* 4.如果对应桶位是链表,在链表尾部创建新节点插入
* (1).链表长度达到树化阈值(8)且table长度达到树化阈值(64),链表树化
* (2).否则将table容量扩大到之前的两倍
* 5.处理存在节点hash和key与要插入节点相同的情况
* (1).OnlyIfAbsent值为true则不替换
* (2).为false或者节点value则替换
* 6.如果table容量超过扩容阈值,则进行扩容(resize)
*
* resize:(初始化或扩容)
* 有两种情况会进行resize,
* 1.HashMap没有初始化:
* 初始化table数组,设置扩容阈值(threshold)
* 2.HashMap需要进行扩容:
* (1).计算新的table大小和新的扩容阈值
* (2).创建新的table数组
* (3).遍历老数组,逐个桶位迁移元素
* [1].当前桶位只有一个元素:计算在新table桶位,迁移
* [2].当前桶位为链表:拆分成低位链和高位链,计算在新table的桶位,放入新table
* [3].当前桶位为红黑树:拆分成低位链和高位链,并判断是否需要树转链,需要则转化
* 计算在新table的桶位,放入新table
* (4).将table替换为新table
*
* remove
* 1.计算删除节点的桶位
* 2.遍历桶位寻找与要删除key相同的节点
* (1).没找到,返回null
* (2).桶位首节点是要删除节点,将桶位首节点变为下一个节点
* (3).链表中一个节点是要删除节点,按照链表方式删除
* (4).红黑树中一个节点是要删除节点,按照红黑树的方式删除节点
*
* size
* 返回size大小
* empty
* 判断size是否等于0
*/