解读HashMap<K,V>内部原理
纵然到处都有关于HashMap原理解析,但我还是要对HashMap做一个内部原理的探索,因为每个人看待问题的角度是不同,就像一千个人眼中有一千个哈姆雷特,看似相同的事物,却亦可以给予你更多的惊喜。
文章结构:
- HashMap的整体结构
- 简介
- 应用
- 缺陷
- HashMap的增删查改
- Put
- Remove
- Get
- Set
- HashMap的内部结构
- Node
- TreeNode
- Iterator与KeySet、EntrySet的联系
- 总结
一、HashMap的整体结构
1、简介
HashMap 是基于hashing 与 mapping 即(哈希映射)构造的一个存储Key—Value的数据结构。它基于哈希表的Map接口实现。其主要的特征是:
- hash算法
- collision 哈希冲突的解决
- Java 8 以前采用简易的链式法(复杂度为O(N))
- Java 8 以后采用链式+红黑树实现(红黑树的复杂度为O(logN))
- 拥有调优性(拥有多个调优参数,如负载因子,可以根据自己使用场景的数据模型进行更好地调优)
2、应用
- 适合key-value的存取场景
- 其value可以存储null空值
3、缺陷
- 并非线程安全,并发修改可能出现问题
- 极端问题:
- 每次计算hash值都是同一个,造成单桶(bucket)过大,效率减低(采用红黑树优化)
- 每次计算hash尽皆不同,造成桶数组( [ ] table)过大,不断扩容
整体结构例图:
二、HashMap的增删查改
在讲解增删查改之前,必须明白一点,在hashMap中,key相同指的是:hash(key)相同并且key.equals()相同
1、Put
分为单个put与批量put,接下来就从源码开始解析~~
单一put的源码:
/**
* 添加时候,如果已经存在相同的key,则会替代掉原有的value
* 调用putVal()方法
* onlyIfAbsent=false;如果存在相同的key就会替换value。
*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//onlyIfAbsent=true,如果存在相同的key,只有value==null才会添加
public V putIfAbsent(K key, V value) {
return putVal(hash(key), key, value, true, true);
}
/**
* put的底层实现
* evict:是否需要删除,实现为空
* onlyIfAbsent:只有缺失时才写入?
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K, V>[] tab;
Node<K, V> p;
int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
//指定的数组位置的桶没有元素,则新构建一个node节点
tab[i] = newNode(hash, key, value, null);
else {
Node<K, V> e;
K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
//树形添加
e = ((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);
//如果添加后达到树化阈值,则进行树化
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//树化
treeifyBin(tab, hash);
break;
}
//如果遍历的过程已经存在,则结束遍历
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) {
//已经存在key的情况,onlyIfabsent=true就只允许覆盖原为null的值
//onlyIfabsent=false则允许全方位覆盖
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
//直接跳出
return oldValue;
}
}
//结构修改次数+1
++modCount;
//达到阈值,则进行扩容
if (++size > threshold)
resize();
//调用的实现是linkedHashMap里面的实现
afterNodeInsertion(evict);
return null;
}
对于单一put操作小结:
- 允许存在相同的key,对于存在相同的key,put方法会直接覆盖value;putIfAbsent则只允许覆盖null的value
- 底层实现都是putVal(),该方法的实现:
- 判断要添加的键值对的key的映射到数组的槽位上,是否存在节点。
- 如果不存在,则构造新节点
new Node
添加到槽位上,即桶的首元素。
- 如果不存在,则构造新节点
- 如果key映射的槽位已经存在根节点,则判断是首节点是树形节点还是链式节点。
- 树形节点,调用
putTreeVal
方法,进行红黑树的插入。 - 链式节点,进行链表的遍历,如果已经存在,跳出循环,再判断是否进行value的覆盖。
- 如果不存在,则构造新节点,添加到链的尾部,并且判断是否达了树化的条件,再决定是否进行树化
- 树形节点,调用
- 自增modCount参数,保证hashmap的fail-fast机制能够正常运行。
- 判断自增后的size是否达到了阈值(threshold),进而执行resize()
- 判断要添加的键值对的key的映射到数组的槽位上,是否存在节点。
批量put~~
/**
* 将一个map合并进来。
*/
public void putAll(Map<? extends K, ? extends V> m) {
putMapEntries(m, true);
}
/**
* 遍历参数map
*/
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
//获取参数的map
int s = m.size();
if (s > 0) {
if (table == null) { // pre-size
//补充精度
float ft = ((float) s / loadFactor) + 1.0F;
int t = ((ft < (float) MAXIMUM_CAPACITY) ?
(int) ft : MAXIMUM_CAPACITY);
if (t > threshold)
//重新获取阈值 re get threshold
threshold = tableSizeFor(t);
} else if (s > threshold)
//如果构造参数的hashMap的容量大于当前hashMap的阈值,那么就会重新扩容
resize();
//以下是必执行的
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
批量put的小结:
- 如果原先map为空,且参数map的容量大于0,则重新计算threshold
- 如果原先map不为空,则判断参数map是否大于阈值,若大于阈值,则执行resize
- 最后遍历map进行一个个地putVal(本身会进行resize判断)
以上便是单一添加与批量添加的原理,当然,如果桶是链式的,那么就进行遍历链表添加,如果添加后达到阈值,则会进行桶的树化;如果桶是树形的,那就调用树形的插入方法: putTreeVal(this, tab, hash, key, value)
后续会讲。这里再附上put操作的图例:
2、Remove
*再讲解完put操作之后,接下来就是Remove方法的解析了!*先上代码:
/**
* 如果key存在,则从此映射中删除指定键的映射。
*/
public V remove(Object key) {
Node<K, V> e;
//调用的实现是 removeNode方法
//value=null;matchValue=false;代表无需用值去匹配,只要键相同即可删除
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
//根据 键——值 进行删除,value与matchValue=true
public boolean remove(Object key, Object value) {
return removeNode(hash(key), key, value, true, true) != null;
}
/**
*
* @param hash for key
* @param key key
* @param matchValue (如果为真)只在值相等时删除
* @param movable 如果为false,则在删除时不移动其他节点
* @param value 如果匹配值,则忽略该值
* 返回节点,如果没有,返回null
*/
final Node<K, V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K, V>[] tab;
Node<K, V> p;
int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K, V> node = null, e;
K k;
V v;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null) {
if (p instanceof TreeNode)
//树节点的查找
node = ((TreeNode<K, V>) p).getTreeNode(hash, key);
else {
//以下是链式桶的节点查找,找到则赋值给node,然后跳出循环
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
//树节点的移除
//其中可能将树形桶转化称为链式桶
((TreeNode<K, V>) node).removeTreeNode(this, tab, movable);
else if (node == p)
//如果删除节点是桶的根节点,那么,把next节点存到桶的根位置
tab[index] = node.next;
else
//普通的链式移除,node的父节点p的next指向node的next节点
p.next = node.next;
++modCount;
--size;
//hashMap的这个实现是空。linkedHashMap有实现
afterNodeRemoval(node);
return node;
}
}
return null;
}
对remove操作做一个总结:
- 分别有根据key进行remove;也有根据key-value进行remove,但是底层实现都是
removeNode
removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable)
方法根据传入的参数进行决定执行怎样的操作:- 如果value=null;matchValue=false;只要key相同即删除
- 如果value!=null;matchValue=true;那么,只有key-value都相同才执行删除
- 对于树形的操作后续再讲。
接下来讲解Get方法,Get方法相比以上的两种方法会相对简单~~
3、Get
一如既往,先上代码~~
/**
* 根据key获取对应的value
* 可能返回null
*/
public V get(Object key) {
Node<K, V> e;
//guess getNode is :by hashValue of key and adjust key == Node.key()
//this is why can be only key in the hashMap,if not only, in the linked collision may find lots of value!!!
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
/**
* 根据hash值以及key查找返回对应的Node节点
* note: guess is true!!!
*/
final Node<K, V> getNode(int hash, Object key) {
Node<K, V>[] tab;
Node<K, V> first, e;
int n;
K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//如果第一个节点的键不相等,则查找其它节点
if ((e = first.next) != null) {
//如果是树节点,则进入树形查找
if (first instanceof TreeNode)
return ((TreeNode<K, V>) first).getTreeNode(hash, key);
do {
//如果是链式节点,则进行遍历
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
//以下方法是在JDK8中重写了继承自Map的方法;即根据Key找不到Node,返回默认的value
@Override
public V getOrDefault(Object key, V defaultValue) {
Node<K, V> e;
return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
}
get方法比较简单,通过注释就可以完全明白get的实现,其实在put、remove中也可见,这里就不重复解读了!
4、Set
*Set操作在hashMap中并没有找到对应方法,其实HashMap相比List,把Set、Add整合为一个操作,就是Put,而put操作实现的功能就是:添加或者替换。*增删查改部分的内容到此结束,接下来要讲的是它的内部结构!
三、HashMap的内部结构
1、Node implements Map.Entry
Node就是HashMap实际存储Key、Value的数据结构,它是一个单向节点,实现了Map.Entry的getter setter,代码如下:
static class Node<K, V> implements Map.Entry<K, V> {
//哈希值
final int hash;
//键值对,注意Key为Final,这是为什么在hashMap中,键不可变而值可变的原因
final K key;
V value;
//指向下一个节点,意味着是使用“链地址法解决哈希冲突”
Node<K, V> next;
Node(int hash, K key, V value, Node<K, V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
//这也是在打印hashMap节点的时候输出: key = value的原因
public final String toString() {
return key + "=" + value;
}
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
//判断节点相等的条件是:都是判断引用
//1、key相等 ==
//2、value相等 ==
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
小结:
- HashMap中的Node实现Map.Entry,且为单向节点,只有next指针。
- 该Node的key字段用final修饰,这也是为何hashMap中只能改变指定键值对的value,而不能改变键的原因
- 补充:该Node在桶树化的时候,会转化成为treeNode。
2、TreeNode extends LinkedHashMap.Entry
*顾名思义,TreeNode就是树节点,该内部类是在JDK1.8之后引入的,主要用于hashMap的桶的链式转化成红黑树,提高性能。*上代码:
//可以看到,是通过继承LinkedHashMap.Entry获取它的一些属性
static final class TreeNode<K, V> extends LinkedHashMap.Entry<K, V> {
TreeNode<K, V> parent; // 红黑树的链接
TreeNode<K, V> left;
TreeNode<K, V> right;
TreeNode<K, V> prev; // 删除后需要断开next链接
boolean red;
}
上述是TreeNode的内部结构,单一的红黑树是只有value、parent、left、right、color字段,但是,hashMap在原先基础上,增加了prev,并且继承了LinkedHashMap.Entry。我们接下来跳进LinkedHashMap.Entry看看源码
static class Entry<K,V> extends HashMap.Node<K,V> {
//定义了前、后指针。
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
以上内容的小结:
- 通过继承LinkedHashMap.Entry,添加了红黑树需要的属性,同时多加了prev指针,姑且不讨论作者添加了prev指针的目的;但是LinkedHashMap.Entry其实是继承了HashMap.Node,因此,TreeNode的真实内部结构应该是如下:
- value
- parent、left、right、color
- prev、next
OK!我们已经小结完结构部分,对于TreeNode的内部树形操作,我想有必要简单地进行讲解,当然,具体的红黑树增删查改,有兴趣的朋友可以关注我后面的博文,我将会发布红黑树的讲解博文!
2.1、prev、next指针的妙用
HashMap的设计者很巧妙地利用了next指针,并且在实现TreeNode的时候,补充了prev指针,下面我将展示它的妙处:
- 通过这两个指针,将链式节点与树形节点联系起来,方便链式转红黑树、红黑树退化成链式的转换。
- 在处理树形与链式的节点时,提高了遍历的速率。
2.2、TreeNode的操作合集
贴出操作方法的代码:
//删除平衡
static <K, V> TreeNode<K, V> balanceDeletion(TreeNode<K, V> root,TreeNode<K, V> x);
//插入平衡
static <K, V> TreeNode<K, V> balanceInsertion(TreeNode<K, V> root,TreeNode<K, V> x);
//右旋
static <K, V> TreeNode<K, V> rotateRight(TreeNode<K, V> root,TreeNode<K, V> p);
//左旋
static <K, V> TreeNode<K, V> rotateLeft(TreeNode<K, V> root,TreeNode<K, V> p);
//将一个树桶分裂出两个树桶,如果太小,就链化
final void split(HashMap<K, V> map, Node<K, V>[] tab, int index, int bit);
//树节点的移除
final void removeTreeNode(HashMap<K, V> map, Node<K, V>[] tab,boolean movable);
//树节点的添加
final TreeNode<K, V> putTreeVal(HashMap<K, V> map, Node<K, V>[] tab,int h, K k, V v)
//树化
final void treeify(Node<K, V>[] tab)
//链化
final Node<K, V> untreeify(HashMap<K, V> map)
以上就是树的相关操作的合集,详细的操作感兴趣的同学可以去看看源码,这里就不多一一讲解,后续我可能会退出专栏,对hashMap的树化、链化做一个讲解。
这里需要注意一点:树化的时候,会判断一些参数:
- 最小执行树化阈值
static final int MIN_TREEIFY_CAPACITY = 64;
- 只有hashMap的table[]数组长度达到这个数,才有可能执行树化,table的桶的数组。
- 最小桶树化阈值
static final int TREEIFY_THRESHOLD = 8;
- 只有在达到 最小执行树化阈值且满足桶内达到这个阈值时,才会执行该桶的树化。
- 还原阈值
static final int UNTREEIFY_THRESHOLD = 6;
- 一个桶内的红黑树如果节点小于6,则会执行退化操作,退化成链式结构。
好了!以上树结构的总结到此结束,关于红黑树与树化、链化,后期会退出专栏进行讲解!敬请期待。
3、Iterator与KeySet、EntrySet
在HashMap中,除去核心的内部数据结构,还有一些巧妙的设计,如我们常用的keySet、entrySet,而这些集合的操作其实都离不开 Iterator迭代器,接下来让我们一起感觉大师们的巧妙设计吧。
3.1、KeySet
keySet即键集,继承了 abstractSet间接实现了Set接口,拥有了对应的操作规范。以下围绕它的结构代码进行讲解:
final class KeySet extends AbstractSet<K> {
public final int size() {
return size;
}
//清楚map中的keySet对象
public final void clear() {
HashMap.this.clear();
}
//返回key的迭代器
public final Iterator<K> iterator() {
//键迭代器
return new KeyIterator();
}
//判断是否包含某key
public final boolean contains(Object o) {
return containsKey(o);
}
//移除
public final boolean remove(Object key) {
return removeNode(hash(key), key, null, false, true) != null;
}
//分裂迭代器
public final Spliterator<K> spliterator() {
return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
//foreach调用的实现
public final void forEach(Consumer<? super K> action) {
Node<K, V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (Node<K, V> e : tab) {
for (; e != null; e = e.next)
action.accept(e.key);
}
//判断是否进行了并发的结构化修改
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
KeySet的小结:
- keySet通过继承
AbstractSet
与 间接实现Set
,从而让自己成为Set家族的成员。- 但是,KeySet并没有重写抽象Set的方法,因此,它并不支持Set的增删查改,下面会贴出代码说明。
- KeySet的迭代只有两种方法:foreach与iterator,foreach实现如上,iterator是调用自己实现的keyIterator,后面会列出源码。
这是一个说明KeySet并非真的Set的一个Demo:
public class TestHashMap {
private static final HashMap map = new HashMap();
public static void main(String[] args) {
map.put("123", "abc");
map.put("1234", "abcd");
Set set = map.keySet();
set.add("newKey");
for (Object o : set) {
System.err.println(o);
}
}
}
//以上程序运行会抛出:java.lang.UnsupportedOperationException异常
3.2、Values
在讲解完KeySet之后,其实Values的原理也并不难懂,如出一辙。但是,它的命名时Values,而非ValueSet,我想,可能是在命名KeySet之后,发现命名为KeySet但是从实现上又没有进行支持有点不妥,因此命名为Values。
final class Values extends AbstractCollection<V> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<V> iterator() { return new ValueIterator(); }
public final boolean contains(Object o) { return containsValue(o); }
public final Spliterator<V> spliterator() {
return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super V> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
//我们可以看到,节点普通遍历是采用next指针,不论是树形还是链表形
//因此,某些查找功能调用hashMap的get方法会更快。
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
总结:
- values,即值集的内部实现与键集如出一辙。
- 但是值集继承的是
AbstractCollection
,更加规范,它是集合的一个家族成员,而不是Set家族的成员;可用性更好。
- 但是值集继承的是
- 与键集的遍历一样,都是对每个桶的链式遍历!
3.3、EntrySet与迭代器
*在我个人看来,EntrySet其实可以改名为NodeSet,因为JDK1.8已经把Entry改为Node,现在的EntrySet其实就是Node的集合。*由于内部的结构与keySet等一般无二,所以直接上这个的总结:
- entrySet其实就是hashMap中node的集合,即nodeSet。
- entrySet是keySet与values的合集。
讲完hashMap中几个重要的集合之后,就来讲讲迭代器与这些集合的关系,毕竟forEach遍历已经讲完。
3.3.1、迭代器
在hashMap中,迭代器有很多种,这里只讲迭代器的实现与内部集合的关联,直接上源码:
//hashIterator
abstract class HashIterator {
Node<K, V> next; // next entry to return
Node<K, V> current; // current entry
int expectedModCount; // for fast-fail
int index; // current slot
HashIterator() {
expectedModCount = modCount;
Node<K, V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
do {
} while (index < t.length && (next = t[index++]) == null);
}
}
public final boolean hasNext() {
return next != null;
}
final Node<K, V> nextNode() {
Node<K, V>[] t;
Node<K, V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
if ((next = (current = e).next) == null && (t = table) != null) {
do {
} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
public final void remove() {
Node<K, V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
removeNode(p.hash, p.key, null, false, false);
expectedModCount = modCount;
}
}
final class EntryIterator extends HashIterator
implements Iterator<Map.Entry<K, V>> {
public final Map.Entry<K, V> next() {
return nextNode();
}
}
//ketIterator与EntryIterator类似。
以上便是普通迭代器:
- 都是继承于
HashIterator
哈希迭代器进行拓展,实现对应的迭代功能。
那么有普通迭代器,自然有与众不同的迭代器,就是分裂迭代器,如下:
static class HashMapSpliterator<K, V> {
final HashMap<K, V> map;
Node<K, V> current; // current node
int index; // current index, modified on advance/split
int fence; // one past last index
int est; // size estimate
int expectedModCount; // for comodification checks
HashMapSpliterator(HashMap<K, V> m, int origin,
int fence, int est,
int expectedModCount) {
this.map = m;
this.index = origin;
this.fence = fence;
this.est = est;
this.expectedModCount = expectedModCount;
}
final int getFence() { // initialize fence and size on first use
int hi;
if ((hi = fence) < 0) {
HashMap<K, V> m = map;
est = m.size;
expectedModCount = m.modCount;
Node<K, V>[] tab = m.table;
hi = fence = (tab == null) ? 0 : tab.length;
}
return hi;
}
public final long estimateSize() {
getFence(); // force init
return (long) est;
}
}
分裂迭代器在这里并没有使用。
四、总结
- 以上hashMap的解读分为3个部分,通过以上几个部分的讲解,相信读者对hashMap有比较好的理解了。
- 当然,存在一些具体的实现,如扩容、树的操作等,之后我会推出专栏进行讲解。
- 以下再给大家分享一下hashMap的一个扩容算法,很优秀,大师的杰作:
/**
* 运算得出的结果比当前capacity大的一个由2^N得出的一个数值
* 如果当前数值为2^,那么结果就等于原数值
* 简化说法:就是使得最高位为1的位之后的全部位的数都是1
*/
static final int tableSizeFor(int cap) {
//对cap-1的目的就是实现等于原数值的操作
int n = cap - 1;
//>>>标识无符号右移,忽略符号位,空位都以0补齐
//标识n=n|(n>>>1)
n |= n >>> 1;//得到2个1
n |= n >>> 2;//得到4个1
n |= n >>> 4;//得到8个1
n |= n >>> 8;//得到16个1
n |= n >>> 16;//得到32个1
//由于最低位肯定会变为1,因此是单数,但是hashMap要求容量为2^,因此,需要判断执行+1.
//返回值等于N+1,N+1等于2^
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
好了!本期就到此结束,请关注我后续对链化树、扩容、树化链的讲解,也会推出红黑树专栏!