Java创建的数据结构有Collection和Map。Collection分为List、Set、Queue。版本是jdk1.8
目录
1.List:有序集合,顺序插入元素,允许相同的值(ArrayList、LinkList、Vector、Stack)
2.Set:无序性集合,不允许相同的值(有序和无序不是指排序,而是遍历的时候,先插入的先遍历就是有序)(HashSet、TreeSet、LinkHashSet)
3.Queue:队列:是一个先入先出(FIFO)的数据结构(PriorityQueue、ConcurrentLinkedQueue 、ArrayBlockingQueue、LinkedBlockingQueue 、PriorityBlockingQueue、DelayQueue、SynchronousQueue )
4.Map:存储键值对,形式为K-V。
1.List:有序集合,顺序插入元素,允许相同的值(ArrayList、LinkList、Vector、Stack)
1.ArrayList:底层由数组实现,可以认为ArrayList是一个可改变大小的数组。随着元素增加会自动扩容,扩容策略是每次增大50%。线程不安全。如果需要线程安全,可以使用List list=Collections.synchronizedList(new ArrayList());允许插入空值null
2.LinkList:底层由双向链表实现,线程不安全。与ArrayList的区别就是数组和链表的区别。数组查询快,可以通过下标直接访问,而删除、插入慢,因为需要移动数据。链表相反,删除插入可以直接操作链表的指针(每一个元素含有上一个和下一个元素的引用)。允许插入空值null
3.Vector:底层有数组实现,线程安全,每个操作都会加锁。扩容策略,每次增加一倍。允许插入空值null。
4.Stack:栈,先进后出。继承于Vector,底层有数组实现。线程安全。
2.Set:无序性集合,不允许相同的值(有序和无序不是指排序,而是遍历的时候,先插入的先遍历就是有序)(HashSet、TreeSet、LinkHashSet)
1.HashSet:底层通过HashMap实现的。所有操作都是通过内部的HashMap操作的(判断值相同的原理见下面HashMap)。扩容策略,对内部维护的HashMap的加载因子是使用默认的0.75,且默认的Entry数组大小根据不同的情况确定,无参则是16,有参则是在集合数量的2倍,同16之间取最大值。允许插入空值null。线程不安全。
2.TreeSet:底层是通过TreeMap实现的,基本与HashSet一样。不同的是TreeSet有排序功能,分为自然排序和自定义排序两类,默认是自然排序;在程序中,我们可以按照任意顺序将元素插入到集合中,等到遍历时TreeSet会按照一定顺序输出--倒序或者升序。允许插入空值null。线程不安全。
3.LinkHashSet:底层是通过LinkHashMap实现的。与其他Set不同,LinkHashSet有序。同样它也允许插入空值null。线程不安全。
3.Queue:队列:是一个先入先出(FIFO)的数据结构(PriorityQueue、ConcurrentLinkedQueue 、ArrayBlockingQueue、LinkedBlockingQueue 、PriorityBlockingQueue、DelayQueue、SynchronousQueue )
1.PriorityQueue:优先级队列,底层有数组实现的小顶堆实现。PriorityQueue的逻辑结构是一棵完全二叉树,存储结构其实是一个数组。逻辑结构层次遍历的结果刚好是一个数组。优先队列中不能存放空元素。压入元素后如果数组的大小不够会进行扩充,大小默认初始值为11的数组(也可以赋初始值)。不阻塞队列。线程不安全。
offer操作:将元素插入最后一个位置,然后进行siftUp操作,该方法从k
指定的位置开始,将x
逐层与当前点的parent
进行比较并交换,直到满足x >= queue[parent]
为止。注意这里的比较可以是元素的自然顺序,也可以是依靠比较器的顺序。
poll操作:将第一个元素弹出,然后进行siftDown操作,该方法的作用是从k
指定的位置开始,将x
逐层向下与当前点的左右孩子中较小的那个交换,直到x
小于或等于左右孩子中的任何一个为止。
关键源码解析
// 移除指定位置的数据,这个位置不是优先级排列,而是数组下标
private E removeAt(int i) {
// assert i >= 0 && i < size;
modCount++;
int s = --size;
// 如果是最后一个元素直接移除
if (s == i)
queue[i] = null;
else {
// 将最后一个元素放在移除的位置,将最后的位置置为null
E moved = (E) queue[s];
queue[s] = null;
// 进行向下调整结构
siftDown(i, moved);
// 调整完成之后,会判断queue[i] == moved,如果为true表示新增元素之后并没有调整结构(满足堆结构)
// 那么就会尝试向上调整结构。(如果向下调整过结构自然是不需要再向上调整了),
// 如果queue[i] != moved值为true表示向上调整过结构,那么将会返回moved。
if (queue[i] == moved) {
siftUp(i, moved);
// 代表进行了向上调整
if (queue[i] != moved)
// 之所以要返回被移动的元素是因为迭代器的需要
// 如果迭代器在迭代的过程中删除了元素。则需要讨论一些特殊的情况。
// 最末未元素进行了下滤操作,不需要考虑因为迭代器是按照顺序进行的。 下滤的元素必然会被之后的迭代器迭代到
// 最末未元素进行了上移。这需要用另一个堆来保存它。因为上滤的元素不会被之后的迭代器迭代到。
// 所以这里返回被移动的元素,供迭代器使用。迭代器会把上移过的元素放到一个单独队列里。
return moved;
}
}
return null;
}
siftDown操作
// siftDown操作
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
int half = size >>> 1; // loop while a non-leaf
// k代表下移元素的下标。他的左孩子下标为 2*i+1 右孩子为 2*i+2 如果k>=half 说明他没有子节点了
while (k < half) {
int child = (k << 1) + 1; // assume left child is least
Object c = queue[child];
int right = child + 1;
if (right < size &&
((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
c = queue[child = right];
// c是左右孩子中的最小值 如果下移元素小于左右孩子 下移结束
if (key.compareTo((E) c) <= 0)
break;
// 和最小的孩子节点互换,继续下移
queue[k] = c;
k = child;
}
queue[k] = key;
}
siftUp操作
// 上移操作
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
// 不是顶点
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
// 如果大于父节点 上移结束
if (key.compareTo((E) e) >= 0)
break;
// 和父节点交换位置 继续上移
queue[k] = e;
k = parent;
}
queue[k] = key;
}
2.ConcurrentLinkedQueue :一个基于链接节点的无界线程安全队列。采用cas操作,线程安全。底层用链表实现的。不能够存null。不阻塞队列。
offer操作:先寻找队列的尾节点,然后用cas操作,将尾节点的next指针指向插入的元素。
poll操作:找到头节点,判断头节点中元素是否为null,不为null直接返回,未null更新头节点
关键源码解析
offer操作
// offer操作
public boolean offer(E e) {
checkNotNull(e);
final ConcurrentLinkedQueue.Node<E> newNode = new ConcurrentLinkedQueue.Node<E>(e);
for (ConcurrentLinkedQueue.Node<E> t = tail, p = t;;) {
ConcurrentLinkedQueue.Node<E> q = p.next;
// 寻找最后一个节点不是根据tail指针,而是根据p.next == null判断 tail指针会延迟恒心
if (q == null) {
// p is last node
// cas操作 将最后一个节点next指针指向新节点
if (p.casNext(null, newNode)) {
// Successful CAS is the linearization point
// for e to become an element of this queue,
// and for newNode to become "live".
// 不止走一次循环,更新tail指针 可能失败,失败时没关系的
if (p != t) // hop two nodes at a time
casTail(t, newNode); // Failure is OK.
return true;
}
// Lost CAS race to another thread; re-read next
}
// 当tail指针延迟更新,,可能指向原来的head节点 poll操作的缘故
// 此时head节点next指针指向自己 让p节点恢复到新的head指针
else if (p == q)
// We have fallen off list. If tail is unchanged, it
// will also be off-list, in which case we need to
// jump to head, from which all live nodes are always
// reachable. Else the new tail is a better bet.
p = (t != (t = tail)) ? t : head;
else
// Check for tail updates after two hops.
// 重新寻找tail指针 寻找为节点 继续操作
p = (p != t && t != (t = tail)) ? t : q;
}
}
poll操作
// poll操作
public E poll() {
restartFromHead:
for (; ; ) {
for (ConcurrentLinkedQueue.Node<E> h = head, p = h, q; ; ) {
E item = p.item;
// 如果head节点中数据不为null cas操作将里面的数据置为null 返回数据
if (item != null && p.casItem(item, null)) {
// 说明head节点中数据为null for循环不止走一次
if (p != h) // hop two nodes at a time
// 将原来head节点的next指针指向自己,将head指针指向有数据的节点。
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
// 代表队列空了
else if ((q = p.next) == null) {
updateHead(h, p);
return null;
}
// 多线程时 当自己进入for循环(此时head指针还没有变)
// 其他线程走到updateHead(h, ((q = p.next) != null) ? q : p);head指针已经移走,原head节点next指针指向自己
// 此时重新获取head节点 重新开始操作
else if (p == q)
continue restartFromHead;
else
// head节点的元素为null p指向下一个节点,继续操作
p = q;
}
}
}
3.ArrayBlockingQueue:底层基于数组实现,不能够存null,通过对ReentrantLock加锁;线程安全,通过对notEmpty条件对象(用于通知take方法队列已有元素,可执行获取操作)和 通过对notFull条件对象(用于通知put方法队列未满,可执行添加操作 )实现阻塞。是一个阻塞队列。
offer操作:先获取ReentrantLock加锁,然后判断队列是否满了,满了直接返回,没满执行enqueue入队列。此方法不阻塞。
put操作:先获取ReentrantLock加锁,然后判断队列是否满了,满了就通过notFull.await阻塞,没满执行enqueue入队列
poll操作:先获取ReentrantLock加锁,判断队列是否为空,空就直接返回,没空就执行deenqueue弹出头节点
take操作:先获取ReentrantLock加锁,判断队列是否为空,空就通过notEmpty.await阻塞,没空就执行deenqueue弹出头节点
关键源码解析:通过takeIndex和putIndex实现数组的循环使用
//入队操作
private void enqueue(E x) {
//获取当前数组
final Object[] items = this.items;
//通过putIndex索引对数组进行赋值
items[putIndex] = x;
//索引自增,如果已是最后一个位置,重新设置 putIndex = 0;
if (++putIndex == items.length)
putIndex = 0;
count++;//队列中元素数量加1
//唤醒调用take()方法的线程,执行元素获取操作。
notEmpty.signal();
}
4.LinkedBlockingQueue :底层基于链表实现,不能够存null。线程安全。是一个阻塞队列。通过ReentrantLock takeLock(获取并移除元素时使用的锁)和ReentrantLock putLock (添加元素时使用的锁)实现线程安全。通过Condition notEmpty条件对象和Condition notFull条件对象实现阻塞。
offer操作:先获取putLock 加锁,然后判断队列是否满了,满了直接返回,没满执行enqueue入队列。此方法不阻塞。
put操作:先获取putLock k加锁,然后判断队列是否满了,满了就通过notFull.await阻塞,没满执行enqueue入队列
poll操作:先获取takeLock加锁,判断队列是否为空,空就直接返回,没空就执行deenqueue弹出头节点
take操作:先获取takeLock加锁,判断队列是否为空,空就通过notEmpty.await阻塞,没空就执行deenqueue弹出头节点
ArrayBlockingQueue、LinkedBlockingQueue两者区别
1.队列大小有所不同,ArrayBlockingQueue是有界的初始化必须指定大小,而LinkedBlockingQueue可以是有界的也可以是无界的(Integer.MAX_VALUE),对于后者而言,当添加速度大于移除速度时,在无界的情况下,可能会造成内存溢出等问题。
2.数据存储容器不同,ArrayBlockingQueue采用的是数组作为数据存储容器,而LinkedBlockingQueue采用的则是以Node节点作为连接对象的链表。
3.由于ArrayBlockingQueue采用的是数组的存储容器,因此在插入或删除元素时不会产生或销毁任何额外的对象实例,而LinkedBlockingQueue则会生成一个额外的Node对象。这可能在长时间内需要高效并发地处理大批量数据的时,对于GC可能存在较大影响。
4.两者的实现队列添加或移除的锁不一样,ArrayBlockingQueue实现的队列中的锁是没有分离的,即添加操作和移除操作采用的同一个ReenterLock锁,而LinkedBlockingQueue实现的队列中的锁是分离的,其添加采用的是putLock,移除采用的则是takeLock,这样能大大提高队列的吞吐量,也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
4.PriorityBlockingQueue:一个由优先级堆支持的无界优先级队列。底层由数组实现,线程安全。用ReentrantLock保证线程安全,用Condition notEmpty保证队列为空时阻塞。
5.DelayQueue :一个由优先级堆支持的、基于时间的调度队列。DelayQueue的元素都必须继承Delayed接口。DelayQueue内部实现的机制:以支持优先级无界队列的PriorityQueue作为一个容器,容器里面的元素都应该实现Delayed接口,在每次往优先级队列中添加元素时以元素的过期时间作为排序条件,最先过期的元素放在优先级最高。DelayQueue相当于一个特殊的PriorityBlockingQueue。底层由数组实现,线程安全。用ReentrantLock保证线程安全,用Condition notEmpty保证队列为空时阻塞。同样无界。
6:SynchronousQueue:是一个同步阻塞队列,它的每个插入操作都要等待其他线程相应的移除操作。每一个移除操作都要等待其他线程相应的插入操作。唤醒策略分为公平(TransferQueue FIFO)与非公平模式(TransferStack LIFO) 默认是非公平策略。
TransferStack的关键代码
// transfer 函数
E transfer(E e, boolean timed, long nanos) {
SynchronousQueue.TransferStack.SNode s = null;
// 判断是take操作还是put操作
int mode = (e == null) ? REQUEST : DATA;
for (;;) {
SynchronousQueue.TransferStack.SNode h = head;
// 如果栈顶为空,或者模式相同
// 栈顶为空说明没有线程操作,那么这个操作应该入栈。
// 模式相同,说明没有相反的模式匹配,也尝试入栈
if (h == null || h.mode == mode) {
// 如果有时间限制并且时间用完
if (timed && nanos <= 0) {
// 栈顶元素被取消了 弹出栈顶,将head指向下一个
if (h != null && h.isCancelled())
casHead(h, h.next);
// 栈顶为null 或者栈顶没被取消,直接返回null
else
return null;
// 入栈操作,更新head指针为新节点
} else if (casHead(h, s = snode(s, e, h, mode))) {
// 入栈成功,调用awaitFulfill()方法自旋+阻塞当前入栈的线程并等待被匹配到或者取消
SynchronousQueue.TransferStack.SNode m = awaitFulfill(s, timed, nanos);
// 走到这里 说明匹配到了或取消了
// 说明被取消了
if (m == s) {
// 清除节点 更新head指针指向仍需要匹配的元素
clean(s);
return null;
}
// 说明匹配到了
// 如果s匹配到的节点正好是head节点 出栈s,h
// 一般情况下 在模式相反的逻辑中,s和h已经出栈了,这里大概是再试一次
if ((h = head) != null && h.next == s)
casHead(h, s.next);
// 根据模式返回对应的值
return (E) ((mode == REQUEST) ? m.item : s.item);
}
// 如果模式相反 且头节点不是匹配中
} else if (!isFulfilling(h.mode)) { // try to fulfill
// 头节点被取消了 出栈,更新头节点
if (h.isCancelled()) // already cancelled
casHead(h, h.next); // pop and retry
// 入栈 并更新head节点
else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
for (;;) {
// m是s匹配的节点
SynchronousQueue.TransferStack.SNode m = s.next;
// 模式相反的情况下,m是null,说明其他相反的节点都被其他线程匹配完了
// 清空队列重新来过
if (m == null) { // all waiters are gone
casHead(s, null); // pop fulfill node
s = null; // use new node next time
break; // restart main loop
}
SynchronousQueue.TransferStack.SNode mn = m.next;
// 尝试匹配
if (m.tryMatch(s)) {
// 弹出s和m 将head更新为第三个节点
casHead(s, mn);
// 根据模式返回对应的值
return (E) ((mode == REQUEST) ? m.item : s.item);
} else // lost match
// 说明m节点被其他线程抢了 将s的next指向m的next 重新匹配s和m的next
s.casNext(m, mn); // help unlink
}
}
// 头节点匹配中 帮助头节点匹配 重新来过
} else {
SynchronousQueue.TransferStack.SNode m = h.next;
if (m == null)
casHead(h, null);
else {
SynchronousQueue.TransferStack.SNode mn = m.next;
// 匹配 唤醒等待线程
if (m.tryMatch(h))
casHead(h, mn);
else
h.casNext(m, mn);
}
}
}
}
TransferQueue 逻辑与TransferStack不同,TransferQueue底层使用队列,先进先出。
// 使用 TransferQueue, 则队列中永远会存在一个 dummy nod
E transfer(E e, boolean timed, long nanos) {
SynchronousQueue.TransferQueue.QNode s = null;
boolean isData = (e != null);
for (;;) {
SynchronousQueue.TransferQueue.QNode t = tail;
SynchronousQueue.TransferQueue.QNode h = head;
// 队列还没初始化
if (t == null || h == null)
continue;
// 队列为空或者模式相同 入队列
if (h == t || t.isData == isData) {
SynchronousQueue.TransferQueue.QNode tn = t.next;
// 尾指针被其他线程更新了
if (t != tail)
continue;
// 尾指针被其他线程更新了
if (tn != null) {
advanceTail(t, tn);
continue;
}
// 超时了
if (timed && nanos <= 0)
return null;
if (s == null)
s = new SynchronousQueue.TransferQueue.QNode(e, isData);
// 插入队列 插入失败则循环插入
if (!t.casNext(null, s))
continue;
// 插入成功后 更新尾指针
advanceTail(t, s);
// 入栈成功,调用awaitFulfill()方法自旋+阻塞当前入栈的线程并等待被匹配到或者取消
Object x = awaitFulfill(s, e, timed, nanos);
// 节点被取消了
if (x == s) {
clean(t, s);
return null;
}
// 如果还没有出栈 帮助出栈
if (!s.isOffList()) { // not already unlinked
advanceHead(t, s); // unlink if head
if (x != null) // and forget fields
s.item = s;
s.waiter = null;
}
// 根据模式返回对应的值
return (x != null) ? (E)x : e;
// 模式相反 进行匹配操作
} else {
// 从头节点开始匹配
SynchronousQueue.TransferQueue.QNode m = h.next;
// 指针被更新 重来
if (t != tail || m == null || h != head)
continue;
Object x = m.item;
if (isData == (x != null) || // 被人抢了
x == m || // m被取消了
!m.casItem(x, e)) { // 匹配失败
// 推进头节点 重来
advanceHead(h, m);
continue;
}
// 匹配成功
advanceHead(h, m);
// 唤醒等待线程
LockSupport.unpark(m.waiter);
return (x != null) ? (E)x : e;
}
}
}
关于TransferQueue清除的时候:队列的尾节点不能删除,防止其他线程插入的节点丢失,删除尾节点时,将尾节点的前置指针设置为cleanMe,等待下次删除。下次删除如果发现cleanMe不是null且cleanMe不是尾节点,就删除cleanMe。这样能够保证并发的情况下,不会丢失节点
4.Map:存储键值对,形式为K-V(HashMap,TreeMap,LinkHashMap,ConcurrentHashMap)
1.HashMap:底层实现 数组+链表+红黑树。线程不安全,键和值都可以为null初始大小16,扩容*2。
扩容策略为2的原因:扩容需要将老数组的元素移动到新的数组,扩容为2能保证元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。这样能够尽量的保持原来的位置。
关键操作的源码解析
// 当hash冲突不严重的时候,直接用单项链表存储数据,当超过8个的时候 如果表长度小于64扩容 大于64转化为红黑树
// put方法内部调用putVal方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i;
// 当数组为空的时候 扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 如果没有hash相等的时候 直接放入
// (n - 1) & hash n:表的长度 hash:key的hash值 下标的计算方式 (n - 1) & hash
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {// 存在hash相等
HashMap.Node<K,V> e; K k;
// hash相等 如果两者地址相同或者equal方法为true
// 认为key是相同的 e指向老节点
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 到这里:hash冲突了 hash值相同 但是不是同一个key 即两个key计算出相同的hash值
// 如果是已经是用红黑树存储了 构造树型节点 放入
else if (p instanceof HashMap.TreeNode)
e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {// 还在用链表存储元素
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) { // 找个空位插入
p.next = newNode(hash, key, value, null);
// 如果超过了8 转化红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 如果是同一个key 此时e指向老节点 跳出循环
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 存在相同的key 覆盖老值
if (e != null) { // existing mapping for key
V oldValue = e.value;
// 如果当前位置已存在一个值,是否替换,false是替换,true是不替换
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// 此方法hashmap是空方法 为linkhashmap写的
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
// 如果大于负载数 扩容
if (++size > threshold)
resize();
// 此方法hashmap是空方法 为linkhashmap写的
afterNodeInsertion(evict);
return null;
}
// 将单项链表转化为双向链表 然后再转化为红黑树
// 当hash冲突数目不多的时候,用单向链表存储数据
// 此函数将单向链表转化为双向链表
final void treeifyBin(HashMap.Node<K,V>[] tab, int hash) {
int n, index; HashMap.Node<K,V> e;
// 当tab表为null或长度太短时,扩容
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
// 获取链表的头节点
else if ((e = tab[index = (n - 1) & hash]) != null) {
HashMap.TreeNode<K,V> hd = null, tl = null;
do {
HashMap.TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
// 遍历单向链表
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
// 将双向链表转化为红黑树
final void treeify(HashMap.Node<K,V>[] tab) {
HashMap.TreeNode<K,V> root = null;
// x = this 赋值为头节点
for (HashMap.TreeNode<K,V> x = this, next; x != null; x = next) {
next = (HashMap.TreeNode<K,V>)x.next;
x.left = x.right = null;
// 根节点为空,创建黑色根节点
if (root == null) {
x.parent = null;
x.red = false;
root = x;
}
else {
K k = x.key;
int h = x.hash;
Class<?> kc = null;
// 查找数的插入 对比hash值 插入对应位置
// dir -1 方向为左 1 方向为右
for (HashMap.TreeNode<K,V> p = root;;) {
int dir, ph;
K pk = p.key;
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
// hash值一样 通过对比类名 对象名等方式比较 算出节点插入方向
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);
HashMap.TreeNode<K,V> xp = p;
// 插入红黑树
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
// 插入调整
root = balanceInsertion(root, x);
break;
}
}
}
}
// 红黑树同时也是双向链表
// 这个方法里做的事情,就是保证树的根节点一定也要成为链表的首节点 也就是table数组的元素 保证能够通过table[i]访问到其他元素
// map 先算table[i]是否存在。如果不存在就直接塞入 存在就用链表或者红黑树存储冲突的元素
moveRootToFront(tab, root);
}
关于get remove方法都是先计算hash值,然后hash&(n-1)计算出下标,然后判断是否存在,如果存在,然后遍历链表或者红黑树,调用equal方法,如果equal为true返回元素,其余返回null。
如果是增加或者移除,如果是用红黑树存储的,这个时候涉及到红黑树的调整。
红黑树:所有节点都是红色或者黑色、根节点为黑色、所有的 NULL 叶子节点都是黑、红色节点不能连续、所有的 NULL 节点到根节点的路径上的黑色节点数量一定是相同的。
- 插入节点都是红色。如果是root节点,直接转化为黑色
- 如果父节点是黑色的,直接插入,不需要调整
- 如果父节点是红色的,叔叔节点也是红色的,将父节点和叔叔节点置黑,将爷爷节点置红。将爷爷节点当作新插入的节点,递归处理,如果递归到根节点,则将根节点置黑。
- 如果父节点是红色的,叔叔节点不存在(null就是黑节点)或者黑色节点(递归的时候会出现):0:当前节点是父亲的左孩子,父亲是爷爷的左孩子。将爷爷节点右旋.交换父节点和爷爷节点的颜色。1:当前节点是父亲的右孩子,父亲是爷爷的左孩子:.将父节点左旋,并将父节点作为当前节点; 第0情形。 2:当前节点是父亲的右孩子,父亲是爷爷的右孩子:将爷爷节点左旋;交换父节点和爷爷节点的颜色。3:当前节点是父亲的左孩子,父亲是爷爷的右孩子:将父节点右旋,并将父节点作为当前节点; 然后再使用第2情形
删除:红黑树删除中间节点时会找后继节点或前继节点代替,即删除的肯定是叶子节点(不为null的叶子节点)
- 当删除节点是红色时,直接移除
- 当删除节点黑色时,此时必有兄弟节点,不然不满足红黑树特性(从根节点出发,到任意黑色null叶子节点经过的黑色节点一样)
- 当兄弟节点为黑色时且有红色子节点时(不会有黑色节点,红黑树特性),如果兄弟节点为右节点(左节点时,镜像操作就行),兄弟子节点为右节点时(无论有没有左红子节点),将父节点的颜色给兄弟节点,将父节点、兄弟子节点置黑,父节点左旋。
- 当兄弟节点为黑色,仅有左子红节点时,先将兄弟节点和兄弟子节点互换颜色。兄弟节点右旋,变为第3的情形
- 当兄弟节点没有子节点时,将兄弟节点置红,将父节点当作递归节点(如果父节点是红色的,就不用递归了,置黑就行),直到遇到红色的父节点,将其置黑。
- 当兄弟节点为红色节点时,必有两黑色子节点(红黑树特性),将兄弟左节点置红,兄弟节点置黑,父节点左旋。
红黑树比查找树查询效率更优,查找树不会自平衡,可能演变为链表
红黑树相比AVL(平衡二叉查找树) 查询效率稍微低一点。avl查询效率永远是logn,因为avl任何两颗子树高度相差不超过1。红黑树在极端情况下,一边的高度是另一边的2倍(一边全是黑,一边红黑相间)所以效率为logn~2logn。
红黑树不追求"完全平衡",它只要求部分达到平衡,红黑是用非严格的平衡来换取增删节点时候旋转次数的降低,任何不平衡都会在三次旋转之内解决,AVL是严格平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多
hashMap扩容:
final HashMap.Node<K,V>[] resize() {
HashMap.Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
// 大于最大的容量
if (oldCap >= MAXIMUM_CAPACITY) {
// 阈值赋予整数最大值,再不会扩容了
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 如果两倍扩容后的长度小于最大容量 当前容量>=默认初始化容量16
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
// 这是扩容 不是初始化 初始化当前容量为0
// 新的阈值 超过此值将会扩容
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0)
// 进入此if证明创建map时用的带参构造 带入的参数会被调整为initialCapacity参数的2^n来作为初始化容量
newCap = oldThr;
else {
// 无参构造函数
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
// 老容量<默认的值16 或 oldThr > 0即带参初始化
// 计算新的阈值
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
// 设置阈值
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
HashMap.Node<K,V>[] newTab = (HashMap.Node<K,V>[])new HashMap.Node[newCap];
table = newTab;
if (oldTab != null) {
// 扩容之后开始迁移数据
for (int j = 0; j < oldCap; ++j) {
HashMap.Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null) // 不存在hash值相同的对象 直接解算出新坐标移动过去
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof HashMap.TreeNode)
// 红黑树的迁移
((HashMap.TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
HashMap.Node<K,V> loHead = null, loTail = null;
HashMap.Node<K,V> hiHead = null, hiTail = null;
HashMap.Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) { //位置不需要变动的组成一个链表
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else { //位置需要移动的组成一个链表
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
// 放入对应位置
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
final void split(HashMap<K,V> map, HashMap.Node<K,V>[] tab, int index, int bit) {
HashMap.TreeNode<K,V> b = this;
// Relink into lo and hi lists, preserving order
HashMap.TreeNode<K,V> loHead = null, loTail = null;
HashMap.TreeNode<K,V> hiHead = null, hiTail = null;
int lc = 0, hc = 0;
// e是红黑树的根节点
// 将红黑树拆分为两个双向链表
for (HashMap.TreeNode<K,V> e = b, next; e != null; e = next) {
next = (HashMap.TreeNode<K,V>)e.next;
e.next = null;
if ((e.hash & bit) == 0) { //位置不需要改变
if ((e.prev = loTail) == null)
loHead = e;
else
loTail.next = e;
loTail = e;
++lc;
}
else { //位置需要改变
if ((e.prev = hiTail) == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
++hc;
}
}
if (loHead != null) {
if (lc <= UNTREEIFY_THRESHOLD) // 长度不够 直接用链表存
tab[index] = loHead.untreeify(map);
else {
tab[index] = loHead;
if (hiHead != null) // 转化为红黑俗话
loHead.treeify(tab);
}
}
if (hiHead != null) {
if (hc <= UNTREEIFY_THRESHOLD)
tab[index + bit] = hiHead.untreeify(map);
else {
tab[index + bit] = hiHead;
if (loHead != null)
hiHead.treeify(tab);
}
}
}
2.TreeMap:底层是通过红黑树实现的。线程不安全,默认自然有序,可以实现Comparator来自定义排序。底层操作基本都是红黑树的操作。
3.LinkHashMap:底层实现可以看成HashMap的基础上,多了一个双向链表来维持顺序。在插入元素或者删除元素的时候同时维护这个双向链表。LinkHashMap的顺序性就是通过这个双向链表实现的。线程不安全。
4.ConcurrentHashMap:底层实现基本和hashMap一样,只不过线程安全。采用了CAS + synchronized来保证并发安全性。
ConcurrentHashMap的put操作:1.根据key.hashCode()计算出hash值 。2.通过key定位出node,如果hash表为空表示当前位置可以写入数据,利用循环CAS写入,如果不为空,则利用synchronized锁写入数据。锁的级别在于hash表上的一个元素。