1类签名与注释

  1. public class HashMap<K,V> extends AbstractMap<K,V>
  2. implements Map<K,V>, Cloneable, Serializable

HashMap是基于哈希表实现的Map接口。 此实现提供了所有可选的地图操作,并允许null的值和null键。 ( HashMap类大致相当于Hashtable ,除了它是不同步的,并允许null)。该类不能保证map的顺序,特别是,它不能保证顺序在一段时间内保持不变(因为hash数组扩容时会重新散列)。

假设哈希函数在这些存储桶之间合理分散元素,这个实现为基本操作( getput )提供了恒定的时间性能。收集视图的迭代与HashMap实例(桶数)加上其大小(键值映射数)的容量成正比 。 因此,想要好的迭代性能,不要将初始容量设置得太高(或负载因子太低)。

HashMap的一个实例有两个影响其性能的参数: 初始容量(capacity)和负载因子(load factor) 。 容量是哈希表中的桶数,初始容量只是创建哈希表时的容量。 负载因子是在容量自动增加之前允许哈希表得到满足的度量。 当在散列表中的条目的数量超过了负载因数和当前容量的乘积,哈希表被重新散列 (即,内部数据结构被重建),使得哈希表具有桶的大约两倍。

作为一般规则,默认负载因子(0.75)提供了时间和空间成本之间的良好折中。 更高的值会降低空间开销,但会增加查找成本(反映在HashMap类的大部分操作中,包括getput )。 在设置其初始容量时,应考虑map中预期的条目数及其负载因子,以便最小化rehash操作的数量。 如果初始容量大于最大条目数除以负载因子,则不会发生重新排列操作。

如果有许多映射要存储在HashMap实例中,那么创建足够大的容量存储要比它自己自动扩容再rehash的效率高。注意,多个keys使用同样的hashCode会降低hash表的性能。为了改善影响,当按键是Comparable时,这个类可以使用keys之间的比较顺序来帮助打破关系。

请注意,此实现不同步。 如果多个线程同时访问哈希映射,并且至少有一个线程在结构上修改了映射,那么它必须在外部进行同步。 (结构修改是添加或删除一个或多个映射的任何操作;仅改变与实例已经包含的密钥相关联的值不是结构修改。)这可以通过同步对象来实现,如果没有同步对象,map应该使用Collections.synchronizedMap方法“包装”。 这最好在创建时完成,以防止意外的不同步访问map:

  1. Map m = Collections.synchronizedMap(new HashMap(...));

被该类的所有集合视图方法返回的迭代器都是fail-fast的:如果映射在迭代器创建之后的任何时间被结构地修改,除了通过迭代器自己的remove方法之外,迭代器将抛出一个ConcurrentModificationException 。 因此,面对并发修改,迭代器将快速而干净地失败,而不是在未来未确定的时间冒着任意的非确定性行为。

请注意,迭代器的故障快速行为无法保证,迭代器的故障快速行为应仅用于检测错误,而不是依赖它来保证程序的正确性。

该类是Java Collections Framework的成员。

2成员变量

  1. // 序列号
  2. private static final long serialVersionUID = 362498820763181265L;
  3. // 默认初始化容量16,要求必须是2的n次方。
  4. static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  5. // 最大容量,因为必须是2的n次方,int中最大只能到230
  6. static final int MAXIMUM_CAPACITY = 1 << 30;
  7. // 默认负载因子
  8. static final float DEFAULT_LOAD_FACTOR = 0.75f;

  9. //当桶中的键值对数量到达8个,且桶数量大于等于64,则将底层实现从链表转为红黑
    // 如果桶中的键值对到达该阀值,则检测桶数量 
    static final int TREEIFY_THRESHOLD = 8;
  10.  
  11. static final int UNTREEIFY_THRESHOLD = 6;
  12. //当桶数(底层数组长度)到达64个的时候,则将链表转为红黑树
  13. static final int MIN_TREEIFY_CAPACITY = 64;
  14.  
  15. -------------------------------------------------------------------------------
  16.  
  17. transient Node<K,V>[] table;
  18.  
  19. //Holds cached entrySet(). Note that AbstractMap fields are used for keySet() and values().
  20. transient Set<Map.Entry<K,V>> entrySet;
  21.  
  22. //The number of key-value mappings contained in this map.
  23. transient int size;
  24.  
  25. transient int modCount;
  1. //当集合没有分配空间时,表示初始的数组容量。当分配了,则表示(capacity * load factor)
    //注意理解这个属性,否则后面的代码不好理解
    int threshold;
  2.  
  3. //The load factor for the hash table.
  4. final float loadFactor;

存储桶table是用Node型的数组表示的,Node是一个静态内部类,实现了Map.Entry接口。这里将每个Entity封装成一个链表的节点,每个节点指向下一个节点(如果有的话)。

所以hashMap是用数组和链表实现的。(当当达到一定条件后,由“数组/链表”变为“数组/红黑树”实现)

  1. static class Node<K,V> implements Map.Entry<K,V> {
  2. final int hash;
  3. final K key;
  4. V value;
  5. Node<K,V> next;
  6.  
  7. Node(int hash, K key, V value, Node<K,V> next) {
  8. this.hash = hash;
  9. this.key = key;
  10. this.value = value;
  11. this.next = next;
  12. }
  13.  
  14. public final K getKey() { return key; }
  15. public final V getValue() { return value; }
  16. public final String toString() { return key + "=" + value; }
  17.  
  18. public final int hashCode() {
  19. return Objects.hashCode(key) ^ Objects.hashCode(value);
  20. }
  21.  
  22. public final V setValue(V newValue) {
  23. V oldValue = value;
  24. value = newValue;
  25. return oldValue;
  26. }
  27.  
  28. public final boolean equals(Object o) {
  29. if (o == this)
  30. return true;
  31. if (o instanceof Map.Entry) {
  32. Map.Entry<?,?> e = (Map.Entry<?,?>)o;
  33. if (Objects.equals(key, e.getKey()) &&
  34. Objects.equals(value, e.getValue()))
  35. return true;
  36. }
  37. return false;
  38. }
  39. }

3构造函数

  1. //(1)指定初始容量initialCapacity与负载因子loadFactor
    public HashMap(int initialCapacity, float loadFactor) {
  2. if (initialCapacity < 0)
  3. throw new IllegalArgumentException("Illegal initial capacity: " +
  4. initialCapacity);
  5. if (initialCapacity > MAXIMUM_CAPACITY) //初始容量最大值
  6. initialCapacity = MAXIMUM_CAPACITY;
  7. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  8. throw new IllegalArgumentException("Illegal load factor: " +
  9. loadFactor);
  10. this.loadFactor = loadFactor;
  11. this.threshold = tableSizeFor(initialCapacity);
  12. }
    //该方法可以将参数cap值改为2n,2n-1<cap≤2n
  13. static final int tableSizeFor(int cap) {
  14. int n = cap - 1;
  15. n |= n >>> 1;
  16. n |= n >>> 2;
  17. n |= n >>> 4;
  18. n |= n >>> 8;
  19. n |= n >>> 16;
  20. return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
  21. }

  22. //(2)指定初始容量,负载因子默认0.75
  23. public HashMap(int initialCapacity) {
  24. this(initialCapacity, DEFAULT_LOAD_FACTOR);
  25. }
  26. //(3)默认初始容量16(在put时会检查扩容),负载因子默认0.75
  27. public HashMap() {
  28. this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
  29. }

  30. public HashMap(Map<? extends K, ? extends V> m) {
  31. this.loadFactor = DEFAULT_LOAD_FACTOR;
  32. putMapEntries(m, false);
  33. }
  34. final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
  35. int s = m.size();
  36. if (s > 0) {
  37. if (table == null) { // pre-size
  38. float ft = ((float)s / loadFactor) + 1.0F;
  39. int t = ((ft < (float)MAXIMUM_CAPACITY) ?
  40. (int)ft : MAXIMUM_CAPACITY);
  41. if (t > threshold)
  42. threshold = tableSizeFor(t);
  43. }
  44. else if (s > threshold)
  45. resize();
  46. for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
  47. K key = e.getKey();
  48. V value = e.getValue();
  49. putVal(hash(key), key, value, false, evict);
  50. }
  51. }
  52. }

tableSizeFor(int cap)方法使用无符号右移和或运算,实现了将cap参数转为2n的整数。思路如下:以00001000(8)为例,从左往右找到第一个1,先>>>1得(00000100)然后或操作原数(00001000)得(00001100),将原数第一个1的下一位变成1。下一次>>>2或之后得15(00001111),也就是把最高位1开始之后的所有位都变成1,最后加1就变成了2n。因为int是32位得所以最多移16位即可。为什么要先加1,最后再减1?这样可以保证2n-1<cap≤2n,否则当cap=8时,返回得结果为16都是2的n次方,没有什么意义。

也可以通过1个map类型的参数构造HashMap,构造函数通过调用putMapEntries实现。putMapEntries先检查一下容量,然后通过putVal插入元素。关于putVal方法,在后面小节详细说明。

4查找

  1. public V get(Object key) {
  2. Node<K,V> e;
  3. return (e = getNode(hash(key), key)) == null ? null : e.value;
  4. }
  5.  
  6. final Node<K,V> getNode(int hash, Object key) {
  7. Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
  8. if ((tab = table) != null && (n = tab.length) > 0 &&
  9. (first = tab[(n - 1) & hash]) != null) {
  10. if (first.hash == hash && // always check first node
  11. ((k = first.key) == key || (key != null && key.equals(k))))
  12. return first;
  13. if ((e = first.next) != null) {
  14. if (first instanceof TreeNode)
  15. return ((TreeNode<K,V>)first).getTreeNode(hash, key);
  16. do {
  17. if (e.hash == hash &&
  18. ((k = e.key) == key || (key != null && key.equals(k))))
  19. return e;
  20. } while ((e = e.next) != null);
  21. }
  22. }
  23. return null;
  24. }
  25.  
  26. public boolean containsKey(Object key) {
  27. return getNode(hash(key), key) != null;
  28. }

get方法和containsKey都是通过getNode实现。

(1)如何通过key的hash值找到key在数组中的位置?

注意代码line9,first = tab[(n - 1) & hash],前面我们知道HashMap的容量是2的指数倍的,所以n-1可以保证低位全部都是1,例如n=16,n-1=1(00001111)。而(n - 1) & hash可以将hash值得高位置0,相当于hash%n,但计算速度比后者要快。

(2)找到该位置的对应节点

first表示该位置的第一个节点,当找到该位置时,总是要先检查一下first节点是不是查找的元素(line10-12)。若不是则往后遍历链表。(为什么要判断TreeNode?)

(3)hash(key)实现的原理

  1. static final int hash(Object key) {
  2. int h;
  3. return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  4. }

若非空,则高16位不变,低16位变成高16位和低16位的异或。为什么要这么做?前面我们知道定位是通过(length - 1) & hash,当length不够大时(也就是hashMap容量不够大),一直是hash值的低位起作用,这样容易造成碰撞(不同的hash值定位的结果相同),所以要提高高位的影响。然后就有了该操作。

注意key为null的情况,hashMap是允许key为null的,key为null的entry(键值对)存储在存储数组的0号位。

也可以查找value是否在集合中

  1. public boolean containsValue(Object value) {
  2. Node<K,V>[] tab; V v;
  3. if ((tab = table) != null && size > 0) {
  4. for (int i = 0; i < tab.length; ++i) {
  5. for (Node<K,V> e = tab[i]; e != null; e = e.next) {
  6. if ((v = e.value) == value ||
  7. (value != null && value.equals(v)))
  8. return true;
  9. }
  10. }
  11. }
  12. return false;
  13. }

这里总结两个源码实现的小技巧:

(1)在遍历数组前先检查数组是否为空。((tab = table) != null && size > 0)

(2)判断对象是否相等的方式。((v = e.value) == value || (value != null && value.equals(v)))

5扩容

resize方法

  1. final Node<K,V>[] resize() {
  2. Node<K,V>[] oldTab = table;
  3. int oldCap = (oldTab == null) ? 0 : oldTab.length;
  4. int oldThr = threshold;
  5. int newCap, newThr = 0;
  6. if (oldCap > 0) { // 容量不为空(已分配内存空间)
  7. if (oldCap >= MAXIMUM_CAPACITY) {
  8. threshold = Integer.MAX_VALUE;
  9. return oldTab; //已到最大值,没法继续扩容
  10. }
  11. else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
  12. oldCap >= DEFAULT_INITIAL_CAPACITY) //容量增为2倍,并检查
  13. newThr = oldThr << 1; // 将阈值也扩为2倍
  14. }
  15. else if (oldThr > 0) // 若容量为0,old阈值大于0。容量用阈值表示(见构造函数1)
  16. newCap = oldThr;
  17. else { // 若容量为0,old阈值也为0。使用默认值初始化(见构造函数3)
  18. newCap = DEFAULT_INITIAL_CAPACITY;
  19. newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
  20. }
  21. if (newThr == 0) { //计算阈值的合理值
  22. float ft = (float)newCap * loadFactor;
  23. newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
  24. (int)ft : Integer.MAX_VALUE);
  25. }
  26. threshold = newThr;
  27. @SuppressWarnings({"rawtypes","unchecked"})
  28. Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
  29. table = newTab;
  30. if (oldTab != null) { //将旧的值复制到新的存储桶里面
  31. for (int j = 0; j < oldCap; ++j) {
  32. Node<K,V> e;
  33. if ((e = oldTab[j]) != null) {
  34. oldTab[j] = null;
  35. if (e.next == null) //如果该位置只有1个元素,直接插如到新的位置(从新计算位置)
  36. newTab[e.hash & (newCap - 1)] = e;
  37. else if (e instanceof TreeNode)
  38. ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
  39. else { // preserve order
  40. Node<K,V> loHead = null, loTail = null;
  41. Node<K,V> hiHead = null, hiTail = null;
  42. Node<K,V> next;
  43. do {
  44. next = e.next;
  45. if ((e.hash & oldCap) == 0) {
  46. if (loTail == null)
  47. loHead = e; // 头节点指向第一个元素
  48. else
  49. loTail.next = e; // 当前个元素的next指向下一个元素
  50. loTail = e; //尾节点移向下一个个元素
  51. }
  52. else {
  53. if (hiTail == null)
  54. hiHead = e;
  55. else
  56. hiTail.next = e;
  57. hiTail = e;
  58. }
  59. } while ((e = next) != null);
  60. if (loTail != null) {
  61. loTail.next = null;
  62. newTab[j] = loHead;
  63. }
  64. if (hiTail != null) {
  65. hiTail.next = null;
  66. newTab[j + oldCap] = hiHead;
  67. }
  68. }
  69. }
  70. }
  71. }
  72. return newTab;
  73. }

下面重点分析line40-66,这里的任务是处理oldTab[j]位置的值不是单个元素,而是由多个元素组成链表的情况。

原理是通过头节点Head定位第一个元素,通过尾节点Tail的不断后移组装链表。但是为什么这里要使用两组头尾节点呢?(loHead+loTail、hiHead+hiTail)

这里是定位用的。举个例子原集合容量为16(0001 0000),(e.hash & oldCap) == 0表示hash值的第5位(从右往左)为0,这样扩容后定位为e.hash & (newCap-1)= e.hash &31(0001 1111),计算后第5位也为0,与旧集合的位置一样。所以line62直接将链表存在和同样的位置上。否则hash值的第5位为1,定位计算后第5位也为0,与原来相比大了2(5-1)=16,正好是大了旧集合的容量,所以line66定位用j+oldCap。可以把容量为32的新集合简单理解为高16位和低16位,结合取模计算就很好理解了。

line37-38在后面讨论红黑树的时候解释。

6添加元素

通过调用put方法向hashMap中添加1个元素

  1. public V put(K key, V value) {
  2. return putVal(hash(key), key, value, false, true);
  3. }
  4.  
  5. /**
  6. * @param hash hash for key
  7. * @param key the key
  8. * @param value the value to put
  9. * @param onlyIfAbsent if true, don't change existing value
  10. * @param evict if false, the table is in creation mode.
  11. * @return previous value, or null if none
  12. */
  13. final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
  14. boolean evict) {
  15. Node<K,V>[] tab; Node<K,V> p; int n, i;
  16. if ((tab = table) == null || (n = tab.length) == 0)
  17. n = (tab = resize()).length;
  18. if ((p = tab[i = (n - 1) & hash]) == null)
  19. tab[i] = newNode(hash, key, value, null);
  20. else {
  21. Node<K,V> e; K k;
  22. if (p.hash == hash &&
  23. ((k = p.key) == key || (key != null && key.equals(k))))
  24. e = p;
  25. else if (p instanceof TreeNode)
  26. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  27. else {
  28. for (int binCount = 0; ; ++binCount) {
  29. if ((e = p.next) == null) {
  30. p.next = newNode(hash, key, value, null);
  31. if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
  32. treeifyBin(tab, hash);
  33. break;
  34. }
  35. if (e.hash == hash &&
  36. ((k = e.key) == key || (key != null && key.equals(k))))
  37. break;
  38. p = e;
  39. }
  40. }
  41. if (e != null) { // existing mapping for key
  42. V oldValue = e.value;
  43. if (!onlyIfAbsent || oldValue == null)
  44. e.value = value;
  45. afterNodeAccess(e);
  46. return oldValue;
  47. }
  48. }
  49. ++modCount;
  50. if (++size > threshold)
  51. resize();
  52. afterNodeInsertion(evict);
  53. return null;
  54. }

line16-17检查集合是否为空,若是则分配初始容量。

line18-19根据hash值进行定位,检查存储桶在该位置i是否为空。若是则直接放入新的值。(newNode方法新建1个Node类型的对象)。

若存储桶在i位置有元素

(1)首先判断key值是否和链表的第一个元素相等(line22)

(2)若相等则如果参数onlyIfAbsent为false或者该位置的值为null,将value赋值为新值,并返回旧值(line41-47)。

(3)若1步不相等,则检查是否是TreeNode类型(红黑树实现)

(4)在链表的尾部插入新的元素(Node类型)

line25-26中的treeNode后面再研究。

最后判断当前容量是否超过阈值,若超过就要进行扩容。line50-51

afterNodeInsertion是一个post-actions,为了方便LinkedHashMap插入节点后的行为。但是在HashMap里面是该方法没有任何行为。

注意:line31检查桶中的节点数量(链表的长度),大于阀值则调用treeifyBin方法,treeifyBin中检查桶数量,大于64则需要将底层实现由链表改为红黑树,  如果桶数量不到64则重构一下链表 。 (jdk8中做的优化)

  1. //当桶的数量太多的时候,底层则改为红黑树实现
  2. final void treeifyBin(Node<K,V>[] tab, int hash) {
  3. int n, index; Node<K,V> e;
  4. //当桶的数量有 MIN_TREEIFY_CAPACITY (64)个时才将链表改为红黑树
  5. if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
  6. //节点太少则resize()
  7. resize();
  8. else if ((e = tab[index = (n - 1) & hash]) != null) {
  9. //将链接的结点转为二叉树结构
  10. TreeNode<K,V> hd = null, tl = null;
  11. do {
  12. TreeNode<K,V> p = replacementTreeNode(e, null);
  13. if (tl == null)
  14. hd = p;
  15. else {
  16. p.prev = tl;
  17. tl.next = p;
  18. }
  19. tl = p;
  20. } while ((e = e.next) != null);
  21. if ((tab[index] = hd) != null)
  22. hd.treeify(tab);
  23. }
  24. }

7删除元素

  1. public V remove(Object key) {
  2. Node<K,V> e;
  3. return (e = removeNode(hash(key), key, null, false, true)) == null ?
  4. null : e.value;
  5. }
  6.  
  7. /**
  8. * @param hash hash for key
  9. * @param key the key
  10. * @param value the value to match if matchValue, else ignored
  11. * @param matchValue if true only remove if value is equal
  12. * @param movable if false do not move other nodes while removing
  13. * @return the node, or null if none
  14. */
  15. final Node<K,V> removeNode(int hash, Object key, Object value,
  16. boolean matchValue, boolean movable) {
  17. Node<K,V>[] tab; Node<K,V> p; int n, index;
  18. if ((tab = table) != null && (n = tab.length) > 0 &&
  19. (p = tab[index = (n - 1) & hash]) != null) {
  20. Node<K,V> node = null, e; K k; V v;
  21. if (p.hash == hash &&
  22. ((k = p.key) == key || (key != null && key.equals(k))))
  23. node = p;
  24. else if ((e = p.next) != null) {
  25. if (p instanceof TreeNode)
  26. node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
  27. else {
  28. do {
  29. if (e.hash == hash &&
  30. ((k = e.key) == key ||
  31. (key != null && key.equals(k)))) {
  32. node = e;
  33. break;
  34. }
  35. p = e;
  36. } while ((e = e.next) != null);
  37. }
  38. }
  39. if (node != null && (!matchValue || (v = node.value) == value ||
  40. (value != null && value.equals(v)))) {
  41. if (node instanceof TreeNode)
  42. ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
  43. else if (node == p)
  44. tab[index] = node.next;
  45. else
  46. p.next = node.next;
  47. ++modCount;
  48. --size;
  49. afterNodeRemoval(node);
  50. return node;
  51. }
  52. }
  53. return null;
  54. }

删除所有元素

  1. public void clear() {
  2. Node<K,V>[] tab;
  3. modCount++;
  4. if ((tab = table) != null && size > 0) {
  5. size = 0;
  6. for (int i = 0; i < tab.length; ++i)
  7. tab[i] = null;
  8. }
  9. }

 8迭代器

hashMap内部实现了四个迭代器,分别是HashIterator,KeyIterator,ValueIterator,EntryIterator。其中HashIterator是其他迭代器的父类。

HashIterator实现如下:

  1. abstract class HashIterator {
  2. Node<K,V> next; // next entry to return
  3. Node<K,V> current; // current entry
  4. int expectedModCount; // for fast-fail
  5. int index; // current slot
  6.  
  7. HashIterator() {
  8. expectedModCount = modCount;
  9. Node<K,V>[] t = table;
  10. current = next = null;
  11. index = 0;
  12. if (t != null && size > 0) { // 找到数组第一个不为null的位置
  13. do {} while (index < t.length && (next = t[index++]) == null);
  14. }
  15. }
  16.  
  17. public final boolean hasNext() {
  18. return next != null;
  19. }
  20.  
  21. final Node<K,V> nextNode() {
  22. Node<K,V>[] t;
  23. Node<K,V> e = next;
  24. if (modCount != expectedModCount)
  25. throw new ConcurrentModificationException();
  26. if (e == null)
  27. throw new NoSuchElementException();
  28. if ((next = (current = e).next) == null && (t = table) != null) { // 这里不仅判断,而且将next节点后移。
  29. do {} while (index < t.length && (next = t[index++]) == null); // 找到数组下一个不为null的位置
  30. }
  31. return e;
  32. }
  33.  
  34. public final void remove() {
  35. Node<K,V> p = current;
  36. if (p == null)
  37. throw new IllegalStateException();
  38. if (modCount != expectedModCount)
  39. throw new ConcurrentModificationException();
  40. current = null; //要求remove之前的操作是nextNode
  41. K key = p.key;
  42. removeNode(hash(key), key, null, false, false);
  43. expectedModCount = modCount;
  44. }
  45. }

KeyIterator是keySet的迭代器实现

ValueIterator是valuse的迭代器实现

EntryIterator是Entry的迭代器实现

  1. final class KeyIterator extends HashIterator
  2. implements Iterator<K> {
  3. public final K next() { return nextNode().key; }
  4. }
  5.  
  6. final class ValueIterator extends HashIterator
  7. implements Iterator<V> {
  8. public final V next() { return nextNode().value; }
  9. }
  10.  
  11. final class EntryIterator extends HashIterator
  12. implements Iterator<Map.Entry<K,V>> {
  13. public final Map.Entry<K,V> next() { return nextNode(); }
  14. }

本文只分析了hashMap的一些基本操作的实现,其他的方法(包括函数编程内容,红黑树的实现等)后续会深入学习。

Java源码阅读HashMap的更多相关文章

  1. Java源码阅读的真实体会(一种学习思路)

    Java源码阅读的真实体会(一种学习思路) 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈 ...

  2. Java源码阅读的真实体会(一种学习思路)【转】

    Java源码阅读的真实体会(一种学习思路)   刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+ ...

  3. 如何阅读Java源码 阅读java的真实体会

    刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心.   说到技术基础,我打个比 ...

  4. [收藏] Java源码阅读的真实体会

    收藏自http://www.iteye.com/topic/1113732 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我 ...

  5. JDK 1.8源码阅读 HashMap

    一,前言 HashMap实现了Map的接口,而Map的类型是成对出现的.每个元素由键与值两部分组成,通过键可以找对所对应的值.Map中的集合不能包含重复的键,值可以重复:每个键只能对应一个值. 存储数 ...

  6. java源码阅读Hashtable

    1类签名与注释 public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, C ...

  7. 【JDK1.8】JDK1.8集合源码阅读——HashMap

    一.前言 笔者之前看过一篇关于jdk1.8的HashMap源码分析,作者对里面的解读很到位,将代码里关键的地方都说了一遍,值得推荐.笔者也会顺着他的顺序来阅读一遍,除了基础的方法外,添加了其他补充内容 ...

  8. Java源码阅读Stack

    Stack(栈)实现了一个后进先出(LIFO)的数据结构.该类继承了Vector类,是通过调用父类Vector的方法实现基本操作的. Stack共有以下五个操作: put:将元素压入栈顶. pop:弹 ...

  9. Java源码解析|HashMap的前世今生

    HashMap的前世今生 Java8在Java7的基础上,做了一些改进和优化. 底层数据结构和实现方法上,HashMap几乎重写了一套 所有的集合都新增了函数式的方法,比如说forEach,也新增了很 ...

随机推荐

  1. 股神小D [点分治 or LCT]

    题面 思路 点分治非常$naive$,不讲了,基本思路就是记录路径最小最大值.....然后没了 重点讲一下LCT的做法(好写不卡常)(点分一堆人被卡到飞起hhhh) 首先,这个路径限制由边限制决定,而 ...

  2. BZOJ1055[HAOI2008]玩具取名 【区间dp + 记忆化搜索】

    题目 某人有一套玩具,并想法给玩具命名.首先他选择WING四个字母中的任意一个字母作为玩具的基本名字.然后 他会根据自己的喜好,将名字中任意一个字母用“WING”中任意两个字母代替,使得自己的名字能够 ...

  3. 朗格拉日计数(counter)

    朗格拉日计数(counter) 题目描述 在平面上以圆周等分排列着n个带标号(标号为1-n)的点,你需要计算有多少个三元组(a,b,c),满足a<b<c而且标号为a,b,c的点在圆上分布的 ...

  4. H5单文件压缩插件

    单文件压缩上传 <input type="file" id="file"> 构造函数 function UpFileImg(options){ va ...

  5. bzoj4418 [Shoi2013]扇形面积并

    传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4418 [题解] 被题目名称吓死系列. 用一棵线段树维护当前有哪些半径. 那么将扇形差分,每段 ...

  6. bzoj3643 Phi的反函数

    传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3643 [题解] n = p1^a1*p2^a2*...*pm^am phi(n) = p1( ...

  7. VS MFC 添加菜单

    新建出来的基于对话框的MFC工程是没有菜单的,如何在对话框中添加菜单?又如何给菜单的菜单项添加事件应用响应?下面小编来具体描述一下,希望能帮助到一些人. 工具/原料   电脑一台 VS2010 方法/ ...

  8. 华为上机测试题(Excel表格纵列字母数字转换-java)

    PS:这是我刚做的一道题,题目不难,满分60,得分40,大家看看哪里有问题,欢迎提意见,感谢! /* * 题目:Excel表格纵列字母数字转换 * 描述: 在Excel中列的编号为A-Z,AA-AZ, ...

  9. 将log4j2的配置文件log4j2.xml拆分成多个xml文件

    在日常的项目开发中,我们可能会使用log4j2分离系统日志与业务日志 ,这样一来,log4j2.xml 这个配置文件可能就会变得非常臃肿.庞大,那么我们可以将这个文件拆分成多个配置文件吗? 答案是肯定 ...

  10. Python学习杂记_12_函数(三)

    内置函数 Python有很多内置函数,以下这些是常用且必须要掌握的: 强制类型转换: bool() # 把一个对象转换成布尔类型 int() # 整形 float() # 小数 str() # 字符 ...