HASHMAP原理
Hashmap是数组和链表的集合体,即key是以数组来储存,value是以链表储存
默认的容量是16,加载因子是0.75,达到16*0.75 = 12会触发自动扩容
系统总是将新添加的 Entry对象放入 table数组的 bucketIndex索引处——如果 bucketIndex索引处已经有了一个 Entry对象,那新添加的 Entry对象指向原有的 Entry对象(产生一个 Entry链),如果 bucketIndex索引处没有 Entry对象,也就是上面程序代码的 e变量是 null,也就是新放入的 Entry对象指向 null,也就是没有产生 Entry链。
HashMap里面没有出现hash冲突时,没有形成单链表时,hashmap查找元素很快,get()方法能够直接定位到元素,但是出现单链表后,单个bucket里存储的不是一个 Entry,而是一个 Entry链,系统只能必须按顺序遍历每个 Entry,直到找到想搜索的 Entry为止——如果恰好要搜索的 Entry位于该 Entry链的最末端(该 Entry是最早放入该 bucket中),那系统必须循环到最后才能找到该元素。
当创建 HashMap时,有一个默认的负载因子(load factor),其默认值为 0.75,这是时间和空间成本上一种折衷:增大负载因子可以减少 Hash表(就是那个 Entry数组)所占用的内存空间,但会增加查询数据的时间开销,而查询是最频繁的的操作(HashMap的 get()与 put()方法都要用到查询);减小负载因子会提高数据查询的性能,但会增加 Hash表所占用的内存空间。
HashMap和Hashtable和LinkedHashMap的区别
HashMap和Hashtable是一组相似的键值对集合,它们的区别也是面试常被问的问题之一,我这里简单总结一下HashMap和Hashtable的区别:
1、Hashtable是线程安全的,Hashtable所有对外提供的方法都使用了synchronized,也就是同步,而HashMap则是线程非安全的
2、Hashtable不允许空的value,空的value将导致空指针异常,而HashMap则无所谓,没有这方面的限制
3、LinkedHashMap是有序的,继承自hashmap,多了一个after,before属性,用于记录插入的时候的前后的元素
Map的线程安全问题
1)hashtable,如上。对所有方法使用了synchronized关键字加锁。效率比较低。
2)Collections.synchronizedMap(map)
以上两者差别不大,基本相同。但是在多线程环境下有如下问题
1)一个在迭代的时候,别的线程就不能添加删除。
2)所有方法都进行了同步,但是这并不等于在使用过程中就不用再考虑线程安全问题。
如下面这段代码:
Java代码
1. // shm是SynchronizedMap的一个实例
2. if(shm.containsKey('key')){
3. shm.remove(key);
4. }
这段代码用于从map中删除一个元素之前判断是否存在这个元素。这里的containsKey和reomve方法都是同步的,但是整段代码却不是。考虑这么一个使用场景:线程A执行了containsKey方法返回true,准备执行remove操作;这时另一个线程B开始执行,同样执行了containsKey方法返回true,并接着执行了remove操作;然后线程A接着执行remove操作时发现此时已经没有这个元素了。要保证这段代码按我们的意愿工作,一个办法就是对这段代码进行同步控制,但是这么做付出的代价太大。
更好的选择:ConcurrentHashMap
而ConcurrentHashMap中则是一次锁住一个桶。ConcurrentHashMap默认将hash表分为16个桶,诸如get,put,remove等常用操作只锁当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。
ConcurrentHashMap 类中包含两个静态内部类 HashEntry 和 Segment。
HashEntry 用来封装映射表的键 / 值对;Segment 继承reentrantlock用来充当锁的角色,每个 Segment (桶)维护着一个HashEntry 数组,每个桶是由若干个 HashEntry 对象链接起来的链表。对这个桶内的数据的增删操作,都会调用这个segment对象的加锁方法。一个 ConcurrentHashMap 实例中包含由若干个 Segment 对象组成的数组。
结构示意图
如何定位?
ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部,
一、核心思想
1、锁分离技术:
ConcurrentHashMap首先将数据分成一段一段(segment)的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
2、 HashEntry 中的 key,hash,next 都声明为 final 型。这意味着,不能把节点添加到链接的中间和尾部,也不能在链接的中间和尾部删除节点。它是怎么删除的呢,是把前半部分copy一个副本进行修改,利用copyonwrite思想,从而读操作不需要加锁,但同时也引来另外的一个问题,即concurrenthashmap是弱一致性的(最终所有线程的视图是一致的,但某一瞬间可能不同,比如一个正在读,另外一个已经把即将要读的删除了),这是copyonwrite容器的固有特性,。
3、 Volatile 保证内存可见性:
由于内存可见性问题,未正确同步的情况下,写线程写入的值可能并不为后续的读线程可见,通过Volatile 变量可以保证其内存可见性问题。