基于JDK1.8—HashMap源码简要分析

HashMap继承关系

  • HashMap:根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。HashMap 最多只允许一条记录的键为 null ,允许多条记录的值为null。HashMap 非线程安全,即任一时刻可以有多个线程同时写 HashMap ,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections 的 synchronizedMap 方法使 HashMap 具有线程安全的能力,或者使用 ConcurrentHashMap 。
  • LinkedHashMap:LinkedHashMap 是 HashMap 的一个子类,保存了记录的插入顺序,在用 Iterator 遍历 LinkedHashMap 时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。

HashMap底层储存结构

  • 底层结构其实就是 数组+链表+红黑树 。而 HashMap 的数据存储结构是一个 Node<K,V> 数组,在(Java 7 中是 Entry<K,V> 数组,但结构相同)。
  • 链表:是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
  • 红黑树:是一种特定类型的二叉树,是一种平衡二叉查找树的变体,它的左右子树高差有可能大于 1,所以红黑树不是严格意义上的平衡二叉树(AVL),但对之进行平衡的代价较低, 其平均统计性能要强于 AVL。

HashMap类定义

HashMap的定义:

public class HashMap<K,V> extends AbstractMap<K,V>    implements Map<K,V>, Cloneable, Serializable {}

从中我们可以了解到:

  • HashMap<K,V>:HashMap 是以 key-value 形式存储数据的。
  • extends AbstractMap<K,V>:继承了 AbstractMap ,大大减少了实现 Map 接口时需要的工作量。
  • implements Map<K,V>:实现了 Map ,提供了所有可选的 Map 操作。
  • implements Cloneable:表明其可以调用 clone() 方法来返回实例的 field-for-field 拷贝。
  • implements Serializable:表明该类是可以序列化的。

put() 数据原理图解分析


特别说明:

  1. hashmap的底层数据结构名为 table 的数组,是一个 Node 数组;
  2. table 数组中的每个元素是一个 Node 元素(但是这个 Node 元素可能指向下一个 Node 元素从而形成链表),table 数组的每个位置称为桶,比如 talbe[0] 称为一个桶,也可以称为一个 bin 。
  3. 源码中有很多临时 node,如 v 等。
    源码分析

核心属性分析—静态常量

 /*** The default initial capacity - MUST be a power of two.* 默认的初始容量,必须是二的次方*/static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16/*** The maximum capacity, used if a higher value is implicitly specified* by either of the constructors with arguments.* MUST be a power of two <= 1<<30.* * 最大容量,当通过构造函数隐式指定了一个大于MAXIMUM_CAPACITY的时候使用*/static final int MAXIMUM_CAPACITY = 1 << 30;/*** The load factor used when none specified in constructor.* 加载因子,当构造函数没有指定加载因子的时候的默认值的时候使用,经过科学家多次计算得到。*/static final float DEFAULT_LOAD_FACTOR = 0.75f;/*** The bin count threshold for using a tree rather than list for a* bin.  Bins are converted to trees when adding an element to a* bin with at least this many nodes. The value must be greater* than 2 and should be at least 8 to mesh with assumptions in* tree removal about conversion back to plain bins upon* shrinkage.* * TREEIFY_THRESHOLD为当一个bin从list转化为tree的阈值,当一个bin中元素的总元素最低超过这个值的时候,bin才被转化为tree;* 为了满足转化为简单bin时的要求,TREEIFY_THRESHOLD必须比2大而且比8要小*/static final int TREEIFY_THRESHOLD = 8;/*** The bin count threshold for untreeifying a (split) bin during a* resize operation. Should be less than TREEIFY_THRESHOLD, and at* most 6 to mesh with shrinkage detection under removal.* * bin反tree化时的最大值,应该比TREEIFY_THRESHOLD要小,* 为了在移除元素的时候能检测到移除动作,UNTREEIFY_THRESHOLD必须至少为6*/static final int UNTREEIFY_THRESHOLD = 6;/*** The smallest table capacity for which bins may be treeified.* (Otherwise the table is resized if too many nodes in a bin.)* Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts* between resizing and treeification thresholds.* * 树化的另外一个阈值,table的长度(注意不是bin的长度)的最小得为64。为了避免扩容和树型结构化阈值之间的冲突,MIN_TREEIFY_CAPACITY 应该最小是 4 * TREEIFY_THRESHOLD*/static final int MIN_TREEIFY_CAPACITY = 64;

核心属性分析—成员变量

/*** The table, initialized on first use, and resized as* necessary. When allocated, length is always a power of two.* (We also tolerate length zero in some operations to allow* bootstrapping mechanics that are currently not needed.)* * table,第一次被使用的时候才进行加载*/transient Node<K,V>[] table;/*** Holds cached entrySet(). Note that AbstractMap fields are used* for keySet() and values().* 键值对缓存,它们的映射关系集合保存在entrySet中。即使Key在外部修改导致hashCode变化,缓存中还可以找到映射关系*/transient Set<Map.Entry<K,V>> entrySet;/*** The number of key-value mappings contained in this map.* table中 key-value 元素的个数*/transient int size;/*** The number of times this HashMap has been structurally modified* Structural modifications are those that change the number of mappings in* the HashMap or otherwise modify its internal structure (e.g.,* rehash).  This field is used to make iterators on Collection-views of* the HashMap fail-fast.  (See ConcurrentModificationException).* * HashMap在结构上被修改的次数,结构上被修改是指那些改变HashMap中映射的数量或者以其他方式修改其内部结构的次数(例如,rehash)。* 此字段用于使HashMap集合视图上的迭代器快速失败。*/transient int modCount;/*** The next size value at which to resize (capacity * load factor).** 下一次resize扩容阈值,当前table中的元素超过此值时,触发扩容* threshold = capacity * load factor* @serial*/// (The javadoc description is true upon serialization.// Additionally, if the table array has not been allocated, this// field holds the initial array capacity, or zero signifying// DEFAULT_INITIAL_CAPACITY.(???????))int threshold;/*** The load factor for the hash table.* 负载因子* @serial*/final float loadFactor;

构造方法() 分析

主要看最长参数的构造方法就行了,其它三个都是调用了此构造方法。

  public HashMap(int initialCapacity, float loadFactor) {//做了一些校验,capacity必须大于0,最大值也就是MAXIMUM_CAPACITYif (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;//loadFactor必须大于0if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: " +loadFactor);//把传入的值赋值给HashMap的属性this.loadFactor = loadFactor;//给扩容阈值赋值,给转化为2的次方数this.threshold = tableSizeFor(initialCapacity);}/*** Returns a power of two size for the given target capacity.* * 1.返回一个大于等于当前值cap的一个的数字,并且这个数字一定是2的次方数* 假如cap为10,那么n= 9 = 0b1001* 0b1001 | 0b0100 = 0b1101* 0b1101 | 0b0011 = 0b1111* 0b1111 | 0b0011 = 0b1111* ......* .....* n = 0b1111 = 15* * 2.这里的cap必须要减1,如果不减,并且如果传入的cap为16,那么算出来的值为32* * 3.这个方法就是为了把最高位1的后面都变为1* 0001 1101 1100 -> 0001 1111 1111 -> +1 -> 0010 1111 1111*/static final int tableSizeFor(int cap) {int n = cap - 1;n |= n >>> 1;n |= n >>> 2;n |= n >>> 4;n |= n >>> 8;n |= n >>> 16;return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;}

put() 方法分析

 /*** @param key key with which the specified value is to be associated* @param value value to be associated with the specified key* @return the previous value associated with key, or*         null if there was no mapping for key.*         (A null return can also indicate that the map*         previously associated null with key.)*         返回先前key对应的value值(如果value为null,也返回null),如果先前不存在这个key,那么返回的就是null;*/public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}/* 在往haspmap中插入一个元素的时候,由元素的hashcode经过一个扰动函数之后再与table的长度进行与运算才找到插入位置,下面的这个hash()方法就是所谓的扰动函数* 作用:让key的hashCode值的高16位参与运算,hash()方法返回的值的低十六位是有hashCode的高低16位共同的特征的* 高16位参与运算原因:当数组的长度很短时,只有低位数的hashcode值能参与运算。而让高16位参与运算可以更好的均匀散列,减少碰撞,进一步降低hash冲突的几率,并且使得高16位和低16位的信息都被保留了。* 举例* hashCode = 0b 0010 0101 1010 1100  0011 1111 0010 1110* *     0b 0010 0101 1010 1100  0011 1111 0010 1110  ^ *     0b 0000 0000 0000 0000  0010 0101 1010 1100 *     0b 0010 0101 1010 1100  0001 1010 1000 0010*/static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}
// onlyIfAbsent:插入的值是否存在,存在就不插了。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {// tab表示当前hashmap的table// p表示table的元素// n表示散列表的长度// i表示路由寻址结果Node<K,V>[] tab; Node<K,V> p; int n, i;// 延迟初始化逻辑,第一次调用putval()方法的时候才进行初始化hashmap中最耗内存的talbeif ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;//其实就是给n赋值// 1.最简单的一种情况,寻找到的桶位,刚好是null,这个时候直接将当前k-v=>node 扔进去即可if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {// e:如果key不为null,并且找到了当前要插入的key一致的node元素,就保存在e中// k表示一个临时的keyNode<K,V> e; K k;// 2.表示该桶位中的第一个元素与你当前插入的node元素的key一致,表示后序要进行替换操作if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;// 3.表示当前桶位已经树化了else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);// 4.当前捅位是一个链表else {for (int binCount = 0; ; ++binCount) {// 4.1 条件成立的话,说明迭代到最后一个元素,也没有找到与要插入的key一致的node,说明需要加入到当前链表的尾部if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);//条件成立的话,说明当前链表的长度,达到了树化标准,需要进行树化if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}// 4.1 说明找到了与要插入的key一致的node元素,需要进行替换操作if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}// 如果找到了与要插入的key一致的node元素,那么进行替换if (e != null) { // existing mapping for keyV oldValue = e.value;**加粗样式**              if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}// nodeCount表示散列表table结构的修改次数,替换Node元素的value不算++modCount;//插入新元素,size自增,如果自增后的值大于扩容阈值,则触发扩容操作if (++size > threshold)resize();afterNodeInsertion(evict);return null;}

HashMap put 过程总结:

  1. 计算 key 的 hash 值。计算方式是 (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  2. 检查当前数组是否为空,为空需要进行初始化,初始化容量是16 ,负载因子默认0.75;
  3. 计算 key 在数组中的坐标。计算方式:(容量 - 1) & hash,因为容量总是2的次方,所以 -1 的值的二进制总是全1,方便与 hash 值进行与运算;
  4. 如果计算出的坐标元素为空,创建节点加入,put 结束。而如果当前数组容量大于负载因子设置的容量,进行扩容;
  5. 如果计算出的坐标元素有值;
    • 如果 next 节点为空,把要加入的值和 key 加入 next 节点;
    • 如果 next 节点不为空,循环查看 next 节点。如果发现有 next 节点的 key 和要加入的 key 一样,对应的值替换为新值;
    • 如果循环 next 节点查找超过8层还不为空,把这个位置元素转换为红黑树;
    • 如果坐标上的元素值和要加入的值 key 完全一样,覆盖原有值;
    • 如果坐标上的元素是红黑树,把要加入的值和 key 加入到红黑树;
    • 如果坐标上的元素和要加入的元素不同(尾插法增加)。

resize() 方法分析

  • 当在 table 长度位16中的元素移到 table 长度位32的 table 中的时候;我们可以知道,原来在15这个槽位的元素的 hash() 值的后四位一定是1111(因为跟1111即 table 长度-1进行与(&)运算得到了1111)。所以当 table长度变为32的时候,原来在15这个槽位的元素要么还在15这个槽位,要么在31这个槽位(因为原来15这个槽位的元素后五位一定是11111或者01111,跟11111即table新长度-1 进行与运算一定得到 01111或者11111)而11111 这种情况扩容后的槽位=扩容前槽位+扩容前数组长度。
/*** Initializes or doubles table size.  If null, allocates in* accord with initial capacity target held in field threshold.* Otherwise, because we are using power-of-two expansion, the* elements from each bin must either stay at same index, or move* with a power of two offset in the new table.** @return the table*///为什么需要扩容? 元素太多就会导致查询效率由O(1)->O(n) 扩容后使得元素更加分散,查询效率更高//为了解决哈希冲突导致的链化影响查询效率的问题,扩容会缓解该问题final Node<K,V>[] resize() {// oldTab:引用扩容前的哈希表Node<K,V>[] oldTab = table;// oldCap:表示扩容前table数组的长度int oldCap = (oldTab == null) ? 0 : oldTab.length;// oldThr:表示扩容之前的扩容阈值,触发本次扩容的阈值int oldThr = threshold;// newCap:扩容之后table数组的大小// newThr:扩容之后,下次再次触发扩容的条件int newCap, newThr = 0;//===================给newCap和newThr赋值start=============================// oldCap大于零,说明之前已经初始化过了(hashmap中的散列表不是null),要进行正常的扩容操作if (oldCap > 0) {// 扩容之前的table数组大小已经达到最大阈值后,则不扩容,且设置扩容条件为 int 最大值。if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}// (1)// oldCap左移一位实现数值翻倍(*2),并且赋值给newCap,newCap小于数组最大值限制 且扩容之前的阈值 >= 16// 这种情况下,则进行下一次扩容的阈值等于当前阈值翻倍else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // double threshold}// (2)// oldCap == 0(说明hashmap中的散列表是null)且oldThr > 0 ;下面几种情况都会出现oldCap == 0,oldThr > 0// 1.public HashMap(int initialCapacity);// 2.public HashMap(Map<? extends K, ? extends V> m);并且这个map有数据// 3.public HashMap(int initialCapacity, float loadFactor);else if (oldThr > 0) // initial capacity was placed in thresholdnewCap = oldThr;// oldCap == 0 ,oldThr == 0// new HashMap(); 默认初始化else {               // zero initial threshold signifies using defaultsnewCap = DEFAULT_INITIAL_CAPACITY; 16newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 12}// 对应上面(1)不成立或者(2)成立的情况 newThr为零时,通过newCap和loadFactor计算出一个newThrif (newThr == 0) { float ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}//===================给newCap和newThr赋值end=============================threshold = newThr;// 创建一个更长 更大的数组@SuppressWarnings({"rawtypes","unchecked"})Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];table = newTab;// 说明,hashMap本次扩容前,table不为nullif (oldTab != null) {for (int j = 0; j < oldCap; ++j) {// 当前头node节点Node<K,V> e;// 说明当前桶位中有数据,但是数据具体是  单个数据,还是链表 还是红黑树 并不知道if ((e = oldTab[j]) != null) {方便JVM GC时回收内存oldTab[j] = null;// 第一种情况:当桶位只有一个元素,从未发生哈希碰撞,这种情况直接计算出当前元素应该存放在 新数组中的位置, 然后扔进去即可if (e.next == null)newTab[e.hash & (newCap - 1)] = e;// 第二种情况:当前节点已经树化,红黑树else if (e instanceof TreeNode)((TreeNode<K,V>)e).split(this, newTab, j, oldCap);else { // preserve order// 第三种情况:桶位已经形成链表// 低位链表:存放扩容之的数组下标位置,与当期位置的下标位置一致Node<K,V> loHead = null, loTail = null;// 高位链表:存放扩容之后的数组下标位置为 当前数组下标位置 + 扩容之后数组的长度Node<K,V> hiHead = null, hiTail = null;Node<K,V> next;do {next = e.next;// hash->....1 1111// hash->....0 1111 if ((e.hash & oldCap) == 0) {if (loTail == null) // 存放在低位链中loHead = e;elseloTail.next = e;loTail = e;}else {if (hiTail == null)// 存放在高位链中hiHead = e;elsehiTail.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;}

get() 方法分析

public V get(Object key) {Node<K,V> e;return (e = getNode(hash(key), key)) == null ? null : e.value;}final Node<K,V> getNode(int hash, Object key) {// tab:引用当前hashmap的table// first:桶位中的头元素// n:table的长度// e:是临时Node元素// k:是key的临时变量Node<K,V>[] tab; Node<K,V> first, e; int n; K k;// 1.如果哈希表为空,或key对应的桶为空,返回nullif ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {// 2.这个桶的头元素就是想要找的if (first.hash == hash && // always check first node((k = first.key) == key || (key != null && key.equals(k))))return first;// 说明当前桶位不止一个元素,可能是链表,也可能是红黑树if ((e = first.next) != null) {// 3.树化了if (first instanceof TreeNode)return ((TreeNode<K,V>)first).getTreeNode(hash, key);// 4.链表do {if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))return e;} while ((e = e.next) != null);}}return null;}

HashMap get 方法流程总结。

  1. 计算 key 的 hash 值;
  2. 如果存储数组不为空,且计算得到的位置上的元素不为空。继续,否则,返回 Null;
  3. 如果获取到的元素的 key 值相等,说明查找到了,返回元素;
  4. 如果获取到的元素的 key 值不相等,查找 next 节点的元素;
    1. 如果元素是红黑树,在红黑树中查找;
    2. 不是红黑树,遍历 next 节点查找,找到则返回。

remove() 方法分析

 public V remove(Object key) {Node<K,V> e;return (e = removeNode(hash(key), key, null, false, true)) == null ?null : e.value;}    final Node<K,V> removeNode(int hash, Object key, Object value,boolean matchValue, boolean movable) {// tab:引用当前hashmap的table// p:当前的node元素// n:当前的散列表数组长度// index:表示寻址结果Node<K,V>[] tab; Node<K,V> p; int n, index;// 1.如果数组table为空或key映射到的桶为空,返回null。if ((tab = table) != null && (n = tab.length) > 0 &&(p = tab[index = (n - 1) & hash]) != null) {// node:查找到的结果// e:当前Node的下一个元素Node<K,V> node = null, e; K k; V v;// 2.桶位的头元素就是我们要找的,即为要删除的元素if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))node = p;// 说明当前桶位不止一个元素,可能是链表,可能是红黑树else if ((e = p.next) != null) {// 3.树化了if (p instanceof TreeNode)// 判断当前桶位是否升级为红黑树node = ((TreeNode<K,V>)p).getTreeNode(hash, key);// 红黑树查找操作// 4.链表中else {do {// 循环链表 查找node节点if (e.hash == hash &&((k = e.key) == key ||(key != null && key.equals(k)))) {node = e;break;}p = e;} while ((e = e.next) != null);// 顺着链表一直向下找}}// 删除数据// 如果node不为null,说明按照key查找到想要删除的数据了if (node != null && (!matchValue || (v = node.value) == value ||(value != null && value.equals(v)))) {// 第一种情况:node是树节点,说明需要进行树节点移除操作if (node instanceof TreeNode)((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);// 桶位元素即为查找结果,则将该元素的下一个元素放到桶位中else if (node == p)tab[index] = node.next;// 将当前元素p的下一个元素 设置成 要删除的下一个元素elsep.next = node.next;++modCount;--size;afterNodeRemoval(node);return node;}}return null;}

replace() 方法分析

 @Overridepublic boolean replace(K key, V oldValue, V newValue) {Node<K,V> e; V v;// 根据key找到元素,直接替换value。 if ((e = getNode(hash(key), key)) != null &&((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {e.value = newValue;afterNodeAccess(e);return true;}return false;}@Overridepublic V replace(K key, V value) {Node<K,V> e;if ((e = getNode(hash(key), key)) != null) {V oldValue = e.value;e.value = value;afterNodeAccess(e);return oldValue;}return null;}

isEmpty() 方法分析

/*** 如果map中没有键值对映射,返回true* * @return <如果map中没有键值对映射,返回true*/
public boolean isEmpty() {return size == 0;
}

putMapEntries() 方法分析

 final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {int s = m.size();if (s > 0) {// table为null,代表这里使用HashMap(Map<? extends K, ? extends V> m)构造函数 或者其它方式实例化hashmap但是还没往里面添加过元素if (table == null) { // pre-size//前面讲到,initial capacity*load factor就是当前hashMap允许的最大元素数目。那么不难理解,s/loadFactor+1即为应该初始化的容量。float ft = ((float)s / loadFactor) + 1.0F;int t = ((ft < (float)MAXIMUM_CAPACITY) ?(int)ft : MAXIMUM_CAPACITY);if (t > threshold)threshold = tableSizeFor(t);}//table已经初始化,并且map的大小大于临界值else if (s > threshold)//扩容处理resize();//将map中所有键值对添加到hashMap中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);}}}

putAll() 方法分析

/*** 将参数map中的所有键值对映射插入到hashMap中,如果有碰撞,则覆盖value。* @param m 参数map* @throws NullPointerException 如果map为null*/
public void putAll(Map<? extends K, ? extends V> m) {putMapEntries(m, true);
}

clear() 方法分析

/*** 删除map中所有的键值对*/
public void clear() {Node<K,V>[] tab;modCount++;if ((tab = table) != null && size > 0) {size = 0;for (int i = 0; i < tab.length; ++i)tab[i] = null;}
}

containsValue( Object value) 方法分析

/*** 如果hashMap中的键值对有一对或多对的value为参数value,返回true** @param value 参数value* @return 如果hashMap中的键值对有一对或多对的value为参数value,返回true*/
public boolean containsValue(Object value) {Node<K,V>[] tab; V v;//if ((tab = table) != null && size > 0) {//遍历数组tablefor (int i = 0; i < tab.length; ++i) {//遍历桶中的nodefor (Node<K,V> e = tab[i]; e != null; e = e.next) {if ((v = e.value) == value ||(value != null && value.equals(v)))return true;}}}return false;
}

基于JDK1.8---HashMap源码分析相关推荐

  1. JDK1.8 HashMap源码分析

    HahsMap实现了Map接口.其继承关系如下图: HashMap有两个影响性能的重要参数:初始容量和加载因子.容量是Hash表中桶的个数,当HashMap初始化时,容量就是初始容量.加载因子是衡量h ...

  2. 查询已有链表的hashmap_源码分析系列1:HashMap源码分析(基于JDK1.8)

    1.HashMap的底层实现图示 如上图所示: HashMap底层是由  数组+(链表)=(红黑树) 组成,每个存储在HashMap中的键值对都存放在一个Node节点之中,其中包含了Key-Value ...

  3. 源码分析系列1:HashMap源码分析(基于JDK1.8)

    1.HashMap的底层实现图示 如上图所示: HashMap底层是由  数组+(链表)+(红黑树) 组成,每个存储在HashMap中的键值对都存放在一个Node节点之中,其中包含了Key-Value ...

  4. Java类集框架 —— HashMap源码分析

    HashMap是基于Map的键值对映射表,底层是通过数组.链表.红黑树(JDK1.8加入)来实现的. HashMap结构 HashMap中存储元素,是将key和value封装成了一个Node,先以一个 ...

  5. 在参考了众多博客之后,我写出了多达三万字的HashMap源码分析,比我本科毕业论文都要精彩

    HashMap源码分析 以下代码都是基于java8的版本 HashMap简介 源码: public class HashMap<K,V> extends AbstractMap<K, ...

  6. hashmap源码分析及常用方法测试_一点课堂(多岸学院)

    HashMap 简介 底层数据结构分析 JDK1.8之前 JDK1.8之后 HashMap源码分析 构造方法 put方法 get方法 resize方法 HashMap常用方法测试 感谢 changfu ...

  7. HashMap 源码分析与常见面试题

    文章目录 HashMap 源码分析 jdk 1.7 内部常量 静态内部类 Holder 类 构造方法 put 过程 put 整体流程图 jdk 1.8 增加的常量 Node 类 Hash 值计算的变化 ...

  8. HashMap源码分析

    文章目录 简介 继承关系 存储结构 源码分析 属性 Node节点 TreeNode HashMap 构造方法 put 添加方法 待更新 简介 在我们使用数据存储的时候都会有数据结构这种东西,但是传统的 ...

  9. 【Java源码分析】Java8的HashMap源码分析

    Java8中的HashMap源码分析 源码分析 HashMap的定义 字段属性 构造函数 hash函数 comparableClassFor,compareComparables函数 tableSiz ...

  10. HashMap源码分析(转载)

    一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap  ...

最新文章

  1. matlab配对交易回测,精品案例 | 经典投资策略之配对交易策略
  2. 基于 ida 的反汇编转换 Obj 的可行性 笔记(1)
  3. 厉害!中国AI企业50强榜单!看完员工待遇,网友:我酸了!
  4. System.DBNull.Value与Null的区别
  5. java接口自动化(一) - 接口自动化测试整体认知 - 开山篇(超详解)
  6. 华为 android 5.0系统下载地址,Emui5.0+Android 华为Nova内测包
  7. Tomcat安装及配置教程(超详细的图文教程)
  8. 笔记本系统恢复连载之九:神舟笔记本系统恢复
  9. NOTEXPRESS 链接文件夹——让题录在文件夹中共享
  10. 伺服控制的三环控制原理及整定仿真和Simulink模型
  11. 抖音上热门的六大规律
  12. BPI 流程优化和BPR流程重组
  13. 【老生谈算法】matlab实现MF-TDMA系统中多用户多业务的无线接入控制和时隙分配算法源码——时隙分配算法
  14. 0716 process finished with exit code 0 解决
  15. Android 全屏悬浮窗适配(悬浮窗沉浸式)
  16. 揭秘世上唯一无癌国家吃什么?原来肿瘤也有克星
  17. CPU处理器的分类(ARM系列中央处理器)
  18. mysql查询用户留存语法(用户留存和用户留存率问题)
  19. 【CF #777 div2】A-C
  20. excel表格末尾添加一行_#天职经验谈# WORD表格技巧之 定位与公式

热门文章

  1. mysql MDL锁如何解决_MySQL元数据锁MDL
  2. java怎么复制动态数组_Java 数组排序复制等操作(Java Arraycopy)
  3. oracle数据库 gbk,oracle 数据库编码转换(转GBK) | 学步园
  4. 单片机中存储器扩展位地址线怎么算_小白学单片机 :AT89S51单片机基本硬件结构认识(2)...
  5. aes离线解密工具_CrazyCrypt2.1勒索病毒已有一键解密工具
  6. T75 大数加法+取模
  7. 哪怕你不认可,我还是要为R语言正名
  8. 呼之欲出的量子计算机和漫长的最后一公里
  9. HBase结合MapReduce批量导入
  10. signal---高级信号注册函数