jdk HashMap源码解读
一、哈希表介绍
在哈希表中进行添加,删除,查找等操作,性能十分高,在不考虑哈希冲突的情况下,仅需一次定位即可完成,时间复杂度为O(1),接下来说明这是怎么达到的。
数据结构的物理存储结构只有两种:顺序存储结构和链式存储结构(像栈,队列,树,图等是从逻辑结构去抽象的,映射到内存中,也是这两种物理组织形式),哈希表的主干就是数组+链表(当链表长度达到一定值时(默认是8),就会将链表转化为红黑树)。
如下图所示:
二、参数
2.1 Node
Node是HashMap的基本组成单元,每一个Node包含一个Key-Value键值对,它是HashMap中的一个静态内部类。
/*** Basic hash bin node, used for most entries. (See below for* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)*/static class Node<K,V> implements Map.Entry<K,V> {final int hash;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; }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;if (Objects.equals(key, e.getKey()) &&Objects.equals(value, e.getValue()))return true;}return false;}}
2.2 table
这个数组就是哈希表的主干,在第一次使用的时候初始化,并且在需要重新分配大小的时候会重新分配容量。被分配时,数组的长度总是2的倍数(在某些操作中,为了支持bootstrapping机制,数组的长度同样可以为0)
transient Node<K,V>[] table;
从table+Node可以看出,HashMap是由数组+链表组成的,数组是HashMap的主体,链表则主要是为了解决哈希冲突而存在的。
2.3 参数列表
2.3.1 size
指明了HashMap中包含多少键值对
transient int size;
2.3.2 DEFAULT_INITIAL_CAPACITY
HashMap的默认大小是16.
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 1
2.3.3 threshold
当size到达threshold时数组会扩容,这个值等于capacity*loadFactor。(默认会初始化为DEFAULT_INITIAL_CAPACITY,16)
int threshold;
2.3.4 loadFactor
负载因子,代表了table的填充度有多少,默认是0.75,加载因子存在的原因就是为了减少哈希冲突,如果初始桶为16,等到满16个元素再扩容,某些桶里面可能有多于一个的元素了,所以加载因子小于1。
final float loadFactor;
2.3.5 modCount
这个变量记载了HashMap被改变(代表着改变键值对的数量、键值对本身或者使用rehash改变了内部结构)的次数。因为HashMap非线程安全,在对HashMap进行迭代时,如果期间其他线程的参与导致HashMap的结构发生改变了,需要抛出ConcurrentModificationException异常。
可以改变modCount的操作:
- put
transient int modCount;
三、方法
3.1 构造函数
注意在HashMap的构造方法中,发现没有执行new操作,猜测可能跟ArrayList一样是在第一次添加的时候开辟的内存(在put中实现)
传入初始容量和负载因子的构造器
public HashMap(int initialCapacity, float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: " +loadFactor);this.loadFactor = loadFactor;this.threshold = tableSizeFor(initialCapacity);}
仅传入初始容量的构造器,将负载因子初始化为0.75
public HashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR);}
默认构造器,将capacity初始化为16,loadFactor初始化为0.75.
public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}
根绝传入的Map构造出一个新的HashMap,负载因子为0.75,capacity设置为足够容量传入Map的元素。
public HashMap(Map<? extends K, ? extends V> m) {this.loadFactor = DEFAULT_LOAD_FACTOR;putMapEntries(m, false);}
注意从上面的代码中我们可以看出,在常规构造器中,没有为数组分配内存空间(有一个入参为指定Map的构造器除外),而是在执行put操作的时候才真正构建table数组。
3.2 put
逻辑图:
将特定的Key-Value键值对存储进入哈希表中,如果已经存在了Key,那么更新Value的值。
返回值:
当插入前不存在Key的时候返回null(返回null还有可能是之前与Key对应的Value值为null),当插入前存在Key的时候返回之前与Key结合的Value值。
public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}
3.2.1 putVal
参数:
hash:Key的哈希值
Key:Key
Value:Value
onlyIfAbsent:当这个值为真的时候,不会修改已经存在的Key-Value键值对
evict:if false, the table is in creation mode.
返回值:
如果插入前存在Key-Value键值对,返回旧值,否则返回null
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;//当table数组为空数组时,进行resize()初始化if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;//这里注意,当key值为null时,hash值为0,存储在table[0]处//当table[i]为空时,直接存储//这里i=(n-1)&hash和hash%n相等,但是按位相与运算更快if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);//当table[i]已经存在了其他的值,这时候要利用链进行处理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);//遍历链,直到最后一个节点的next为null或者有重复的key值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 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}//原本存在键值对的情况,替换valueif (e != null) {V oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}//保证并发访问时,如果HashMap内部结构发生变化,则快速响应失败++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;}
同时注意HashMap的线程不安全就体现在这里:
看上面源码的11行if ((p = tab[i = (n - 1) & hash]) == null)
,这句话用来判断是否出现hash碰撞。
假设两个线程A、B都在进行put操作,并且hash函数计算出的插入下标是相同的,当线程A执行完第六行代码后由于时间片耗尽导致被挂起,而线程B得到时间片后在该下标处插入了元素,完成了正常的插入,然后线程A获得时间片,由于之前已经进行了hash碰撞的判断,所以此时不会再进行判断,而是直接进行插入,这就导致了线程B插入的数据被线程A覆盖了,从而线程不安全。
除此之外,还有就是代码的50行有一个++size
,还是线程A、B,这两个线程同时进行put操作时,假设当前HashMap的zise大小为10,当线程A执行到第38行代码时,从主内存中获得size的值为10后准备进行+1操作,但是由于时间片耗尽只好让出CPU,线程B快乐的拿到CPU还是从主内存中拿到size的值10进行+1操作,完成了put操作并将size=11写回主内存,然后线程A再次拿到CPU并继续执行(此时size的值仍为10),当执行完put操作后,还是将size=11写回内存,此时,线程A、B都执行了一次put操作,但是size的值只增加了1,所有说还是由于数据覆盖又导致了线程不安全。
3.2.1.1 resize
初始化或者将table的大小翻倍。如果原HashMap不为空,则如果原数组上只有单个的元素,将这个元素映射到扩容后的数组的相同位置,如果原数组上存在链,则将链上的元素拆分,拆分依据是原先length位置的最高位是否为一。因为之前的映射是hash与length-1按位相与,此时扩容了一倍后多出了一个1,比如原来capacity为8,则与0111按位相与,扩容为16后则与1111按位相与,所以可以按照最高位是否为1,即与1000相与是否为0,来判断原先在一条链上的元素是继续存储在原来的位置,还是在原来的位置上加上一个old length,存放到新的位置。
final Node<K,V>[] resize() {Node<K,V>[] oldTab = table;int oldCap = (oldTab == null) ? 0 : oldTab.length;int oldThr = threshold;int newCap, newThr = 0;//原HashMap已经被初始化过的情况if (oldCap > 0) {//超过最大值,不扩容,只通过碰撞处理if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}//没有超过最大值,那么扩充为原来的两倍else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // double threshold}//因为初始时capacity被放置在threshold中,所以将capacity设置为thresholdelse if (oldThr > 0) newCap = oldThr;//利用默认值初始化else { // zero initial threshold signifies using defaultsnewCap = DEFAULT_INITIAL_CAPACITY;newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}//重新计算threshold的值,得到新的resize上限if (newThr == 0) {float ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}threshold = newThr;@SuppressWarnings({"rawtypes","unchecked"})Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];table = newTab;//扩容时原HashMap非空if (oldTab != null) {//把每个bucket都移动到新的buckets中去for (int j = 0; j < oldCap; ++j) {Node<K,V> e;if ((e = oldTab[j]) != null) {//清除掉原来table[i]中的值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 orderNode<K,V> loHead = null, loTail = null;Node<K,V> hiHead = null, hiTail = null;Node<K,V> next;do {next = e.next;//不存在高位,这些元素调整后放到原来oldCapacity的位置中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;}
3.2.1.2 newNode
创建一个常规(非树)的节点
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {return new Node<>(hash, key, value, next);}
3.2.2 hash
当我们put的时候,首先计算Key的hash值,这里就调用了hash方法,hash方法实际是让key.hashCode()与key.hashCode>>>16进行异或运算,高16位补0,一个数和0异或结果不变,所以hash函数大概的作用就是:
高16bit不变,低16bit和高16bit做了一个异或,目的是减少碰撞。因为底层的table数组大小是2的幂,计算下标index=(table.length-1)&hash,如果不做hash处理,相当于散列生效的只有几个低bit位,为了减少散列的碰撞,设计者综合考虑了速度、作用、质量之后,使用高16bit和低16bit异或来简单处理减少碰撞。
/*** Computes key.hashCode() and spreads (XORs) higher bits of hash* to lower. Because the table uses power-of-two masking, sets of* hashes that vary only in bits above the current mask will* always collide. (Among known examples are sets of Float keys* holding consecutive whole numbers in small tables.) So we* apply a transform that spreads the impact of higher bits* downward. There is a tradeoff between speed, utility, and* quality of bit-spreading. Because many common sets of hashes* are already reasonably distributed (so don't benefit from* spreading), and because we use trees to handle large sets of* collisions in bins, we just XOR some shifted bits in the* cheapest possible way to reduce systematic lossage, as well as* to incorporate impact of the highest bits that would otherwise* never be used in index calculations because of table bounds.*/static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}
3.3 get
给定Key,查找对应的Value值,如果不存在key值,返回null。(同时注意返回null并不能说明不存在键值对,还有可能是Key对应的值为value,可以使用containsKey进行区分)。
public V get(Object key) {Node<K,V> e;return (e = getNode(hash(key), key)) == null ? null : e.value;}
3.3.1 getNode
当定位到的数组位置不含链表时,那么查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,那么会遍历这个链表(当一个链表上的元素过多时,会转化为树处理)
/*** Implements Map.get and related methods.** @param hash hash for key* @param key the key* @return the node, or null if none*/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;}
3.3.2 hash
/*** Computes key.hashCode() and spreads (XORs) higher bits of hash* to lower. Because the table uses power-of-two masking, sets of* hashes that vary only in bits above the current mask will* always collide. (Among known examples are sets of Float keys* holding consecutive whole numbers in small tables.) So we* apply a transform that spreads the impact of higher bits* downward. There is a tradeoff between speed, utility, and* quality of bit-spreading. Because many common sets of hashes* are already reasonably distributed (so don't benefit from* spreading), and because we use trees to handle large sets of* collisions in bins, we just XOR some shifted bits in the* cheapest possible way to reduce systematic lossage, as well as* to incorporate impact of the highest bits that would otherwise* never be used in index calculations because of table bounds.*/static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}
3.3.2.1. hashCode
/*** Returns a hash code value for the object. This method is* supported for the benefit of hash tables such as those provided by* {@link java.util.HashMap}.* <p>* The general contract of {@code hashCode} is:* <ul>* <li>Whenever it is invoked on the same object more than once during* an execution of a Java application, the {@code hashCode} method* must consistently return the same integer, provided no information* used in {@code equals} comparisons on the object is modified.* This integer need not remain consistent from one execution of an* application to another execution of the same application.* <li>If two objects are equal according to the {@code equals(Object)}* method, then calling the {@code hashCode} method on each of* the two objects must produce the same integer result.* <li>It is <em>not</em> required that if two objects are unequal* according to the {@link java.lang.Object#equals(java.lang.Object)}* method, then calling the {@code hashCode} method on each of the* two objects must produce distinct integer results. However, the* programmer should be aware that producing distinct integer results* for unequal objects may improve the performance of hash tables.* </ul>* <p>* As much as is reasonably practical, the hashCode method defined by* class {@code Object} does return distinct integers for distinct* objects. (This is typically implemented by converting the internal* address of the object into an integer, but this implementation* technique is not required by the* Java™ programming language.)** @return a hash code value for this object.* @see java.lang.Object#equals(java.lang.Object)* @see java.lang.System#identityHashCode*/public native int hashCode();
3.4 containsKey
public boolean containsKey(Object key) {return getNode(hash(key), key) != null;}
3.5 getOrDefault
当不存在这样的键值对或者原键指向的值为null时,返回defaultValue
@Overridepublic V getOrDefault(Object key, V defaultValue) {Node<K,V> e;return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;}
四、注意事项
在各种资料上都会提到:“重写equals时也要同时覆盖hashcode”,我们举个例子来看,如果重写了equals而不重写hashcode会发生什么样的问题。
4.1 重写和没有重写hashcode的对比
import java.util.*;public class test {private static class Person{int idCard;String name;public Person(int idCard, String name) {this.idCard = idCard;this.name = name;}@Overridepublic boolean equals(Object o) {if (this == o) {return true;}if (o == null || getClass() != o.getClass()){return false;}Person person = (Person) o;//两个对象是否等值,通过idCard来确定return this.idCard == person.idCard;}}public static void main(String []args){HashMap<Person,String> map = new HashMap<Person, String>();Person person = new Person(1234,"乔峰");System.out.println(person.hashCode());//put到hashmap中去map.put(person,"天龙八部");//get取出,从逻辑上讲应该能输出“天龙八部”Person x=new Person(1234,"萧峰");System.out.println(person.equals(x));System.out.println(x.hashCode());System.out.println("结果:"+map.get(x));}
}
输出结果:
460141958
true
1163157884
结果:null
重写后:
import java.util.*;public class test {private static class Person{int idCard;String name;public Person(int idCard, String name) {this.idCard = idCard;this.name = name;}@Overridepublic boolean equals(Object o) {if (this == o) {return true;}if (o == null || getClass() != o.getClass()){return false;}Person person = (Person) o;//两个对象是否等值,通过idCard来确定return this.name.equals(person.name);}@Overridepublic int hashCode(){return name.hashCode();}}public static void main(String []args){HashMap<Person,String> map = new HashMap<Person, String>();Person person = new Person(12345678,"乔峰");System.out.println(person.hashCode());//put到hashmap中去map.put(person,"天龙八部");//get取出,从逻辑上讲应该能输出“天龙八部”Person x=new Person(1234,"乔峰");System.out.println(person.equals(x));System.out.println(x.hashCode());System.out.println("结果:"+map.get(x));}
}
输出结果:
645404
true
645404
结果:天龙八部
对比两个输出结果可以看出,如果没有重写hashcode,尽管我们在进行get和put操作的时候,使用的key从逻辑上是等值的(通过equals比较是相等的),但是由于没有重写hashcode方法,put操作时的hash值和get时候的hash值不相等就会定位到数组的不同地方,无法取到我们想要的值。
4.2 源码
首先看下equals方法和hashcode的源码
4.2.1 hashcode
/*** Returns a hash code value for the object. This method is* supported for the benefit of hash tables such as those provided by* {@link java.util.HashMap}.* <p>* The general contract of {@code hashCode} is:* <ul>* <li>Whenever it is invoked on the same object more than once during* an execution of a Java application, the {@code hashCode} method* must consistently return the same integer, provided no information* used in {@code equals} comparisons on the object is modified.* This integer need not remain consistent from one execution of an* application to another execution of the same application.* <li>If two objects are equal according to the {@code equals(Object)}* method, then calling the {@code hashCode} method on each of* the two objects must produce the same integer result.* <li>It is <em>not</em> required that if two objects are unequal* according to the {@link java.lang.Object#equals(java.lang.Object)}* method, then calling the {@code hashCode} method on each of the* two objects must produce distinct integer results. However, the* programmer should be aware that producing distinct integer results* for unequal objects may improve the performance of hash tables.* </ul>* <p>* As much as is reasonably practical, the hashCode method defined by* class {@code Object} does return distinct integers for distinct* objects. (This is typically implemented by converting the internal* address of the object into an integer, but this implementation* technique is not required by the* Java™ programming language.)** @return a hash code value for this object.* @see java.lang.Object#equals(java.lang.Object)* @see java.lang.System#identityHashCode*/public native int hashCode();
4.2.2 equals
/*** Indicates whether some other object is "equal to" this one.* <p>* The {@code equals} method implements an equivalence relation* on non-null object references:* <ul>* <li>It is <i>reflexive</i>: for any non-null reference value* {@code x}, {@code x.equals(x)} should return* {@code true}.* <li>It is <i>symmetric</i>: for any non-null reference values* {@code x} and {@code y}, {@code x.equals(y)}* should return {@code true} if and only if* {@code y.equals(x)} returns {@code true}.* <li>It is <i>transitive</i>: for any non-null reference values* {@code x}, {@code y}, and {@code z}, if* {@code x.equals(y)} returns {@code true} and* {@code y.equals(z)} returns {@code true}, then* {@code x.equals(z)} should return {@code true}.* <li>It is <i>consistent</i>: for any non-null reference values* {@code x} and {@code y}, multiple invocations of* {@code x.equals(y)} consistently return {@code true}* or consistently return {@code false}, provided no* information used in {@code equals} comparisons on the* objects is modified.* <li>For any non-null reference value {@code x},* {@code x.equals(null)} should return {@code false}.* </ul>* <p>* The {@code equals} method for class {@code Object} implements* the most discriminating possible equivalence relation on objects;* that is, for any non-null reference values {@code x} and* {@code y}, this method returns {@code true} if and only* if {@code x} and {@code y} refer to the same object* ({@code x == y} has the value {@code true}).* <p>* Note that it is generally necessary to override the {@code hashCode}* method whenever this method is overridden, so as to maintain the* general contract for the {@code hashCode} method, which states* that equal objects must have equal hash codes.** @param obj the reference object with which to compare.* @return {@code true} if this object is the same as the obj* argument; {@code false} otherwise.* @see #hashCode()* @see java.util.HashMap*/public boolean equals(Object obj) {return (this == obj);}
由上面的源码可知,equals在其内部是调用了“==”,所以在不重写equals方法的情况下,equals方法是比较两个对象是否具有相同的引用,即是否指向了同一块内存地址。
而hashcode是一个本地方法,它返回的是这个对象的内存地址。
4.2.3 hashcode的规定
在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对同一个对象的多次调用,hashCode方法都必须始终返回同一个值。在一个应用程序与另一个应用程序的执行过程中,执行hashCode方法所返回的值可以不一致。
如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中的hashCode方法都必须产生同样的整数结果
如果两个对象根据equals(Object)方法比较是不相等的,那么调用这两个对象中的hashCode方法,则不一定要求hashCode方法必须产生不用的结果。但是程序员应该知道,给不相等的对象产生截然不同的整数结果,有可能提高散列表的性能。
由上面三条规定可知,如果重写了equals方法而没有重写hashcode方法的话,就违反了第二条规定。相等的对象必须拥有相同的hashcode
jdk HashMap源码解读相关推荐
- HashMap源码解读(中篇)
文章目录 前言 一.进入JDK中的源码(InteliJ IDEA为例) 二.HashMap的结构 三.源码解读 3.1 属性解读 3.2 put方法解读 3.2.1 HashMap中的hash方法 3 ...
- 【JavaMap接口】HashMap源码解读实例
这里写自定义目录标题 1.HashMap源码 2.HashMap使用 1.HashMap源码 解读HashMap的源码 执行构造器 new HashMap() 初始化加载因子 loadfactor = ...
- HashMap源码解读—Java8版本
[手撕源码系列]HashMap源码解读-Java8版本 一.HashMap简介 1.1 原文 1.2 翻译 1.3 一语中的 1.4 线程安全性 1.5 优劣分析 二.定义 三.数据结构 四.域的解读 ...
- HashMap源码解读
一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap ...
- 《数据解构》HashMap源码解读
- HashMap、ConcurrentHashMap源码解读(JDK7/8)
下载地址(已将图片传到云端,md文件方便浏览更改):https://download.csdn.net/download/hancoder/12318377 推荐视频地址: https://www.b ...
- JDK 1.6 HashMap 源码分析
前言 前段时间研究了一下JDK 1.6 的 HashMap 源码,把部份重要的方法分析一下,当然HashMap中还有一些值得研究得就交给读者了,如有不正确之处还望留言指正. 准备 需要熟悉数组 ...
- Java Review - HashMap HashSet 源码解读
文章目录 概述 HashMap结构图 构造函数 重点方法源码解读 (1.7) put() get() remove() 1.8版本 HashMap put resize() 扩容 get HashSe ...
- 【Java深入研究】9、HashMap源码解析(jdk 1.8)
一.HashMap概述 HashMap是常用的Java集合之一,是基于哈希表的Map接口的实现.与HashTable主要区别为不支持同步和允许null作为key和value.由于HashMap不是线程 ...
- 疯狂解读HashMap源码
主要针对jdk1.8源码解读 Q:HashMap原理,内部数据结构? A:底层时使用哈希表(数组+链表),当链表过长会将链表转成 红黑树以实现O(logn)时间复杂度内查找. Q:HashMap里pu ...
最新文章
- 20、C#里面方法的创建和显示
- 中的 隐藏鼠标菜单_如何在鼠标右键菜单中添加自定义菜单?工效率提升一倍...
- javascript 中 split 函数分割字符串成数组
- python输入三个整数_python笔记3:依次输入3个数排序打
- ASP.NET Core 项目配置 ( Startup ) - ASP.NET Core 基础教程 - 简单教程,简单编程
- Eric的日期选择下拉列表小控件
- 编码的奥秘:编码与组合
- Android端记录跑步计步运动轨迹数据的App
- 苹果电脑Mac系统如何下载安装谷歌Chrome浏览器
- 手机电视(CMMB+MBBMS)安全架构原理
- word2016 无响应问题解决办法
- matlab群速度色散系数,第三章-群速度色散.ppt
- 好佳居软装十大品牌 软装这些知识我们要知道
- 容联七陌云客服通话超强稳定,今通国际客户服务更加便捷
- 使用Python连接阿里云盘
- idea打不开,双击没反应的解决方案
- 刚性微分方程与非刚性的大概区分(自看,求指点)
- Windows10蓝屏触发及分析
- 百度AI的语音识别与语音合成
- CodeForces - 298B Sail (思维题)
热门文章
- do while新用法--方便数据验证时跳出
- bat 指定hid驱动加载_Linux驱动程序学习一 (续)
- 详细解读二叉树基本操作
- java并发编程实战读书笔记之FutureTask
- 嵌入式linux寄存器移位寻址,嵌入式系统及应用》 (32+16)教学大纲
- Mysql同个用户退款订单_微信退款全款退,退一部分,分开退一次以上区别跳坑[订单金额或退款金额与之前请求不一致]...
- 借助 Flink 与 Pulsar,BIGO 打造实时消息处理系统
- 7 分钟了解 eBay Flink 服务的端到端管理
- 我在创业公司的开发经验总结
- Java基础---Java---基础加强---类加载器、委托机制、AOP、 动态代理技术、让动态生成的类成为目标类的代理、实现Spring可配置的AOP框架