Java HashMap

HashMap
https://blog.csdn.net/fujiakai/article/details/51585767
摘要

HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长;
HashMap 实现了Serializable接口,因此它支持序列化,实现了Cloneable接口,能被克隆;
多线程环境下可以采用concurrent并发包下的concurrentHashMap;
默认加载因子为0.75;
构造方法中提到了两个很重要的参数:初始容量和加载因子;
扩容是是新建了一个HashMap的底层数组 ,而后调用transfer方法, 将旧HashMap的全部元素添加到新的HashMap中 (要重新计算元素在新的数组中的索引位置)。 很明显,扩容是一个 相当耗时 的操作,因为它需要重新计算这些元素在新的数组中的位置并进行复制处理。因此,我们在用HashMap的时,最好能 提前预估下HashMap中元素的个数,这样有助于提高HashMap的性能

1、默认16个元素大小;
2、底层主要数据结构是数组,为了解决哈希冲突,所以会有下一层的链表结构; 数组+链表
3、哈希冲突并不是说key的hashCode重复,而是在那块连续的内存单元中,不同的key经过hash算法散列之后指向了同一个地址,
    如果出现哈希冲突,则 将最新出现的置于链表头部,即数组哈希地址位置  
4、负载因子越大,数组剩余空间越少,哈希碰撞会多,链表会长,链表中的元素查询效率则慢,hashMap消耗空间(数组结构的空间)则小;
      负载因子越小,数字剩余空间则多,哈希碰撞则小,链表短,寻址快,空间消耗则大;
5、为hashMap分配一块连续的内存用于存储元素,存储位置(下标)根据hash算法尽可能均匀散列,不过还是会出现哈希碰撞,从而产生链表结构;
HashMap底层结构,代码演示
定义一个类型作为key使用,重写hashCode方法
package collectionandmap;
public class KeyBean {
    
     public KeyBean() {
    }
     public KeyBean(Integer key , String name ){
        this . key = key ;
        this . name = name ;
    }
     private Integer key ;
     private String name ;
    
     public Integer getKey() {
        return key ;
    }
     public void setKey(Integer key ) {
        this . key = key ;
    }
     public String getName() {
        return name ;
    }
     public void setName(String name ) {
        this . name = name ;
    }
    @Override
     public int hashCode() {//重写hashCode方法
        return this . key .hashCode();
    }
    @Override
     public boolean equals(Object obj ) {
        if (!( obj instanceof KeyBean)){
            return false ;
       }
        return ((KeyBean) obj ).getKey().equals( this . key );
    }
    
}
测试如下
  public static void main(String[] args ) {
       Map map = new HashMap<>();
       KeyBean k1 = new KeyBean(1, "foo" );
       String s1 = "foo" ;
        map .put( k1 , s1 );
       
       KeyBean k2 = new KeyBean(1, "Boo" );
       String s2 = "Boo" ;
        map .put( k2 , s2 );
       
       System.out.println(k1.hashCode());//1
       System.out.println(k2.hashCode());//1
       
       KeyBean k3 = new KeyBean(3, "Zoo" );
       String s3 = "Zoo" ;
        map .put( k3 , s3 );
       System.out.println(k3.hashCode());//3
       System.out.println(map.size());//2
       System.out.println(map.get(k1));//Boo
       System.out.println(map.get(k2));//Boo
       System.out.println(map.get(k3));//Zoo
   }
详细介绍见:
https://www.cnblogs.com/chengxiao/p/6059914.html

通过以上代码能够得知,当发生哈希冲突并且size大于阈值的时候,需要进行数组扩容,扩容时,需要新建一个长度为之前数组2倍的新的数组,然后将当前的Entry数组中的元素全部传输过去,扩容后的新数组长度为之前的2倍,所以扩容相对来说是个耗资源的操作。
当负载因子较大时,去给table数组扩容的可能性( 哈希地址未被占用 )就会少,所以相对占用内存较少(空间上较少),但是每条entry链上的元素会相对较多,查询的时间也会增长(时间上较多)。反之就是,负载因子较少的时候,给table数组扩容的可能性就高,那么内存空间占用就多,但是entry链上的元素就会相对较少,查出的时间也会减少。所以才有了负载因子是时间和空间上的一种折中的说法。所以设置负载因子的时候要考虑自己追求的是时间还是空间上的少。
所以负载因子越大则散列表的装填程度越高,也就是能容纳更多的元素,元素多了,链表大了,所以此时索引效率就会降低。
反之,负载因子越小则链表中的数据量就越稀疏,此时会对空间造成烂费,但是此时索引效率高

对于哈希冲突
事实上,这个优化在JDK 1.8中已经去掉了,因为JDK 1.8中,映射到同一个哈希桶(数组位置)的Entry对象,使用了红黑树来存储,从而大大加速了其查找效率;
HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结构, 但是在 jdk1.8里 加入了红黑树的实现,当链表的长度大于8时,转换为红黑树的结构;
1.8在hash算法上的位操作(与运算)运算次数上比之前少两次,由原来的3次变为1次;



你可能感兴趣的:(java,HashMap,java基础)