【Java从入门到放弃 之 HashMap 和 HashSet】

HashMap 和 HashSet

HashMap

HashMap 是 Java 集合框架中的一个类,它实现了 Map 接口,提供了一种基于哈希表的数据结构来存储键值对(key-value pairs)。HashMap 允许使用 null 作为键或值,并且不保证映射的顺序(特别是它们不会保持插入顺序)。

详细介绍

  1. 概述
    定义:HashMap 是一种以哈希表为基础的实现,提供了常数时间复杂度 O(1) 的基本操作(如 get 和 put),假设哈希函数分布均匀。
    特点:
    1. 线程不安全:与 Hashtable 不同,HashMap 不是同步的,因此在多线程环境中需要额外的同步机制。
    2. 允许一个 null 键和多个 null 值:可以包含一个 null 键和任意数量的 null 值。
    3. 无序:不保证元素的迭代顺序;如果需要有序,可以考虑使用 LinkedHashMap 或 TreeMap。
  2. 内部工作原理
    HashMap 内部使用数组和链表(或红黑树)相结合的方式来存储键值对。每个数组元素被称为"桶"(bucket),其中可能包含一个链表或红黑树,用于处理哈希冲突(即不同键映射到同一个桶的情况)。
    1. 哈希函数
      HashMap 使用键对象的 hashCode() 方法计算哈希码,然后通过某种算法将哈希码映射到数组的一个索引位置。
      如果两个不同的键具有相同的哈希码,或者不同的哈希码但映射到了同一个桶,则会发生哈希冲突。此时,这些键值对会被添加到同一个桶的链表中。
    2. 解决哈希冲突
      链地址法(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的值 还有指向下一个元素的引用。

  1. 当向HashMap中put(key,value)时,会首先通过hash算法计算出存放到数组中的位置,比如位置索引为i,将其放入到Entry[i]中
  2. 如果这个位置上面已经有元素了,那么就将新加入的元素放在链表的头上(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之后会转红黑树)进行实现。有下列特点:

  1. 根据key或者value的效率很高越等与O(1)
  2. 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();
    }
相关推荐
幽兰的天空8 分钟前
Java 基础之 XQuery:强大的 XML 查询语言
java·运维·数据库
不能只会打代码8 分钟前
Java多线程与线程池技术详解(九)
java·开发语言·java线程池·多线程与线程池
灰色人生qwer22 分钟前
使用 java -jar 命令启动 Spring Boot 应用时,指定特定的配置文件的几种实现方式
java·spring boot·jar
sagima_sdu36 分钟前
Python 程序与 Java 系统集成:通过 FastAPI 实现 HTTP 接口
java·python·fastapi
烬奇小云37 分钟前
基于Dockerfile的博客管理系统的容器化部署
java·sql·mysql·spring
液态不合群44 分钟前
[Java] Stream流使用最多的方式
java·windows·python
阿哈831 小时前
A6481 基于Java+mysql+Vue+MySQL+uni-app在线商城系统微信小程序的设计与实现 配置 源码 全套资料
java·vue.js·mysql·微信小程序·uni-app
A Runner for leave1 小时前
146.组合总和
java·数据结构·python·算法·leetcode
cooldream20092 小时前
在Spring Security中使用权限注解实现精确控制
java·spring·springboot
小马爱打代码2 小时前
Spring Boot 启动流程及原理介绍
java·spring boot