我是靠谱客的博主 懦弱小馒头,最近开发中收集的这篇文章主要介绍JAVA中HashMap、HashTable和ConcurrentHashMapHashMapHashtableConcurrentHashMapHashMap和Hashtable的区别,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章目录

  • HashMap
    • HashMap使用红黑树而不使用AVL
    • 扩容机制
    • Entry类
    • 往HashMap中添加元素——put(key,value)函数
    • 从HashMap取元素——get(Object key)函数
    • keySet()和entrySet()
  • Hashtable
  • ConcurrentHashMap
    • 添加元素——put方法
    • 取出元素——get方法
    • JDK1.7和JDK1.8比较
  • HashMap和Hashtable的区别

HashMap

首先有一个table数组,数组的每个节点都是Entry类型。实际上就是数组+链表的形式。

  • HashMap初始化过程就是新建一个大小为capacity,类型为Entry的数组。默认生成一个大小为16,填装因子为0.75的HashMap。(填装因子越小,备用内存越多,冲突越小)
  • 如果链表长度大于8,而table长度小于MIN_TREEIFY_CAPACITY(默认值是64)时,还不会变成红黑树,而是调用resize()方法扩容。
  • 如果链表长度大于8,且table大于等于64了,就将链表转换为红黑树。

HashMap实现原理及扩容机制
深入解析hashmap

HashMap使用红黑树而不使用AVL

  • 红黑树适合修改操作,AVL更适合查找操作
  • AVL树的旋转比红黑树的旋转更加难以平衡和调试

hashmap使用红黑树而不使用AVL

扩容机制

关于为什么初始容量是2的n次幂扩容是2倍

JDK1.7:把原table的Node用头插法放到心得table中(新table和旧table中顺序是反的)
indexFor():计算在新table中的下标用。hash值和(length-1)按位与


/**
* 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);
}

JDK1.8:hash值与旧table的length相与,如果为0,就说明在新table中下标和旧table中相同,如果相与为1,就说明新table下标为原下标加原table长度。

在扩容完成之后,如果某个节点的是树,同时现在该节点的个数又小于等于6个了,则会将该树转为链表。

Entry类

Entry类是HashMap的内部类,包含(next——指向一个Entry的指针,key,value,hash)

往HashMap中添加元素——put(key,value)函数

  1. 如果key为null,先在table[0]链上查找是否有key为null的Entry对象,如果有则覆盖,没有则新生成一个key为null的Entry对象,然后放在table[0]的位置
  2. 如果key不为null,则通过hash(key)算出hash值以此确定放在table的哪条链。
  3. 如果该链上已经有一个key也相同(由2可知hash相同)的,则用新的value覆盖之前的值。
  4. 如果没有key相同的,则新生成一个Entry对象插入到链表中

重点:

  • HashMap中的key可以为null,此时hash=0
  • 用户插入(key,value)时不是直接放到HashMap中,而是新生成一个Entry对象后再插入到HashMap中(可能涉及扩充)。
  • 如果对应链上(hash相同)有key相同的Entry,则直接覆盖value,不生成Entry对象。

从HashMap取元素——get(Object key)函数

  1. 如果key为null,则在table[0]这条链上找,找到返回value值,否则返回null
  2. 通过getEntry(key)——先通过hash(key)找到hash值确定在table的位置,然后在该链查找key相同的,没有找到返回null,找到了返回该entry。
  3. 通过entry.getValue()返回2中entry的vaule值

重点

  • 先由hash值找到链,再用key在链上找
  • key为null则只在table[0]上找
  • getEntry()返回的是Entry对象,而get()需要的是Entry对象的value值
  • 不能由get方法判断HashMap中是否存在某个键,因为get方法返回null时,既可以表示HashMap中没有该键,也可以表示该键所对应的值为null。应该用containsKey()来判断。

keySet()和entrySet()

  • 都是用来遍历HashMap的
  • keySet的迭代器可以依次获取下一个key值,再根据key值取得value值
  • entrySet可以通过迭代器直接获得下一个entry值,所以entrySet比keySet快

Hashtable

  • HashTable是线程安全的
  • HashTable线程安全的策略实现代价非常大

Hashtable是线程安全的哈希表,通过synchronized来保证线程安全,get/put所有相关操作都是synchronized的。多线程通过同一个“对象的同步锁”来实现并发控制,相当于给整个哈希表加了一大把锁,当一个线程访问Hashtable的同步方法时,其他线程如果也访问Hashtable的同步方法可能会进入阻塞状态。

ConcurrentHashMap

线程安全的哈希表

  • JDK1.7中,通过“分段锁”来保证线程安全,容器中有多把锁,每一把锁锁一段数据。本质上也是一个“可重入的互斥锁”(ReentrantLock),多线程对同一个片段访问,互斥;对不同片段访问,可以同步进行。ReentrantLock+segment+HashEntry
  • JDK1.8中,使用CAS原子更新,volatile关键字,synchronized可重入锁实现。
    synchronized+CAS+HashEntry+红黑树

(JDK1.8中) 主要采用数组+链表+红黑树的形式实现。

Node:ConcurrentHashMap存储结构的基本单元,继承于HashMap的Entry,用于存储数据,对value和next设置了volatile同步锁
TreeNode:继承于Node,数据结构换成了二叉树结构,是红黑树的数据存储结构。通过TreeNode代替Node来转换成红黑树
TreeBin:存储树形结构的容器,是封装TreeNode的容器,提供转换为红黑树的一些条件和锁的控制

ConcurrentHashMap实现原理及源码分析

添加元素——put方法

  1. 数组是否初始化,没有则调用initTable()方法进行初始化过程(sizeCtl=0,其他线程正在初始化;sizeCtl=-1,正在初始化,防止其他线程进入;初始化完成,sizeCtl改为0.75*n)
  2. 通过计算hash值来判断放到数组哪个位置
  3. 没有hash冲突则直接CAS自旋锁插入
  4. 如果有hash冲突,则取出数组中的该节点。如果该hash值为MOVED(-1),表示正在对该数组进行扩容,那么协助扩容
  5. 判断取出的节点位置存放的是链表还是树
  6. 链表则遍历整个链表,若有key相等,说明是同一个key,则直接覆盖之前的value,否则添加到链表的末尾
  7. 如果是树,则调用putTreeVal把元素添加到树中
  8. 添加完成后调用addCount()方法统计size,判断目前该节点有多少个节点,如果达到8个以上,则用treeifyBin()方法将该处的链表转为树,或者扩容数组

扩容transfer

  • 构建一个nextTable,容量是原来的两倍,由单线程完成。
  • 将原来的元素复制到nextTable中,允许多线程操作
  • 扩容的时候,可以对数组进行读写操作

取出元素——get方法

计算hash值判断在数组哪个位置,然后遍历链表或树。

JDK1.7和JDK1.8比较

  • JDK1.8的数据结构更加坚定,去掉了Segment,使用synchronized进行同步锁粒度降低,不需要分段锁概念,实现复杂度增加
  • JDK1.8用红黑树优化链表
  • 用内置锁synchronized代替重入锁ReentrantLock

HashMap和Hashtable的区别

  • HashMap是Hashtable的轻量级实现,HashMap允许null键值,而Hashtable不允许
  • HashMap没有contains方法,而是containsKey和containsValue
  • Hashtable是线程安全的,HashMap不是线程安全的
  • HashMap使用Iterator,Hashtable使用Enumeration
  • Hashtable中,hash数组默认大小是11,增加的方式是old*2+1,HashMap中hash数组的默认大小是16,而且一定是2的倍数

最后

以上就是懦弱小馒头为你收集整理的JAVA中HashMap、HashTable和ConcurrentHashMapHashMapHashtableConcurrentHashMapHashMap和Hashtable的区别的全部内容,希望文章能够帮你解决JAVA中HashMap、HashTable和ConcurrentHashMapHashMapHashtableConcurrentHashMapHashMap和Hashtable的区别所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(38)

评论列表共有 0 条评论

立即
投稿
返回
顶部