JAVA--HashMap

个人理解
        HashMap是一个用于存储key-value键值对的集合,每一个<key,value>也被叫做Entry。这些键值对(Entry)分散存储在一直数组当中,这个数据就是HashMap的主体。
HashMap数组的元素在初始化为都为null;
如下图:
在这里插入图片描述
对于HashMap,经常用到就是get和put方法。
1.PUT方法
比如调用Put方法插入<demo,0>的值,即HashMap.put(“demo”,0)。
首先需要使用哈希函数(Hash)来计算确认Entry的位置(index):
index = Hash(“demo”);
假定计算出来的index是3,如下图
在这里插入图片描述
        但是呢,因为长度有限,当插入的元素无限多时,无可避免的就回出现index冲突,即Hash函数计算的值相同,也就是常说的哈希碰撞,哈希冲突。 如下图:
在这里插入图片描述
        这个时候需要怎么解决呢,这里介绍利用链表来解决
       HashMap数组中的每一个元素不止作为一个Entry对象,也可以是一个链表的头结点。每个Entry对象通过next指针(也可以叫做尾指针)指向下一个Entry对象,那么当新的Entry通过Hash运算映射到冲突的位置时,只需要插入到对应的链表中(注意这里使用的是“头插法”,至于原因后续会有介绍)。如下图
在这里插入图片描述
2.GET方法
       使用Get方法就是根据Key值来查找对应的Value,首先把输入的Key值做一次Hash映射,得到对应的index:int index = Hash(“Key”)。由于之前说过的Hash冲突,那么同一位置可能存在多个Entry对象,这时需要顺着对应链表从头结点开始,挨个进行查询比对。如下图
在这里插入图片描述
       步骤一:我们通过index,找到查看头结点Entry1,Entry1的key是张三,不是我们要的结果。
       步骤二:查询next结点Entry3,Entry3的key是李四,正是我们要的结果。
       之前的Put方法使用头插法,是因为HashMap的发明者认为,后插入的Entry被查询的可能性比较大。
3.源码简单分析(java8)
      3.1 参数(DEFAULT_INITIAL_CAPACITY)介绍

static final int DEFAULT_INITIAL_CAPACITY = 16;  //初始容量,且每次扩展或手动初始化时,长度必须为2的幂

      解释下为什么初始容量为16?
      初始容量为16是为了服务于从key值映射到index的Hash算法,上面描述过通过key值计算出index会用到一个Hash函数(index = Hash(“key”)),为了实现高效的Hash算法,HashMap的发明者采用了位运算的方法。
      位运算的公式:index = HashCode(Key)& (length - 1)注:其中的length为HashMap的长度。下来模拟下整个过程:假定key值为“demo”
      步骤一:计算“demo”的hashcode,假定结果十进制为888745,那么二进制位11011000111110101001。
      步骤二:假定HashMap的长度为16,计算length-1的结果十进制为15,二进制为1111。
      步骤三:把以上两个结果做与运算,11011000111110101001 & 1111=1001,十进制为9,所以index为9.
      从上面可以看到,Hash算法得到的index结果,完全取决于Key的HashCode值的最后几位。至于为什么不用其他的长度,假设HashMap的长度为10,重复上面运算:
在这里插入图片描述
单独这个结果,表面上没有问题。我们在试下新的Hashcode 11011000111110101011:
在这里插入图片描述
再试一个新的Hashcode 11011000111110101111:
在这里插入图片描述
      从上面可以看到Hashcode的值只是第二位第三位从0变成了1,但是运算结果都是1001,也就表明当HashMap的长度为10时,有些index出现的几率会更大,有些则不会出现(如:0111),很显然这不符合Hash算法均匀分布的原则。反之,当长度为16或2的幂,length-1的值所有二进制位都是1,这种情况下,index的结果等于Hashcode的后几位值。那么只要保证输入的Hashcode本身均匀分布的话,那么hash算法的结果也就均匀分布。
      3.2 构造函数
      A:HashMap() 无参构造函数

//构造一个初始容量为16,装载因子为0.75的空的HashMap
  public HashMap()
  {
    loadFactor = 0.75F;
  }

      B: HashMap(int paramInt)

//构造一个初始容量为paramInt,装载因子为0.75的空的HashMap
  public HashMap(int paramInt)
  {
    this(paramInt, 0.75F);
  }

注意:HashMap开辟的最大容量为1GB,所以paramInt的最大值为1073741824,如果paramInt的值大于1073741824,会自动默认为1073741824。如下:
      C:public HashMap(int paramInt, float paramFloat)

public HashMap(int paramInt, float paramFloat)
  {
    if (paramInt < 0) {
      throw new IllegalArgumentException("Illegal initial capacity: " + paramInt);
    }
    if (paramInt > 1073741824) {//当长度大于1073741824,自动默认为1073741824
      paramInt = 1073741824;
    }
    if ((paramFloat <= 0.0F) || (Float.isNaN(paramFloat))) {//负载因子小于0,或者不是数字时,抛出异常
      throw new IllegalArgumentException("Illegal load factor: " + paramFloat);
    }
    loadFactor = paramFloat;
    threshold = tableSizeFor(paramInt);
  }

设定threshold,当HashMap的size到了 threshold时,就要进行resize,也就是扩容操作。
tableSizeFor() 方法的主要功能是返回一个大于给定整数且最接近2的幂次的整数,如给定整数位10,则返回2的4次方16。如下图:(移位运算就不介绍了)

static final int tableSizeFor(int paramInt)
  {
    int i = paramInt - 1; //这里减1,是为了方式paramInt 已经是2的幂,执行完位运算返回的值是paramInt 的2倍,因为paramInt 已经是2的幂次,已经满足条件了
    i |= i >>> 1;
    i |= i >>> 2;
    i |= i >>> 4;
    i |= i >>> 8;
    i |= i >>> 16;
    return i >= 1073741824 ? 1073741824 : i < 0 ? 1 : i + 1;
  }

      D:public HashMap(Map<? extends K, ? extends V> paramMap)

 //构造一个和指定Map有相同mappings的HashMap,初始容量能充足的容下指定的Map,负载因子为0.75
public HashMap(Map<? extends K, ? extends V> paramMap)
  {
    loadFactor = 0.75F;
    putMapEntries(paramMap, false);
  }
  
 final void putMapEntries(Map<? extends K, ? extends V> paramMap, boolean paramBoolean)
 {
 	//得到paramMap中元素的个数
   int i = paramMap.size();
   //当paramMap中有元素时,将paramMap中的元素放入本HashMap中
   if (i > 0)
   {
   	//判断table有没初始化,没有的话先进行初始化,初始化是在put方法中进行
     if (table == null)
     {
       float f = i / loadFactor + 1.0F;
       int j = f < 1.07374182E9F ? (int)f : 1073741824;
       if (j > threshold) {
         threshold = tableSizeFor(j);
       }
     }
     else if (i > threshold)
     {
     //扩容
       resize();
     }
     Iterator localIterator = paramMap.entrySet().iterator();
    //循环吧paramMap中的元素放入本HashMap中
     while (localIterator.hasNext())
     {
       Map.Entry localEntry = (Map.Entry)localIterator.next();
       Object localObject1 = localEntry.getKey();
       Object localObject2 = localEntry.getValue();
       putVal(hash(localObject1), localObject1, localObject2, false, paramBoolean);
     }
   }
 }

      本人目前只整理到这里,后续会继续添加或修改

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值