源码分析HashMap、Hashtable、HashSet的区别

HashMap源码分析-基于JDK1.8

基本结构

1)、初始变量

public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable {
    private static final long serialVersionUID = 362498820763181265L;
    //默认初始容量
    static final int DEFAULT_INITIAL_CAPACITY = 16;
    //最大容量
    static final int MAXIMUM_CAPACITY = 1073741824;
    //默认扩展因子,达到容量的一定系数就开始扩容
    static final float DEFAULT_LOAD_FACTOR = 0.75F;
    //转为红黑树结构的 
    static final int TREEIFY_THRESHOLD = 8;
    //当桶(链表)节点数大于这个值时会转为红黑树
    static final int UNTREEIFY_THRESHOLD = 6;
    //桶结构转化为红黑树对应的table的最小大小
    static final int MIN_TREEIFY_CAPACITY = 64;
    //存储元素的数组
    transient HashMap.Node<K, V>[] table;
    //存放具体元素的值
    transient Set<Entry<K, V>> entrySet;
    //存放元素的个数
    transient int size;
    //每次扩容修改结构map结构的计数器
    transient int modCount;
    //临界值,当实际大小超过临界值会扩容
    int threshold;
    //加载因子
    final float loadFactor;
}

2)、链表结构

存放的是键值对。

static class Node<K, V> implements Entry<K, V> {
        final int hash; //hash值
        final K key; 
        V value;
        HashMap.Node<K, V> next;  //下一个节点
//.......省略......
	
        public final String toString() {
            return this.key + "=" + this.value;
        }
		//key的hash值与value的hash值的异或结果
        public final int hashCode() {
            return Objects.hashCode(this.key) ^ Objects.hashCode(this.value);
        }
		.......省略........
		//判断链表值是否相等,key相等且value相等
        public final boolean equals(Object var1) {
            if (var1 == this) {
                return true;
            } else {
                if (var1 instanceof Entry) {
                    Entry var2 = (Entry)var1;
                    if (Objects.equals(this.key, var2.getKey()) && Objects.equals(this.value, var2.getValue())) {
                        return true;
                    }
                }
                return false;
            }
        }
    }

3)、红黑树结构

继承于LinkedHashMap,提高查找效率。

    static final class TreeNode<K, V> extends java.util.LinkedHashMap.Entry<K, V> {
        HashMap.TreeNode<K, V> parent; //父节点
        HashMap.TreeNode<K, V> left; //左节点
        HashMap.TreeNode<K, V> right; 
        HashMap.TreeNode<K, V> prev;
        boolean red; //颜色标志

        TreeNode(int var1, K var2, V var3, HashMap.Node<K, V> var4) {
            super(var1, var2, var3, var4);
        }
		//返回根节点
        final HashMap.TreeNode<K, V> root() {
            HashMap.TreeNode var1 = this;
            while(true) {
                HashMap.TreeNode var2 = var1.parent;
                if (var1.parent == null) {
                    return var1;
                }
                var1 = var2;
            }
        }
        ......省略.......
 }
hash算法
  1. 计算key的hashcode —h
  2. 对h无符号向右位移 16位 i
  3. h和i做异或运算。使得高位也可以参与hash,更大程度上减少了碰撞率。
static final int hash(Object var0) {
        int var1;
        return var0 == null ? 0 : (var1 = var0.hashCode()) ^ var1 >>> 16;
    }
重要方法分析
1)、存放数据putVal方法

对外方法

 //计算key的hash值,调用putval方法
 public V put(K var1, V var2) {
        return this.putVal(hash(var1), var1, var2, false, true);
    }
final V putVal(int var1, K var2, V var3, boolean var4, boolean var5) {
        HashMap.Node[] var6 = this.table;
        int var8;
        //数组是否存在,或长度是否为0 
        if (this.table == null || (var8 = var6.length) == 0) {
        	//是->进行扩容
            var8 = (var6 = this.resize()).length;
        }
        Object var7;
        int var9;
        //计算当前索引,判断table[i]是否存在
        if ((var7 = var6[var9 = var8 - 1 & var1]) == null) {
        	//不存在--->新建节点
            var6[var9] = this.newNode(var1, var2, var3, (HashMap.Node)null);
        } else {
            Object var10;
            label79: {
                Object var11;
                //存在---判断key值是否在数组中存在
                if (((HashMap.Node)var7).hash == var1) {
                    var11 = ((HashMap.Node)var7).key;
                    //判断key值是否相等
                    if (((HashMap.Node)var7).key == var2 || var2 != null && var2.equals(var11)) { //是 --->替换旧值
                        var10 = var7;
                        break label79;
                    }
                }
				//table[i] 是否是红黑树结构
                if (var7 instanceof HashMap.TreeNode) {
                //是--加入红黑树结构
                    var10 = ((HashMap.TreeNode)var7).putTreeVal(this, var6, var1, var2, var3);
                } else {
                 //否--->遍历链表
                    int var12 = 0;

                    while(true) {
                    	//是否有下一个节点
                        var10 = ((HashMap.Node)var7).next;
                        if (((HashMap.Node)var7).next == null) {
                        	//无,创建节点
                            ((HashMap.Node)var7).next = this.newNode(var1, var2, var3, (HashMap.Node)null);
                            //节点数是否 >= 7
                            if (var12 >= 7) {
                            //节点大于8 ,转为红黑树结构
                                this.treeifyBin(var6, var1);
                            }
                            break;
                        }
						//hash判断key是否存在
                        if (((HashMap.Node)var10).hash == var1) {
                            var11 = ((HashMap.Node)var10).key;
                            if (((HashMap.Node)var10).key == var2 || var2 != null && var2.equals(var11)) {
                                break;
                            }
                        }
						//存在--->替换旧值
                        var7 = var10;
                        //节点数的计数器
                        ++var12;
                    }
                }
            }
            if (var10 != null) {
                Object var13 = ((HashMap.Node)var10).value;
                if (!var4 || var13 == null) {
                    ((HashMap.Node)var10).value = var3;
                }
                this.afterNodeAccess((HashMap.Node)var10);
                //返回旧值
                return var13;
            }
        }
        ++this.modCount;
        //数组长度是否大于临界值
        if (++this.size > this.threshold) {
        	//扩容
            this.resize();
        }
        this.afterNodeInsertion(var5);
        return null;
    }

putval调用流程图如下:
在这里插入图片描述

HashMap存储原理:
  1. 根据key计算key.hashcode = (h = hash(key) ^ h>>>16).
  2. 根据key.hash 计算得到桶的索引 i。
    1. 如果该索引位置无数据 table[i],则用该数据生成一个新的节点
    2. 如果该索引有数据且是一个红黑树,在红黑树中执行更新或新增操作
    3. 如果该索引有数据且是一个链表,遍历链表,在链表结尾创建节点,如果节点数超过7 ,转为红黑树,判断key的hash值是否相等,key及value相等跳出循环。
2)、get方法
public V get(Object var1) {
    HashMap.Node var2;
    return (var2 = this.getNode(hash(var1), var1)) == null ? null : var2.value;
}
	//计算key的hash值,根据key.hash取值
    final HashMap.Node<K, V> getNode(int var1, Object var2) {
        HashMap.Node[] var3 = this.table;
        HashMap.Node var4;
        int var6;
        //数组是否存在,长度 ,根据keyhash计算的索引对应的数据 table[i]
        if (this.table != null && (var6 = var3.length) > 0 && (var4 = var3[var6 - 1 & var1]) != null) {
            Object var7;
            //根据keyhash相同找位置  ----其实查找的是第一项数据
            if (var4.hash == var1) {
                var7 = var4.key;
                //equals方法判断key是否一致
                if (var4.key == var2 || var2 != null && var2.equals(var7)) {
                    return var4;
                }
            }
            HashMap.Node var5 = var4.next;
            //存在链表
            if (var4.next != null) {
            //链表是红黑树--从红黑树中取
                if (var4 instanceof HashMap.TreeNode) {
                    return ((HashMap.TreeNode)var4).getTreeNode(var1, var2);
                }
                //遍历链表--获取value
                do {
                    if (var5.hash == var1) {
                        var7 = var5.key;
                        if (var5.key == var2 || var2 != null && var2.equals(var7)) {
                            return var5;
                        }
                    }
                } while((var5 = var5.next) != null);
            }
        }
        return null;
    }

在这里插入图片描述

HashMap取数据原理
  1. 计算key的hash值
  2. 判断table及根据key.hash计算的索引的table[i]值是否!=null.
  3. 开始查找数据
    1. 查找第一个元素,是-则返回
    2. 是否是红黑树,是—则在红黑树中查找
    3. 遍历链表查找keyhash相等且使用equals方法相等的。
3)、resize()方法
  1. 当数组大小达到临界值或者在初始化的时候,则开始进行扩容
  2. 每次扩容都是原来容量的2倍
  3. 扩展后Node对象的位置要么不变,要么是原来位置的两倍。
  4. 扩容的原理:重新创建一个new HashMap.Node[var4],将老的数据复制到新的Node,并将老的置空。

区别

原理:

HashMap实现原理:JDK1.8以前是数组+链表结构,jdk1.8 改为数组+链表+红黑树结构。当链表的节点大于等于8的时候就会使用红黑树结构。

Hashtable实现原理:数组+链表结构。

**HashSet实现原理:**是简单类型的HashMap,值是固定的,只存储key值。

主干table是元素为链表的数组,链表是为了解决hash冲突的,当使用key值计算的hash地址一致时,在该下标下增加链表,存放及查找时需要遍历数组先查下标,如果该下标有链表还需要遍历链表,通过key值的equals方法判断取值或存值。

1、初始化
  • HashMap:初始化容量为16(必须是2的倍数)
  • Hashtable:初始化容量为11

两者的加载因子都是0.75.

2、线程是否安全
  • HashMap:线程不安全,如果是单线程操作,效率较高。如果要使HashMap线程安全,可以使用Collection.synchronizedMap(hashmap)进行同步。
  • Hashtable:线程安全,内部方法都使用了synchronized保证线程安全,因此单线程环境下他比HashMap慢。
3、空键值
  • HashMap:键值都可以为null ,key为null 时放在下标为1的位置。
  • Hashtable:键值不可以为null
4、扩容
  • HashMap:扩容大小是原大小的2倍
  • Hashtable:扩容大小是*2+1。

HashSet是存储了一个固定值的的HashMap,结构及初始化基本相同,具体实现上有区别,set实现的是Set接口,map实现的是map接口。两者hashcode的算法不一样,HashMap是使用key计算hash值,hashset使用成员对象计算hash值,并使用对象的equals方法判断对象的想等性。

参考文案链接

  1. https://www.cnblogs.com/xiaoxi/p/7233201.html
  2. https://mp.weixin.qq.com/s/WSHJW8nnNqC28IaZ3bKzbg

猜你喜欢

转载自blog.csdn.net/MarinaTsang/article/details/84032109