【Java基础】Hashmap

简介: 【Java基础】Hashmap

  hashMap实现Map接口,基于hashing原理,以键值对形式存储,允许null键/值,非同步的集合类型;

    hashmap的底层存储结构是基于数组和链表的


2018093020234487.png


一、put方法



public Object put(Object key,Object value);


1、因为hashmap存储的底层结构是数组和链表,所以,当我们put时,需要计算出数组的下标:


1)执行key.hashCode()方法,得到哈希码:hashCode=key.hashCode();


2)用哈希码做hash运算,得到一个散列值:int hash = hash(hashCode);


3)散列值与数组的长度做indexFor运算,得到数组下标。


2、了解了这个过程,我们很容易能发现,即使key不同,经过前两步运算,也可能得到相同散列值。所以我们put时,新的entry和旧的entry可能发生冲突,解决hash冲突的方法有很多,在hashmap中采用的是链地址法。即:


对新entry和旧entry进行判断:


1)散列值相同,key相同;


此时说明是相同的entry,用新的entry覆盖旧的entry。


2)散列值相同,key不同;


执行key.equals()方法;


如果这两个对象通过hashmap重写的equals()方法判断是一样的,说明是同一个entry对象,用新的entry覆盖旧的entry;


如果不同,就把新加入的entry对象放在对应数组下标位置的表头,早进来的entry向后移动一个位置,形成链表;


特别注意:我们存放在数组中的并不只有value,是一个entry(hash,key,value,bucketIndex)对象。


3、链表长度太长,影响查询效率怎么办?


JDK1.8多了一个新特性,当链表的长度>=8时,链表转换为红黑树,提高查询效率:


 1.8 链表转红黑树的源码
static final int TREEIFY_THRESHOLD = 8;
 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
          treeifyBin(tab, hash);
             break;



4、不断增加对象,是否需要扩容resize?


了解一下API里hashmap的构造方法,里面有initialCapacity和loadFactor两个名词;


initialCapacity是哈希表的数组的初识大小,始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍;loadFactor是加载因子;HashMap的默认大小是16,那么他的数组长度是0-15;默认加载因子是0.75;threshold:扩容的阈值,等于 capacity * loadFactor


所以,当容量达到threshold时,数组会自动扩容到原来大小的两倍;比如,当前我们的数组的大小是16,当存储到12个元素时,数组会自动扩容到32.

void addEntry(int hash, K key, V value, int bucketIndex) {
       if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
           hash = (null != key) ? hash(key) : 0;
             bucketIndex = indexFor(hash, table.length);
         }
        createEntry(hash, key, value, bucketIndex);
    }


 void resize(int newCapacity) {
        Entry[] oldTable = table;
         int oldCapacity = oldTable.length;
         if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
             return;
         }
         Entry[] newTable = new Entry[newCapacity];
        boolean oldAltHashing = useAltHashing;
         useAltHashing |= sun.misc.VM.isBooted() &&
                (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
         boolean rehash = oldAltHashing ^ useAltHashing;
         transfer(newTable, rehash);
         table = newTable;
         threshold = (int)(newCapacity * loadFactor);
     }


5、key为null时

 public V put(K key, V value) {
         if (key == null)
             return putForNullKey(value);
         int hash = hash(key);
         int i = indexFor(hash, table.length);
         for (Entry<K,V> e = table[i]; e != null; e = e.next) {
             Object k;
             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                 V oldValue = e.value;
                e.value = value;
                 e.recordAccess(this);
                return oldValue;
            }
       }
        modCount++;
        addEntry(hash, key, value, i);
        return null;
   }
 private V putForNullKey(V value) {  
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {  
        if (e.key == null) {  
             V oldValue = e.value;  
             e.value = value;  
             e.recordAccess(this);  
             return oldValue;  
         }  
     }  
     modCount++;  
     addEntry(0, null, value, 0);  
    return null;  
 }  



当key为null时,如果数组中有key为null的,遍历table[0]链表(key为null的元素放在了数组的第0位),如果找到,将传入的value重新赋值给这个元素的value,并返回原来的value。如果没有找到,就将元素添加到table[0]链表的表头。所以,key为null时,在hashmap的链表中只有一个值。



二、get

1、与put相同,先根据key,算出hash,算出数组下标,定位到数组元素。


2、遍历该元素处的链表

取出传进来的key与链表中entry对象的key值相等的value或传进来的key和entry对象的key连重写后的equals后都一样的value;

 public V get(Object key) {
         if (key == null)
             return getForNullKey();
         int hash = hash(key.hashCode());
         //先定位到数组元素,再遍历该元素处的链表
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
              e = e.next) {
          Object k;
         if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
              return e.value;
   }
       return null;
 }
  private V getForNullKey() {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }

3、当key为null时?

由put的过程可知,hashmap将key为null的元素放到了数组第0个元素的链表中。根据getForNullKey()的实现代码可知。

 


相关文章
|
2天前
|
存储 Java 索引
JAVA零基础小白学习免费教程day14-Set&HashMap(一)
JAVA零基础小白学习免费教程day14-Set&HashMap
99 0
|
2天前
|
存储 算法 Java
【深入挖掘Java技术】「源码原理体系」盲点问题解析之HashMap工作原理全揭秘(下)
在阅读了上篇文章《【深入挖掘Java技术】「源码原理体系」盲点问题解析之HashMap工作原理全揭秘(上)》之后,相信您对HashMap的基本原理和基础结构已经有了初步的认识。接下来,我们将进一步深入探索HashMap的源码,揭示其深层次的技术细节。通过这次解析,您将更深入地理解HashMap的工作原理,掌握其核心实现。
32 0
【深入挖掘Java技术】「源码原理体系」盲点问题解析之HashMap工作原理全揭秘(下)
|
2天前
|
存储 安全 算法
详解Java中HashMap、HashTable、ConcurrentHashMap常见问题
详解Java中HashMap、HashTable、ConcurrentHashMap常见问题
45 0
|
2天前
|
存储 安全 算法
【JAVA】HashMap扩容性能影响及优化策略
【JAVA】HashMap扩容性能影响及优化策略
|
2天前
|
存储 Java 索引
Java HashMap:设计思想与实现原理详解
Java HashMap:设计思想与实现原理详解
145 0
|
2天前
|
存储 安全 Java
Java一分钟之-Map接口与HashMap详解
【5月更文挑战第10天】Java集合框架中的`Map`接口用于存储唯一键值对,而`HashMap`是其快速实现,基于哈希表支持高效查找、添加和删除。本文介绍了`Map`的核心方法,如`put`、`get`和`remove`,以及`HashMap`的特性:快速访问、无序和非线程安全。讨论了键的唯一性、`equals()`和`hashCode()`的正确实现以及线程安全问题。通过示例展示了基本操作和自定义键的使用,强调理解这些概念对编写健壮代码的重要性。
9 0
|
2天前
|
存储 安全 Java
Java容器类List、ArrayList、Vector及map、HashTable、HashMap
Java容器类List、ArrayList、Vector及map、HashTable、HashMap
|
2天前
|
Java
Java为什么建议初始化HashMap的容量大小?
【5月更文挑战第3天】Java中初始化HashMap容量能提升性能。默认容量16,扩容按当前的1/2进行。预估元素数量设定合适容量可避免频繁扩容,减少性能损耗。过大浪费内存,过小频繁扩容,需权衡。Java 8后扩容策略调整,但核心仍是预估初始容量以优化性能。
40 1
|
2天前
|
存储 Java 索引
【JAVA】HashMap的put()方法执行流程
【JAVA】HashMap的put()方法执行流程
|
2天前
|
存储 安全 Java
Java程序员必须掌握的数据结构:HashMap
HashMap底层原理实现是每个Java Boy必须掌握的基本技能,HashMap也是业务开发每天都需要遇到的好伙伴。如此基础且核心的底层数据结构,JDK也给其赋予了线程安全的功能,我们来看看~
41 2
Java程序员必须掌握的数据结构:HashMap