1. hashMap数据结构
  2. 类注释
  3. HashMap的几个重要的字段
  4. hash和tableSizeFor方法

HashMap源码分析-基于JDK1.8

由上图可知,HashMap的基本数据结构是数组和单向链表或红黑树。

以下内容翻译于HashMap类的注释

HashMap是map接口的基础实现类。这个实现提供了所有可选的Map接口操作。并且允许null键和null值。HashMap类和Hashtable类差不多,只是HashMap不是线程完全的,并且HashMap允许null值和null键。这个类不保证map元素的顺序,也不保证顺序会随着时间保持不变。

假如hash函数能够使元素在桶中(buckets)均匀地分散,对于基本的get,put操作HashMap的性能还是比较稳定的。集合视图的遍历(EntrySet之类的操作,也许这些方法返回的结果都是集合类型,所以叫做集合视图)需要的时间和HashMap的"capacity"(buckets的数据)乘以数量(bucket中的健值对的数量)成正比。因此如果遍历性能非常重要,那么就不要把初始的CAPACITY设置的太大(或者LOAD_FACTOR太小)。

HashMap实例有有两个属性影响它的性能:CAPACITYLOAD_FACTORCAPACITYhash表里桶的数量,并且初始的CAPACITY仅仅是hash表创建时的容量。LOAD_FACTORhash表在自动地增加它的CAPACITY前,允许CAPACITY有多满的测量方式。当hash表里的条目的数量超过当前CAPACITY乘以LOAD_FACTOR的数量时,hash表被重新计算hash。(也就是说内部的数据结构被重建)。以便hash表具有大概两倍于原来桶数量。

一般来说,默认的loadfactory(0.75)在时间和空间消耗上提供了一个好的折中。更高的值减小了空间压力,但是增加了查询消耗(反映在HashMap中的大部分操作,包括getput)。为了减小rehash的操作次数,当设置它的初始capacity时应该考虑将来的map中的条目数量和它的loadfactory。如果初始capacity大于条目最大数量除以loadfactory,就不会有rehash操作发生。

如果很多映射(键值对)将被存储在HashMap中。与在需要的时候自动地执行rehash操作来扩大hash表大小相比,创建一个足够大capacityhashMap来存储映射将是更高效的。注意,很多key具有相同的hashCode()值是降低任何hash表性能的方式。

注意这个实现不是synchronized(线程安全)的。如果多个线程同时访问hashMap,并且只要有一个线程修改map结构,它就必须在外面被加上synchronized。(结构的修改是指任何增加或删除一个或多个的映射,仅仅修改一个健的值不是结构的修改)。这通常通过在天然地包裹map的对象上同步来实现。如果没有这样的对象存在。map应该用Collections.synchronizedMap方法包装一下。为了防止对map意外的不同步的访问,最好在创建的时候完成这样的操作。例如

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

 

被这个类的”集合视图方法”返回的所有遍历器都是快速失败的:在这个遍历器创建之后,用任何方法除了iterator自身的remove方法修改map的结构将会抛出ConcurrentModificationException。因此面对同时的修改,遍历器快速而干净利落地失败。而不是在不确定的未来冒着不确定的危险。

 

注意,遍历器快速失败的行为不能被用来保证它看起来的样子。换句话说,在不同步的同时修改前面不能做任何强的担保。快速失败的遍历器尽量地抛出ConcurrentModificationException。写的程序依赖这个异常来保证正确性将是错误的。iterators的快速失败行为应该只被用于检测错误。

 

HashMap的几个重要的字段

/**
* 默认的CAPACITY值,也就是16,这个值必须是2的幂
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
 * capacity最大值, 当在构造器中传入一个比这大的参数的时候使用。
 * 也就是说,当传入的值大于这个值,就使用这个值
 * 必须是2的幂
 */
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
 * 在构造器中没有指定的时候被使用的默认的加载因子.
 */
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
  * 使用树而不是链表的bin(就是之前常说的桶(bucket))中的数量阀值,当在bin中增加数据的时候,大于这个值
 * 就会把bin中的数据从链表转换成红黑树结构来表示。这个值必须大于2并且应该小
 * 小于8。 
*/
static final int TREEIFY_THRESHOLD = 8;
/**
 * 在resize操作中把bin中数据变为列表结构的数量阀值,如果小于这个值,就会
 * 从树结构变为列表结构。这个值应该小于TREEIFY_THRESHOLD并且最大为6。
 * most 6 to mesh with shrinkage detection under removal.
 */
static final int UNTREEIFY_THRESHOLD = 6;
/**
 * 当bin中的结构转换为树的时候,CAPACITY的最小值.
 * 否则就会resize当bin中数据太多的时候。应该至少4 * TREEIFY_THRESHOLD
 * 来避免resizing和树转换阀值之间的冲突。
 * between resizing and treeification thresholds.
 */
static final int MIN_TREEIFY_CAPACITY = 64;

节点实现类

  1. Node

 1 /**
 2      * 基本的bin节点, 被用于表示大部分的数据条目。
 3      * 
 4      */
 5     static class Node<K,V> implements Map.Entry<K,V> {
 6         final int hash;         // 这个记录的是K的hash值
 7         final K key;           // map的键
 8         V value;              // map的值
 9         Node<K,V> next;          // 指向下一个节点
10 
11         Node(int hash, K key, V value, Node<K,V> next) {
12             this.hash = hash;
13             this.key = key;
14             this.value = value;
15             this.next = next;
16         }
17 
18         public final K getKey()        { return key; }
19         public final V getValue()      { return value; }
20         public final String toString() { return key + "=" + value; }
21      // 节点的hashCode,key的hashCode和value的hashCode的异或 
22         public final int hashCode() {
23             return Objects.hashCode(key) ^ Objects.hashCode(value);
24         }
25 
26         public final V setValue(V newValue) {
27             V oldValue = value;
28             value = newValue;
29             return oldValue;
30         }
31      // 重写的equals,如果节点的key和value都相等,两个节点才相等。
32         public final boolean equals(Object o) {
33             if (o == this)
34                 return true;
35             if (o instanceof Map.Entry) {
36                 Map.Entry<?,?> e = (Map.Entry<?,?>)o;
37                 if (Objects.equals(key, e.getKey()) &&
38                     Objects.equals(value, e.getValue()))
39                     return true;
40             }
41             return false;
42         }
43     }

静态的工具方法

/**
 * 计算key的hashCode值并且把hashCode的高16位和低16位异或。
 * 这是一个折中的做法。因为现在大部分情况下,hash的分布已经
 * 比较分散了,而且如果冲突比较多的时候,我们会把bin中的数据转
 * 换为树结构,来提高搜索速度。
 */
  static final int hash(Object key) {
      int h;
      return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  }

疑问?

  为什么采用这样的算法。或者说为什么要把hashCode的高16位和低16位异或。我一开始也想不明白,看其它的文章也很难找到把这一点解决明白的。

  于是我就动手做实验,来验证,如果不采用异或会怎么样。采用异或之后有什么效果。

  当然,不能忘记一点,计算hash值是为了找这个key对应的table数组之中的下标的。计算下标的算法是tab[i = (n - 1) & hash]。这里的n是table数组的数量。hash就是hash()方法返回的    值。他们两个求'与'。在源码的类说明里,说了一种情况,就是几个连续的Float类型的值在一个小的table中会冲突。我就以几个连续的Float值为样例测试。代码如下

   

  

 1 /**
 2  * 描述:
 3  * 日期:2017年11月13
 4  * @author dupang
 5  */
 6 public class DupangTest {
 7     public static void main(String[] args) {
 8         Float f1 = 1f;
 9         Float f2 = 2f;
10         Float f3 = 3f;
11         Float f4 = 4f;
12 
13         String f1_hashCode = Integer.toBinaryString(f1.hashCode());
14         String f2_hashCode = Integer.toBinaryString(f2.hashCode());
15         String f3_hashCode = Integer.toBinaryString(f3.hashCode());
16         String f4_hashCode = Integer.toBinaryString(f4.hashCode());
17 
18         System.out.println(f1_hashCode);
19         System.out.println(f2_hashCode);
20         System.out.println(f3_hashCode);
21         System.out.println(f4_hashCode);
22 
23         int size = 198;
24         int f1_index = f1.hashCode()&(size-1);
25         int f2_index = f2.hashCode()&(size-1);
26         int f3_index = f3.hashCode()&(size-1);
27         int f4_index = f4.hashCode()&(size-1);
28 
29         int f1_index_1 = hash(f1)&(size-1);
30         int f2_index_2 = hash(f2)&(size-1);
31         int f3_index_3 = hash(f3)&(size-1);
32         int f4_index_4 = hash(f4)&(size-1);
33 
34         System.out.println(f1_index);
35         System.out.println(f2_index);
36         System.out.println(f3_index);
37         System.out.println(f4_index);
38         System.out.println("=========华丽的分割线===========");
39         System.out.println(f1_index_1);
40         System.out.println(f2_index_2);
41         System.out.println(f3_index_3);
42         System.out.println(f4_index_4);
43     }
44 
45     static final int hash(Object key) {
46         int h;
47         return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
48     }
49 }
View Code

相关文章:

  • 2022-01-08
  • 2021-10-14
  • 2021-12-06
  • 2021-09-04
  • 2021-10-21
  • 2021-05-17
猜你喜欢
  • 2021-06-11
  • 2023-02-07
  • 2021-10-30
  • 2022-12-23
  • 2021-12-20
  • 2021-09-16
  • 2021-06-17
相关资源
相似解决方案