您的位置:

Java HashMap实现原理详解

HashMap是Java集合框架中常见的一种数据结构,它提供了快速存储、查找和删除元素的能力。它是由数组和链表实现的键值对,通过哈希算法来快速定位数组下标,避免了遍历整个数组来查找元素的低效率问题。

一、HashMap基本概念

在了解HashMap的实现原理之前,需要掌握HashMap的基本概念。

1. HashMap的键值对

HashMap的元素由键和值组成,称为键值对。HashMap中的键和值必须都是对象才能被存储。通常使用String作为键。

2. 哈希算法

哈希算法是通过将任意长度的数据映射为固定长度的数据,通常是一个较小的整数值,称为哈希值。哈希值可以快速地用于查找数组或其他数据结构中的数据,提高数据的访问效率。

3. 键的唯一性

HashMap中的键是唯一的。如果向HashMap中添加元素时,键已经存在,那么该元素的值将会覆盖掉原有的值。如果元素的值为null,则键可以重复。

二、HashMap的实现原理

HashMap是由数组和链表组成的键值对,每个键值对称为一个Entry对象。HashMap的核心就是通过哈希算法,将键值对存储到数组的不同位置中,然后通过链表将相同下标的元素串起来。

三、HashMap的put方法实现原理

put方法是HashMap中的核心操作,用于向HashMap中添加元素。其实现方式如下:

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

  

在这个put方法中,有三个变量需要了解:

1. hash:通过hashCode()方法计算的键的哈希值。

2. i:计算出的键值对将要存储的位置。

3. table:数组,用于存储键值对。

put方法实现的流程如下:

1. 判断键是否为null,如果是,直接调用putForNullKey方法,将值保存到map中,然后返回值。

2. 通过hash方法计算键的哈希值,然后通过indexFor方法计算出在table数组中的位置。

3. 从table数组中取出对应位置的元素,并遍历这个元素的链表,查找是否有相同key的元素。

4. 如果找到了相同key的元素,则将新的值赋给元素的value,并返回原有的value。

5. 如果在链表中没有找到相同key的元素,则将新的值添加到链表的最前面。

四、HashMap的get方法实现原理

get方法是用于获取HashMap中指定键的值。其实现方式如下:

public V get(Object key) {
    if (key == null)
        return getForNullKey();
    Entry entry = getEntry(key);
    return null == entry ? null : entry.getValue();
}

final Entry
    getEntry(Object key) {
    int hash = (key == null) ? 0 : hash(key);
    for (Entry
     e = table[indexFor(hash, table.length)];
        e != null;
        e = e.next) {
        Object k;
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))
            return e;
    }
    return null;
}

    
   
  

在这个get方法中,有三个变量需要了解:

1. hash:通过hashCode()方法计算的键的哈希值。

2. i:计算出的键值对在table数组中的位置。

3. table:数组,用于存储键值对。

get方法实现的流程如下:

1. 如果键为null,则直接调用getForNullKey 方法获取值。

2. 通过哈希算法和indexFor方法查找元素在table数组中的位置。

3. 从table数组中取出对应位置的元素,并遍历这个元素的链表,查找是否有相同的键,如果找到了,则返回对应的value。

4. 如果在链表中没有找到相同key的元素,则返回null。

五、HashMap的容量和负载因子

HashMap的容量和负载因子是指table数组的长度和键值对个数之比。

当键值对的个数达到容量和负载因子的乘积时,就需要对table进行扩容。同时,在扩容时,需要将现有的元素重新哈希,然后存储到新的table数组中。

在HashMap中,负载因子默认为0.75,即当键值对的个数达到75%的容量时,进行扩容。这是基于将数组长度设置为2的幂的算法,确保哈希链表的最大长度为8位,从而提高了查找和插入元素的效率。

六、HashMap的使用场景

HashMap适用于需要快速查找、插入、删除数据的场景。由于其底层是基于数组和链表实现的,因此处理大量数据时,执行效率较高。尤其是在需要频繁遍历元素的场景下,与传统的数组和链表相比,其查找效率更高。

七、HashMap的例子

以下是一个简单的HashMap例子,用于存储人名和年龄:

public class HashMapExample {
    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<>();
        map.put("Tom", 30);
        map.put("Jerry", 25);
        map.put("Mike", 28);
        map.put("Lucy", 33);

        System.out.println("Tom's age is " + map.get("Tom")); // Tom's age is 30

        map.remove("Lucy");

        System.out.println("Map contains Lucy? " + map.containsKey("Lucy")); // Map contains Lucy? false

        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + " is " + entry.getValue() + " years old.");
        }
    }
}

在这个例子中,我们创建了一个HashMap对象,并将四对键值对(人名和年龄)保存到HashMap中。然后通过get方法获取Tom的年龄,再使用remove方法删除Lucy,最后使用entrySet方法遍历Map的所有元素,并输出人名和年龄。

八、总结

本文探讨了HashMap的原理和实现方式。我们深入了解了HashMap的数据结构,介绍了其put方法和get方法的实现原理,还介绍了HashMap的容量、负载因子以及使用场景。

HashMap在Java中使用广泛,它提供了快速存储、查找和删除元素的能力,避免了遍历整个数组来查找元素的低效率问题。相信通过本文的介绍,读者能够更全面地了解HashMap,并在实际开发中更加灵活地使用HashMap。