hashmap原理总结

HashMap的数据结构




哈希表-拉链法,我们可以理解为“链表的数组” ,如图:

首先HashMap里面实现一个静态内部类Entry,其重要的属性有 key , value, next

HashMap的存取实现

初始:table初始大小是的2的初始容量次幂

   public HashMap( int initialCapacity,  float loadFactor) {
        .....
         int capacity = 1;
         while (capacity < initialCapacity)
            capacity <<= 1;                           //左移代表乘2,右移代表除2
         this. loadFactor = loadFactor;
         threshold = ( int)(capacity * loadFactor);
         table = new Entry[capacity];
        init();
    }

put

  public V put(K key, V value) {
         if (key ==  null)
             return putForNullKey(value);  //null总是放在数组的第一个链表中
         int hash =  hash(key.hashCode());
         int i =  indexFor(hash,  table. length);//找到在数组中的位置
         //遍历数组右侧链表
         for (Entry<K,V> e =  table[i]; e !=  null; e = e. next) {
            Object k;
             //如果key在链表中已存在,则替换为新value
             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;

    }

void addEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e =  table[bucketIndex];
     table[bucketIndex] =  new Entry<K,V>(hash, key, value,  e);  //参数e, 是Entry.next
     //如果table数组的size超过threshold,则扩充table大小。再散列
     if ( size++ >=  threshold)
            resize(2 *  table. length);
}

HashMap里面设置一个因子,随着map的存储的size越来越大,Entry[]会以一定的规则加长长度。




get

  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;
}




null key的存取

null key总是存放在Entry[]数组的第一个元素。HashMap是泛型类,key和value可以为任何类型,包括null类型。key为null的键值对永远都放在以table[0]为头结点的链表中,当然不一定是存放在头结点table[0]中。

    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;
    }
     private V  getForNullKey() {
         for (Entry<K,V> e =  table[0]; e !=  null; e = e. next) {
             if (e. key ==  null)
                 return e. value;
        }
         return  null;
    }

hashMap为什么存储2的n次幂个空间?

确定数组index:hashcode % table.length取模,HashMap存取时,都需要计算当前key应该对应Entry[]数组哪个元素,即计算数组下标;算法如下:

    /**
     * Returns index for hashcode h.
     */
     static  int  indexFor( int  hashcodeint length) {
         return  hashcode  & (length-1);
    }
只有全是1, hashcode 进行按位与才是最均匀的

HashMap是根据key的hash值决定key放到哪个桶中,通过hash&(length-1)公式计算得出,2的n次方一定是最高位1其它低位是0,这样减1的时候才能得到01111这样都是1的二进制。这样(n - 1) & hash的值是均匀分布的,可以减少hash冲突。


 

扩容

当hash数组中的元素个数超出了最大加载因子和容量的乘积时,要对hashMap进行扩容,扩容过程存在于hashmap的put方法中, 扩容过程始终以2次方增长

3. 解决hash冲突的办法

  1. 开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
  2. 再哈希法
  3. 链地址法
  4. 建立一个公共溢出区

Java中hashmap的解决办法就是采用的链地址法。

java8的性能改善

当链表长度太长(默认超过8)时,链表就转换为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能

put方法

table变量:HashMap的底层数据结构,是Node类的实体数组,用于保存key-value对;
capacity:并不是一个成员变量,但却是一个必须要知道的概念,表示容量;
size变量:表示已存储的HashMap的key-value对的数量;
loadFactor变量:装载因子,用于衡量满的程度;
threshold变量:临界值,当超出该值时,表示table表示该扩容了;

threshold=capacity*loadFactor(默认是0.75)

当size>threshold则扩容

一. put方法

HashMap使用哈希算法得到数组中保存的位置,然后调用put方法将key-value对保存到table变量中。我们通过图来演示一下存储的过程。

resize原理

hashmap线程不安全的会导致什么问题?

JDK1.7 HashMap线程不安全体现在:死循环、数据丢失
JDK1.8 HashMap线程不安全体现在:数据覆盖

HashMap为什么线程不安全-优快云博客

<think>好的,我现在需要详细解释哈希映射HashMap)的数据结构原理。首先,我得回想一下自己对HashMap的理解,并确保覆盖所有关键点。用户提供的引用内容中提到了哈希表作为底层结构、哈希函数、冲突处理等,这些都是需要涵盖的。 首先,HashMap的基本结构是什么?它基于哈希表,使用数组来存储键对。哈希函数将键转换为数组的索引,这样可以直接访问对应位置的数据。这里要解释哈希函数的作用,比如将任意大小的数据映射到固定大小的,作为数组。 接下来,哈希冲突是可避免的,因为同的键可能生成相同的哈希。如何处理冲突?常见的方法有开放寻址法和链地址法。根据引用中的内容,用户可能已经提到过链地址法,即每个数组位置是一个链表,存放所有哈希冲突的键对。需要详细说明这个过程,比如当发生冲突时,将新元素添加到链表中。 然后,需要讨论哈希函数的设计。好的哈希函数应该均匀分布键,以减少冲突。可能提到取模运算,比如用键的哈希数组长度取模得到索引。但数组的长度通常选择质数,以减少重复,比如引用中可能提到的。 扩容机制也是一个关键点。当元素数量超过负载因子(比如0.75)时,数组需要扩容,通常翻倍,并重新哈希所有元素到新的数组中。这会影响到性能,但能保持低冲突率。需要解释负载因子的作用和扩容的过程。 另外,时间复杂度方面,理想情况下,插入、删除、查找都是O(1),但最坏情况下(比如所有键冲突)退化为O(n)。所以强调良好的哈希函数和扩容策略的重要性。 可能还需要提到HashMap的应用场景,比如缓存、字典实现等,但用户主要问的是原理,这部分可以简要带过或者放在相关问题里。 现在检查是否有遗漏的部分。引用1提到C语言实现,引用2讲工作原理,引用3提到哈希表和HashMap的实现方法。所以需要综合这些信息,确保覆盖哈希表结构、哈希函数、冲突处理、扩容机制。 需要确保使用正确的中文术语,比如“链地址法”而是“链表法”。另外,按照系统级指令,行内数学表达式用$...$,比如哈希函数h(key) = key % capacity,这里的公式需要用符号表示,可能需要用LaTeX格式。 在解释过程中,要自然引用提供的引用内容,比如在提到哈希表作为底层结构时引用[^1],在解释哈希函数时引用[^2],在冲突处理时引用[^3]。 最后,生成相关问题,这些问题应该与HashMap原理相关,比如哈希函数设计、冲突处理比较、扩容机制的影响、线程安全等,确保问题有针对性,帮助用户进一步学习。 总结思考步骤: 1. 定义和基本结构:哈希表、键对存储。 2. 哈希函数的作用和设计。 3. 冲突处理方法,特别是链地址法。 4. 扩容机制和负载因子。 5. 时间复杂度分析。 6. 引用相关文献。 7. 生成相关问题。 现在将这些点组织成连贯的解释,确保符合用户的要求,结构清晰,分点说明,并正确使用格式。</think>### 哈希映射HashMap数据结构原理分步解释 #### 1. **基本结构** 哈希映射的核心是**哈希表**,它是一个数组结构,每个位置称为一个“桶”(Bucket)。每个桶存储键对(Key-Value Pair)的集合。例如,一个容量为10数组,索引为$0$到$9$,每个索引对应一个桶。 #### 2. **哈希函数** 哈希函数将键(Key)转换为数组索引,公式为: $$ \text{index} = h(\text{key}) \% \text{capacity} $$ 其中$h(\text{key})$是键的哈希,$\text{capacity}$是数组容量。例如,若键为`"apple"`,哈希为$1234$,容量为$10$,则索引为$1234 \% 10 = 4$。 **设计要求**: - **均匀分布**:减少冲突概率。 - **高效计算**:时间复杂度为$O(1)$。 #### 3. **冲突处理** 同键可能产生相同的索引(冲突),常用**链地址法**解决: - 每个桶维护一个链表(或红黑树)。 - 冲突时,新键对追加到链表末尾。 例如,键`"apple"`和`"banana"`同时映射到索引$4$,则链表存储这两个键对[^3]。 #### 4. **扩容机制** 当元素数量与容量的比(负载因子,默认$0.75$)超过阈时,触发扩容: 1. 新建一个容量翻倍的数组。 2. 重新计算所有键的哈希并分配到新桶。 此过程保证桶的负载降低,维持$O(1)$操作效率[^3]。 #### 5. **时间复杂度** - **理想情况**:插入、查找、删除均为$O(1)$(无冲突)。 - **最坏情况**:所有键冲突,退化为链表遍历$O(n)$。 优化手段包括使用红黑树(Java 8+)将链表操作优化至$O(\log n)$。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值