从LeetCode热题到JDK源码:一篇文章吃透Java哈希表

22 阅读5分钟

力扣热题100-《哈希》

哈希 即散列 其发挥的作用包括:1.压缩映射 提高存储空间的利用率 2.提高查询效率,快速去重或快速比较

1.两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1]

发现nums[i]和target有对应关系(target - nums[i] = nums[j]),可使用哈希提升查询效率

class Solution {
    public int[] twoSum(int[] nums, int target) {
         Map<Integer,Integer> hashtable = new HashMap<Integer,Integer>();
         for(int i = 0 ; i < nums.length; i++) {
            if(hashtable.containsKey(target - nums[i])) {
                return new int[]{hashtable.get(target - nums[i]),i};
            }
            hashtable.put(nums[i],i);
         }
               return new int[0];
        }
    }

2.字母异位词分组

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

示例 1:

输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]

输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

1.利用hashmap将排序好的字母作为key 2.把可以映射到数组的str作为value

import java.util.*;
​
public class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        // 1. 创建 HashMap,key是排序后的字符串,value是异位词列表
     Map<String, List<String>> map = new HashMap<>();
    // 2. 遍历每个字符串
    for (String str : strs) {
        // 3. 将字符串转换为字符数组并排序
        char[] chars = str.toCharArray();
        Arrays.sort(chars);
        String key = new String(chars);  // 排序后的字符串作为key
        if (!map.containsKey(key)) {
            map.put(key, new ArrayList<>());
        }
        map.get(key).add(str);
    }
    return new ArrayList<>(map.values());
}

3.最长连续序列

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。

示例 2:

输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9

示例 3:

输入:nums = [1,0,1,2]
输出:3

1.先用set去重 2.遍历数组,把每一个数作为起点

class Solution {
​
  public int longestConsecutive(int[] nums) {
​
•    Set<Integer> set = new HashSet<>();
​
•    for(int num : nums) {
​
•      set.add(num);
​
•    }
​
•    int max = 0;
​
•    for (int num : set) {
​
•      if(!set.contains( num-1 )) {
​
•        int now = num;
​
•        int count = 1;
​
•      while(set.contains(now+1)) {
​
•        now += 1;
​
•        count += 1;
​
•      }
​
•      max = Math.max(max,count);
​
•    }
​
  }
​
•    return max;
​
}
​
}

解析HashMap源码:

Contains和ContainsKey方法:

image.png

image.png

如图,contains和ContainsKey方法实际调用了getNode方法

image.png

首先要对比哈希值,再对比Key值(因为哈希值相同不一定key会相同)

成功找到对应的哈希桶后: 对该哈希桶(必有一个头节点)操作

如果头节点不是目标节点,处理后续节点

if ((e = first.next) != null) {

把头节点的下一个节点赋值给e并判断是否存在后续节点

若存在后续节点(哈希桶的本质是链表或红黑树):差异化查找,链表就正常遍历,红黑树查询时间logn

containsKey(Object key):判断当前 Map 集合中是否包含指定的键(key) ,只关注「键」的存在性,与对应的值(value)无关。

contains(Object o):判断当前 Collection 集合中是否包含指定的元素(element)Collection 是单元素集合,没有「键值对」之分,直接判断元素本身是否存在。所有的Colletion类都会实现该方法

常见CRUD操作:

方法返回值说明示例
V put(K key, V value)V添加/更新:key存在则覆盖,返回旧值;否则返回nullmap.put("a", 1)
V putIfAbsent(K key, V value)V不存在才添加:key存在则不操作,返回旧值;否则插入并返回nullmap.putIfAbsent("a", 2)
V get(Object key)V查询:key不存在返回nullInteger v = map.get("a")
V getOrDefault(Object key, V defaultValue)V查询带默认值:不存在返回default而非nullmap.getOrDefault("b", 0)
V remove(Object key)V删除:返回被删的值,不存在返回nullmap.remove("a")
boolean remove(Object key, Object value)boolean条件删除:仅当key-value匹配时才删除map.remove("a", 1)
V replace(K key, V value)V替换:key存在则替换,返回旧值;不存在返回null(不插入)map.replace("a", 2)
boolean replace(K key, V oldValue, V newValue)boolean条件替换:仅当旧值匹配时才替换map.replace("a", 1, 2)
boolean containsKey(Object key)boolean是否包含keymap.containsKey("a")
boolean containsValue(Object value)boolean是否包含value(遍历,O(n))map.containsValue(1)

HashSet vs HashMap 区别及 HashSet 使用指南

核心区别

特性HashSetHashMap
数据结构只存储单个元素存储键值对
实现方式底层是 HashMap (元素作为 key)哈希表 + 链表/红黑树
用途去重、判断元素是否存在根据 key 查找 value
元素重复元素不能重复key 不能重复,value 可以重复
性能添加/删除/查找: O(1)添加/删除/查找: O(1)
内存占用相对较小相对较大(存 key 和 value)

HashMap的实现:

本质上由数组组成,数组存储哈希桶(每个地址对应一个哈希桶),通过 (数组长度-1) & 哈希值 计算得到地址,每个地址下的哈希桶由链表或者红黑树组成

在java8之后:当哈希桶长度小于8时,用链表实现;当哈希桶长度大于等于8且小于等于64时会退化为红黑树(树的查询复杂度为logn)

HashSet的实现:

内部依赖HashMap

HashSet 方法实际调用 HashMap返回值处理
add(E e)map.put(e, PRESENT)put 返回 null 表示插入成功,返回旧值表示已存在
remove(Object o)map.remove(o)返回 PRESENT 表示删除成功,null 表示不存在
contains(Object o)map.containsKey(o)直接返回
size()map.size()直接返回
iterator()map.keySet().iterator()遍历 KeySet
clear()map.clear()直接委托# 力扣热题100-《哈希》