深入理解HashMap
来源:互联网 发布:android 打开淘宝链接 编辑:程序博客网 时间:2024/06/06 02:49
1、HashMap 是什么
HashMap是散列表,K-V键值对集合。
2、HashMap 数据结构
1) 容量,增长因子,增长阔值, hashSeed 哈希因子,在
private int threshold; // =容量 * loadFactor 增长因子 默认= 16 * 0.75 = 12 ,容量的增长,主要原因是尽量避免Hash冲突,就是为了将 Map.Entry线性分布在 Map.Entry[] talbe中,也就是尽量做到 table数组中的元素的 next 为 null;
private loat loadFactor;//增长因子,默认为0.75 ,
transient int hashSeed = 0; // 在initHashSeedAsNeeded 初始化,并用于 final int hash(Object k) 方法中,算key的哈希值
2)private transient Map.Entry[] table; 也就是HashMap,内部维护着一个 元素类型为Map.Entry的数组。但这个数组是非连续存放的比如,第1个位置,第4个位置有值,
第2个位置,第三个位置为 null。
3)Map.Entry<K,V> 结构详解
private int hash;
private K key;
private V value;
prinvate Map.Entry<K,V> next; // 这里是关键中的关键
HashMap 存放图解
3、核心方法跟踪详解
---------------------------- put(K key, V value) 源码分析 -------------------------------------------------------
3.1 put(K key, V value);/** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old * value is replaced. * * @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 <tt>key</tt>, or * <tt>null</tt> if there was no mapping for <tt>key</tt>. * (A <tt>null</tt> return can also indicate that the map * previously associated <tt>null</tt> with <tt>key</tt>.) */ public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; } 实现思路: 首先,table == EMPTY_TABLE,判断是否是空数组,如果是,开始初始化HashMap的 table数据结构,inflate 膨胀,扩容的意思。那我们把目光投入到 inflateTable方法中 /** * Inflates the table. */ private void inflateTable(int toSize) { // Find a power of 2 >= toSize int capacity = roundUpToPowerOf2(toSize); // 首先计算容量, toSize 容量为 threshold,在构造方法中,threshold默认等于初始容量,也就是16 threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); // 然后重新算成 threshold的值,默认为 capacity * loadFactor//初始化数组 容量为 capacity initHashSeedAsNeeded(capacity); } private static int roundUpToPowerOf2(int number) { // assert number >= 0 : "number must be non-negative"; return number >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1; }//对于roundUpToPowerOf2解读// int number = 16;// 首先与MAXIMUM_CAPACITYB比较,,基本上不会大于,故,主要看 (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;// Integer.highestOneBit((number - 1) << 1) // (16 - 1) << 1 15的二进制为 00000000 00000000 00000000 00001111;然后向左移动一位,变为00000000 00000000 00000000 00011110// Integer.highestOneBit 函数,就是保留高位的第一个1,其他全部为0,所以,00000000 00000000 00000000 00010000,等于16//所以在初始化是,,int capacity = roundUpToPowerOf2(threshold),也就是能确保 HashMap的容量保持在2的幂。//然后计算threshold,增长阔值,也就是当容量达到该值后,需要重新扩充容量。/** * Initialize the hashing mask value. We defer initialization until we * really need it. */ final boolean initHashSeedAsNeeded(int capacity) { boolean currentAltHashing = hashSeed != 0; boolean useAltHashing = sun.misc.VM.isBooted() && (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD); boolean switching = currentAltHashing ^ useAltHashing; if (switching) { hashSeed = useAltHashing ? sun.misc.Hashing.randomHashSeed(this) : 0; } return switching; }继续上面的代码,执行完 inflate 容量初始化后, public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); //初始化HashMap数据接口,主要包括 Map.Entry[] table, int threshold, hasSeed; } if (key == null) // HashTable 支持key 为null return putForNullKey(value); int hash = hash(key); //计算 key 的hash值 int i = indexFor(hash, table.length); // 根据hash值,和表当前的长度,得到一个在数组中的 下标。;重点关注一下 indexFor 方法的实现。 // 该算法主要返回一个索引,0 - table.length-1的数组下标。 for (Entry<K,V> e = table[i]; e != null; e = e.next) { //接下来,找到 table[i]处,以及该处的数据链,看是否存在相同的key;判断key想到,首先判断hash值 //是否相等,然后再 判断key的equals方法是否相等 Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); // 添加元素到 HashMap,,然后将目光投入到 addEntry方法。 return null; }/** * Returns index for hash code h. */ static int indexFor(int h, int length) { // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2"; return h & (length-1); }接下来,重点关注 HashMap 增加一个Entry的实现过程。/** * Adds a new entry with the specified key, value and hash code to * the specified bucket. It is the responsibility of this * method to resize the table if appropriate. * * Subclass overrides this to alter the behavior of put method. */ void addEntry(int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) { // 是否需要扩容的判断;JDK1.7以后的扩容条件;size大于等于threshold,并且新添加元素所在 // 的索引值不等为空,也就是当size达到或超过threshold,新增加元素,只要不会引起hash冲 //突,则不扩容;JDK1.7之前,只要超过threshold,就扩容。如果需要扩容,长度增长到原来的两 //倍。接下来关注一下 resize方法的实现 resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); }/** * Rehashes the contents of this map into a new array with a * larger capacity. This method is called automatically when the * number of keys in this map reaches its threshold. * * If current capacity is MAXIMUM_CAPACITY, this method does not * resize the map, but sets threshold to Integer.MAX_VALUE. * This has the effect of preventing future calls. * * @param newCapacity the new capacity, MUST be a power of two; * must be greater than current capacity unless current * capacity is MAXIMUM_CAPACITY (in which case value * is irrelevant). */ void resize(int newCapacity) { // resize 方法元素的复制,在transfer方法中实现,并重新计算 threshold值。 Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable, initHashSeedAsNeeded(newCapacity)); table = newTable; threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); }/** * Transfers all entries from current table to newTable. * table数组及链表的复制。 */ void transfer(Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; for (Entry<K,V> e : table) { // 遍历 table 数组 while(null != e) { // 如果e 不等为空 Entry<K,V> next = e.next; // 先找到该节点的下一个节点, if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } int i = indexFor(e.hash, newCapacity); // 然后重新计算该节点在新数组中的索引 e.next = newTable[i]; // 然后 将新位置处的元素,当成新加入元素的下一个节点,然后将该节点放入到i的位置。 newTable[i] = e; e = next; // 然后继续遍历 e 节点原先的后继节点。 } } }// 产生一个有趣的现象就是,,在用一个数组索引处的 链表,,每进过一次resize后,,原先排在链表后面的元素,将会排到前面来。 比如如下hashMap结构 index Map.Entry list 0 null null 1 Entry1 Entyr2 ---> Entry3 2 null null resize 后 index Map.Entry list 0 Entry3 Entry2 ----> Entry1 1 null null 2 null null /** * Like addEntry except that this version is used when creating entries * as part of Map construction or "pseudo-construction" (cloning, * deserialization). This version needn't worry about resizing the table. * * Subclass overrides this to alter the behavior of HashMap(Map), * clone, and readObject. */ void createEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex];//首先获取 索引处的值,把这个值当成新节点的next。 table[bucketIndex] = new Entry<>(hash, key, value, e); size++; }----------------------------3.1 put(K key, V value) 源码分析 end-------------------------<pre name="code" class="java">-----------------------------3.2 HashMap 的遍历 public Set<Map.Entry> entrySet(); start -----------------------//首先关注HashMap中一个属性private transient Set<Map.Entry<K,V>> entrySet = null;public Set<Map.Entry<K,V>> entrySet() { return entrySet0();}private Set<Map.Entry<K,V>> entrySet0() { Set<Map.Entry<K,V>> es = entrySet; return es != null ? es : (entrySet = new EntrySet());}//所以代码的关键点在与 new EntrySet();//什么是EntrySet,原来是HashMap中的一静态的内部类,该类继承AbstractSet类,本身就是一个集合。private final class EntrySet extends AbstractSet<Map.Entry<K,V>> { public Iterator<Map.Entry<K,V>> iterator() { return newEntryIterator(); } public boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<K,V> e = (Map.Entry<K,V>) o; Entry<K,V> candidate = getEntry(e.getKey()); return candidate != null && candidate.equals(e); } public boolean remove(Object o) { return removeMapping(o) != null; } public int size() { return size; } public void clear() { HashMap.this.clear(); } }private final class EntryIterator extends HashIterator<Map.Entry<K,V>> { public Map.Entry<K,V> next() { return nextEntry(); } }// 重点关注一下 HashIterator 迭代器private abstract class HashIterator<E> implements Iterator<E> { Entry<K,V> next; // next entry to return //下一个元素,因为我们知道,HashMap内部的 Map.Entry<K,V>[] table内的元素是非连续的。所以访问下一元 // 素,不能简单的用 table[++index] 这个概念。 int expectedModCount; // For fast-fail 遍历时,HashMap结构变化的次数,如果在遍历期间 modCount发生变化,则直接报错,并结束遍历 int index; // current slot 当前位置 Entry<K,V> current; // current entry 当前元素 HashIterator() { expectedModCount = modCount; //设置开始遍历时,记录HashMap结构调整的次数 if (size > 0) { // advance to first entry // 在构造方法时,先在table数组中找到第一不为空的元素,存入next属性中。 Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } } public final boolean hasNext() { //判断是否有下一个可迭代元素,只需要判断 next是否为空即可。 return next != null; } final Entry<K,V> nextEntry() { // 遍历的核心算法 if (modCount != expectedModCount) //如果在遍历过程,有新增元素,删除元素动作,就直接抛异常。 throw new ConcurrentModificationException(); Entry<K,V> e = next; // 如果下一个元素为空,直接报元素异常 if (e == null) throw new NoSuchElementException(); // nextEntry,主要实现思路是:将next的值,赋值给当前元素,然后尝试获取下一个非空Map.Entry元 // 素,找到后,赋值给next元素 if ((next = e.next) == null) { // 这句很关键,作用,先将 要本次返回的元素 next返回【开始遍历链表了】,如果该元素的next为空, //说明该元素下面没有链表,说明该hash没有冲突,然后再遍历table数组,找到下一个非空元素。 Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } current = e; return e; } public void remove() { // 将迭代器 中current所存储的元素,对应的key,删除,然后将current域设置为空,并重新设置 // expectedModCount的值等于modCount,,而current的赋值操作,发送在 next方法中,故 // 故,如过在遍历过程中,想删除当前元素,it.remove()方法要在it.next()方法之后调用。 if (current == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); Object k = current.key; current = null; HashMap.this.removeEntryForKey(k); expectedModCount = modCount; } }//验收 再遍历 HashMap时,remove方法与next方法的使用public static void main(String[] args) { // TODO Auto-generated method stub HashMap<String, String> a = new HashMap(); a.put("a", "a"); a.put("b", "b"); a.put("c", "c"); a.put("d", "d"); a.put("e", "e"); a.put("f", "f"); int i = 0; for (Iterator<Map.Entry<String, String>> it = a.entrySet().iterator(); it.hasNext();) { if(i == 2) { // 如果 修改为 i == 0,,则会抛出异常 IllegalStateException 异常。 it.remove(); } Map.Entry<String, String> entry = it.next(); System.out.println(entry.getKey() + ":" + entry.getValue()); i ++; } System.out.println(a); }-----------------------------3.2 HashMap 的遍历 public Set<Map.Entry> entrySet(); end-----------------------
1 0
- 深入理解HashMap
- 深入理解HashMap
- [zt] 深入理解HashMap
- 深入理解HashMap
- 深入理解HashMap
- 深入理解HashMap
- Hashmap的深入理解
- 深入理解HashMap
- 深入理解HashMap
- 深入理解HashMap
- 深入理解HashMap
- 深入理解hashMap
- 深入理解HashMap
- 深入理解HashMap
- 深入理解HashMap
- (转)深入理解HashMap
- (转)深入理解HashMap
- 深入理解HashMap
- 小demo,自己留着练手//getElementsByTagName
- 使用JdbcTemplate模板时传递的参数Map和Object []数组
- Out of Memory,Matlab
- Python下调用json.dumps中文显示问题及解决办法
- angular之页面跳转隐藏tab
- 深入理解HashMap
- 实现MongoDB多数据源的自动切换
- hdoj-【2136 Largest prime factor】
- Docker系列~代码放在Docker里面还是外面?(八)
- surfview 的一般使用
- vs2012,如何调试dll工程
- python的excel操作
- FlushedInputStream:Android下InputStream发生网络中断时的解决办法
- 安卓 四种控制键盘的方法