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 (Entrye = 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(); Entryentry = 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。