HashMap 和 HashSet
- HashMap
-
- 详细介绍
- HashMap的数据结构
- Hashmap的put方法的参数hash怎么计算
- [JDK1.8 hashmap为什么引入红黑树](#JDK1.8 hashmap为什么引入红黑树)
- 基本用法
- 总结
- HashSet
HashMap
HashMap 是 Java 集合框架中的一个类,它实现了 Map 接口,提供了一种基于哈希表的数据结构来存储键值对(key-value pairs)。HashMap 允许使用 null 作为键或值,并且不保证映射的顺序(特别是它们不会保持插入顺序)。
详细介绍
- 概述
定义:HashMap 是一种以哈希表为基础的实现,提供了常数时间复杂度 O(1) 的基本操作(如 get 和 put),假设哈希函数分布均匀。
特点:- 线程不安全:与 Hashtable 不同,HashMap 不是同步的,因此在多线程环境中需要额外的同步机制。
- 允许一个 null 键和多个 null 值:可以包含一个 null 键和任意数量的 null 值。
- 无序:不保证元素的迭代顺序;如果需要有序,可以考虑使用 LinkedHashMap 或 TreeMap。
- 内部工作原理
HashMap 内部使用数组和链表(或红黑树)相结合的方式来存储键值对。每个数组元素被称为"桶"(bucket),其中可能包含一个链表或红黑树,用于处理哈希冲突(即不同键映射到同一个桶的情况)。- 哈希函数
HashMap 使用键对象的 hashCode() 方法计算哈希码,然后通过某种算法将哈希码映射到数组的一个索引位置。
如果两个不同的键具有相同的哈希码,或者不同的哈希码但映射到了同一个桶,则会发生哈希冲突。此时,这些键值对会被添加到同一个桶的链表中。 - 解决哈希冲突
链地址法(Separate Chaining):默认情况下,HashMap 使用链表来解决哈希冲突。当链表长度超过一定阈值时(默认为8),链表会转换为红黑树,以提高查找效率。
红黑树优化:从 Java 8 开始,当某个桶中的节点数量达到特定阈值时,HashMap 会将该桶中的链表转换为红黑树,从而将最坏情况下的时间复杂度从 O(n) 降低到 O(log n)。
- 哈希函数
HashMap的数据结构
HashMap本质上是一个一定长度的数组,数组中存放的是链表。它是一个Entry类型的数组。
Entry的源码
java
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
其中存放了key value hash的值 还有指向下一个元素的引用。
- 当向HashMap中put(key,value)时,会首先通过hash算法计算出存放到数组中的位置,比如位置索引为i,将其放入到Entry[i]中
- 如果这个位置上面已经有元素了,那么就将新加入的元素放在链表的头上(JDK1.7是头插,JDK1.8是尾插),最先加入的元素在链表尾。比如,第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] =A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0]= B,如果又进来C,index也等于0那么C.next = B,Entry[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起,也就是说数组中存储的是最后插入的元素。
Hashmap的put方法的参数hash怎么计算
java
static final int hash(object key){
int h;
return (key == null)? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
- 当key = null时, hash值=0,所以HashMap的key可为null
注:对比HashTable,HashTable对key直接hashCode () ,若key为null时,会抛出异常,所以HashTable的key不可为null - 当key ≠ null时,则通过先计算出key的 hashCode()(记为h),然后对哈希码进行扰动处理:按位异或()哈希码自身右移16位后的二进制
JDK1.8 hashmap为什么引入红黑树
由于在JDK1.7之前,HashMap的数据结构为:数组+链表。
链表来存储hash值一样的key-value.如果按照链表的方式存储,随着节点的增加数据会越来越多,这会导致查询节点的时间复杂度会逐渐增加,平均时间复杂度O(n)。为了提高查询效率,故在JDK1.8中引入了改进方法红黑树。此数据结构的平均查询效率为O(logn)。
基本用法
java
public interface Map<K, V> { //K和V是类型参数,分别表示键(Key)和值(Value)的类型
V put(K key, V value); //保存键值对,如果原来有key,覆盖,返回原来的值
V get(Object key); //根据key获取值, 没找到,返回null
V remove(Object key); //根据key删除键值对, 返回key原来的值,如果不存在,返回null
int size(); //查看Map中键值对的个数
boolean isEmpty(); //是否为空
boolean containsKey(Object key); //查看是否包含某个key
boolean containsValue(Object value); //查看是否包含某个value
void putAll(Map<? extends K, ? extends V> m); //保存m中的所有键值对到当前Map
void clear(); //清空Map中所有键值对
Set<K> keySet(); //获取Map中所有key
Collection<V> values(); //获取Map中所有值
Set<Map.Entry<K, V>> entrySet(); //获取Map中的所有键值对
interface Entry<K, V> {
K getKey(); // 获取key
V getValue(); // 获取value
V setValue(V value);
boolean equals(Object o);
int hashCode();
}
boolean equals(Object o);
int hashCode();
}
boolean containsValue(Object value);
Set<K> keySet();
总结
HashMap实现了Map接口,可以方便地存储键值对。内部时采用数组链表(后面jdk8之后会转红黑树)进行实现。有下列特点:
- 根据key或者value的效率很高越等与O(1)
- hashmap中的键值对没有顺序,因为求出来的hash时随机分布的
HashSet
HashSet内部时由HashMap实现的,它内部持有一个Hashmap的实例变量。
java
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable
{
@java.io.Serial
static final long serialVersionUID = -5024744406713321676L;
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
/**
* Constructs a new, empty set; the backing {@code HashMap} instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
}
java
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
我们知道map是由key value键值对组成的。 HashSet的里面的map实例的value固定为PRESENT 这个变量。
Set基本用法
java
public interface Set<E> extends Collection<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<? > c);
boolean addAll(Collection<? extends E> c);
boolean retainAll(Collection<? > c);
boolean removeAll(Collection<? > c);
void clear();
boolean equals(Object o);
int hashCode();
}