1:HashMap和ConcurrentHashMap之间的区别:
- JDK 1.7的HashMap和ConcurrentHashMap之间的区别见讲义
- JDK1.8HashMap和ConcurrentHashMap之间的区别
HashMap:
- Java8 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组+链表+红黑树 组成。
- Java7HashMap查找的时候,根据hash值能够快速定位到数组的具体下标,但是随后要顺序遍历链表,复杂度为O(n),Java8中当链表中元素超过8个以后,将;链表转换为红黑树,时间复杂度为O(logn)
- Java 7中是先扩容再添加新值,而Java 8是先增加新值再扩容
- Java7 中使用 Entry 来代表每个 HashMap 中的数据节点,Java8 中使用 Node,基本没有区别,都是 key,value,hash 和 next 这四个属性,不过,Node 只能用于链表的情况,红黑树的情况需要使用 TreeNode。我们根据存放在该位置的第一个节点的数据类型是Node还是TreeNode来判断是链表还是红黑树
get过程
- 计算 key 的 hash 值,根据 hash 值找到对应数组下标: hash & (length-1)
- 判断数组该位置处的元素是否刚好就是我们要找的,如果不是,走第三步
- 判断该元素类型是否是 TreeNode,如果是,用红黑树的方法取数据,如果不是,走第四步
遍历链表,直到找到相等(==或equals)的 key
ConcurrentHashMap
- Java 8中同样引入了红黑树
初始化过程
- 这个初始化方法有点意思,通过提供初始容量,计算了 sizeCtl,sizeCtl = 【 (1.5 * initialCapacity + 1),然后向上取最近的 2 的 n 次方】。如 initialCapacity 为 10,那么得到 sizeCtl 为 16,如果 initialCapacity 为 11,得到 sizeCtl 为 32。
- 初始化数组,也就是确定初始的容量,然后根据初始的容量来确定sizeCtl,并发问题通过对sizeCtl进行CAS操作
HashMap扩容为什么是原来的2倍
- 在hashmap的源码中。put方法会调用indexFor(int h, int length)方法,这个方法主要是根据key的hash值找到这个entry在Hash表数组中的位置,源码如下:
- 上述代码也相当于对length求模。 注意最后return的是h&(length-1)。如果length不为2的幂,比如15。那么length-1的2进制就会变成1110。在h为随机数的情况下,和1110做&操作。尾数永远为0。那么0001、1001、1101等尾数为1的位置就永远不可能被entry占用。这样会造成浪费,不随机等问题。 length-1 二进制中为1的位数越多,那么分布就平均。
- 扩容后,原来table[i]中的链表中所有节点分拆到新数组new Table[i]和new Table[i+oldLength]位置上
2:用自定义类型作为HashMap的key
- 如果HashMap的key是自定义的类型,则需要重写equals()方法,因为对于创建的两个值一样的自定义类型的对象,如果不重写Object类的equals()方法,则由于这两个对象的地址不同,所以equals方法返回false,所以在hashmap中会认为这两个是不一样的两个键
- 因此需要重写equals()方法,一旦重写了equals方法,则一定也要重写hashcode方法
- 如果两个对象的equals()方法相等,则这两个对象一定有相等的hashcode,反之,两个对象有相同的hashcode不一定能说明这两个对象的equals()方法返回true
- 当自定义类的多项式作为hashmap的key时,最好把这个类设计为不可变类,因为如果是可变对象的话,对象中的属性改变,则对象的hashcode也会相应的改变,导致下次无法查找到已存在Map中的数据,如果可变对象在HashMap中被用作键,那就要小心在改变对象状态的时候,不要改变它的hash值。