Map和Set

一.Map和Set

TreeMap 和 TreeSet 即 java 中利用搜索树实现的 Map 和 Set。

HashMap 和 HashSet 即 java 中利用哈希表实现的 Map 和 Set

Map和Set是 一种适合动态查找的集合容器。

1.根据姓名查询考试成绩 2. 通讯录,即根据姓名查询联系方式 3. 不重复集合,即需要先搜索关键字是否已经在集合中 可能在查找时进行一些插入和删除的操作,即动态查找

一般把搜索的数据称为关键字(Key),和关键字对应的称为值(Value)

Map中存储的就是key-value的键值对,Set中只存储了Key

二、Map的使用

1.Map是一个接口类,该类没有继承自Collection,不能直接实例化对象,如果要实例化对象只能实例化其实现类TreeMap或者HashMap,该类中存储的是结构的键值对,并且K一定是唯一的,不能重复,value是可以重复的。

2.在TreeMap中插入键值对时,key不能为空,否则就会抛NullPointerException异常,value可以为空。但 是HashMap的key和value都可以为空。

3. Map中的Key可以全部分离出来,存储到Set中来进行访问(因为Key不能重复)。

4. Map中的value可以全部分离出来,存储在Collection的任何一个子集合中(value可能有重复)。

5. Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行重新插入。

2)map种常用的方法

方法                                                        解释

V get(Object key)                                    返回 key 对应的 value

V getOrDefault(Object key, V defaultValue) 返回 key 对应的 value,key 不存在,返回默认值

V put(K key, V value) 设置 key 对应的 value

V remove(Object key) 删除 key 对应的映射关系

Set keySet() 返回所有 key 的不重复集合

Collection values() 返回所有 value 的可重复集合

<Set> entrySet() 返回所有的 key-value 映射关系

boolean containsKey(Object key) 判断是否包含 key

boolean containsValue(Object value) 判断是否包含 value

Map.Entry 是Map内部实现的用来存放键值对映射关系的内部类,该内部类中主要提供了 <key,value>的获取,value的设置以及Key的比较方式。

K getKey() 返回 entry 中的 key

V getValue() 返回 entry 中的 value

V setValue(V value) 将键值对中的value替换为指定value

代码:

for(String s : m.keySet()){
System.out.print(s + " ");
}
System.out.println();
// 打印所有的value
// values()是将map中的value放在collect的一个集合中返回的
for(String s : m.values()){
System.out.print(s + " ");
}
System.out.println();
// 打印所有的键值对
// entrySet(): 将Map中的键值对放在Set中返回了
for(Map.Entry<String, String> entry : m.entrySet()){
System.out.println(entry.getKey() + "--->" + entry.getValue());
}
System.out.println();
}

3)代码实现例题

例题:

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param numbers int整型一维数组 
     * @param target int整型 
     * @return int整型一维数组
     */
    public int[] twoSum (int[] numbers, int target) {
       Map<Integer,Integer> map=new HashMap<Integer,Integer>();
       for(int i=0;i<numbers.length;i++){
         if(map.containsKey(target-numbers[i])){
            //返回的下标按升序排列
            //return new int[]{i+1,map.get(target-numbers[i])+1};
            //因为先判断是否包含map.containsKey(target-numbers[i]),包含的话就是先存储的,所以下表更小
            return new int[]{map.get(target - numbers[i])+1, i+1};
        }else{
        map.put(numbers[i],i);
        }
       }
       return new int[]{-1,-1};
    }
}

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param numbers int整型一维数组 
     * @return int整型
     */
    public int MoreThanHalfNum_Solution (int[] numbers) {
    int n=numbers.length;
    Map<Integer,Integer> map=new HashMap<Integer,Integer>();
    for(int i=0;i<n;i++){
        if(!map.containsKey(numbers[i])){
            map.put(numbers[i],1);
        }else{
          
            map.put(numbers[i],map.get(numbers[i])+1);
        }
        if(map.get(numbers[i])>n/2){
            return numbers[i];
        }

        
    }
    return 0;
    }
}

三、Set的使用

Set是继承自Collection的接口类,Set中只存储了Key,并且要求key一定要唯一

TreeSet的底层是使用Map来实现的,其使用key与Object的一个默认对象作为键值对插入到Map中的

4. Set最大的功能就是对集合中的元素进行去重

5. 实现Set接口的常用类有TreeSet和HashSet,还有一个LinkedHashSet,LinkedHashSet是在HashSet的基础 上维护了一个双向链表来记录元素的插入次序。

6. Set中的Key不能修改,如果要修改,先将原来的删除掉,然后再重新插入

7. TreeSet中不能插入null的key,HashSet可以。

代码解释:

public static void TestSet(){
Set<String> s = new TreeSet<>();
// add(key): 如果key不存在,则插入,返回ture
// 如果key存在,返回false
boolean isIn = s.add("apple");
s.add("orange");
s.add("peach");
s.add("banana");
System.out.println(s.size());
System.out.println(s);//直接打印s

//使用迭代器遍历,输出每个元素 + 空格
Iterator<String> it = s.iterator();
while(it.hasNext()){
System.out.print(it.next() + " ");
}

TreeSet 默认有序输出[apple, banana, orange, peach]

2)常用方法

方法 解释

boolean add(E e) 添加元素,但重复元素不会被添加成功

void clear() 清空集合

boolean contains(Object o) 判断 o 是否在集合中

Iterator iterator() 返回迭代器

boolean remove(Object o) 删除集合中的 o

int size() 返回set中元素的个数

boolean isEmpty() 检测set是否为空,空返回true,否则返回false

Object[] toArray() 将set中的元素转换为数组返回

boolean containsAll(Collection c) 集合c中的元素是否在set中全部存在,是返回true,否则返回 false

boolean addAll(Collection c) 将集合c中的元素添加到set中,可以达到去重的效果

四、哈希表

理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。

如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

当向该结构中:

插入元素 根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放

搜索元素 对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若 关键码相等,则搜索成功

该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)

 

插入44有冲突

冲突-解决-闭散列 闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以 把key存放到冲突位置中的“下一个” 空位置中去。那如何寻找下一个空位置呢? 1. 线性探测 比如上面的场景,现在需要插入元素44,先通过哈希函数计算哈希地址,下标为4,因此44理论上应该插在该 位置,但是该位置已经放了值为4的元素,即发生哈希冲突。 线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。 插入 通过哈希函数获取待插入元素在哈希表中的位置 如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到 下一个空位置,插入新元素

冲突-解决-开散列/哈希桶

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子 集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。

五、HashMap底层原理和扩容机制

HashMap底层采用数组+链表/红黑树结构,通过哈希算法确定元素存储位置。默认初始容量16,负载因子0.75,当元素数量超过(容量×负载因子)时触发扩容。扩容时创建双倍容量新数组,通过高位运算重新计算节点位置(JDK8优化为无需重新hash),原数据通过尾插法迁移到新数组。链表长度超过8且数组长度≥64时会转为红黑树,提升查询效率。

HashMap 的底层是一个 哈希表(Hash Table),本质是一个动态扩容的数组(称为table),数组的每个元素是一个 链表(或 红黑树,JDK8 及以后),用于存储哈希冲突的键值对。

  • 桶(Bucket):数组的每个槽位(table[i])称为一个桶,用于存放哈希值相同的键值对。
  • 哈希冲突(Hash Collision):不同键通过哈希函数计算出相同的桶下标,导致多个键值对需要存放在同一个桶中。
  • 链表(Entry 或 Node 节点):JDK7 中桶内元素以链表形式存储(节点类型为Entry<K,V>);JDK8 中改为Node<K,V>,当链表长度超过阈值时转换为红黑树。
  • 红黑树(TreeNode):JDK8 引入,当链表长度 ≥8 且数组长度 ≥64 时,链表转换为红黑树(节点类型为TreeNode<K,V>),以将查找时间复杂度从 O(n) 优化到 O(logn)。

键的哈希值通过key.hashCode()方法获取,但 HashMap 会对其进行二次哈希(hash()方法),目的是 减少哈希冲突

当不同键的哈希值映射到同一个桶时,HashMap 使用 链地址法 解决冲突:将冲突的键值对以链表形式挂在同一个桶下。

HashMap 通过 扩容(resize) 保持负载因子(Load Factor)在合理范围,避免哈希冲突过多导致性能下降。

触发条件:当元素数量(size)超过容量(capacity) × 负载因子(loadFactor)时触发扩容。
默认容量为 16,负载因子为 0.75

六、总结

TreeMapHashMap

是 Java 集合框架中两种常用的 Map 接口实现,它们都用于存储键值对(key-value pairs),但在内部实现、性能特征和使用场景上存在显著差异。以下是它们之间主要的不同点:


1. 底层数据结构

  • HashMap

    • 基于 哈希表(Hash Table) 实现。
    • 使用数组 + 链表(或红黑树,当链表长度超过阈值时)来存储数据。
    • 通过 hashCode() 和 equals() 方法来确定键的存储位置和进行查找。
  • TreeMap

    • 基于 红黑树(Red-Black Tree) 实现。
    • 是一种自平衡的二叉搜索树,保证了树的高度接近对数级别,从而保证操作效率。
    • 键按照自然顺序(natural ordering)或自定义的 Comparator 进行排序。

2. 元素排序

  • HashMap

    • 不保证任何顺序。元素的排列顺序可能随着扩容而改变。
    • 如果需要有序,可以使用 LinkedHashMap(按插入顺序)。
  • TreeMap

    • 键是有序的。默认按照键的自然顺序排序(如字母顺序、数值大小)。
    • 也可以通过构造函数传入 Comparator 来自定义排序规则。
    • 这使得 TreeMap 非常适合需要范围查询(如查找某个范围内的键)的场景。

3. 时间复杂度

操作HashMapTreeMap
查找(get)O(1) 平均情况,最坏 O(n)O(log n)
插入(put)O(1) 平均情况,最坏 O(n)O(log n)
删除(remove)O(1) 平均情况,最坏 O(n)O(log n)
  • HashMap 在理想情况下(哈希函数良好,冲突少)性能非常高效。
  • TreeMap 的性能更稳定,但平均来说比 HashMap 慢。

4. 键的约束

  • HashMap

    • 允许一个 null 键和多个 null 值。
    • 对键没有可比性要求。
  • TreeMap

    • 不允许 null 键(会抛出 NullPointerException),因为排序时无法比较 null
    • 允许 null 值。
    • 键必须实现 Comparable 接口,或通过 Comparator 提供比较逻辑。

5. 内存占用

  • HashMap

    • 通常内存开销较小,但需要预留一定的容量以减少哈希冲突。
  • TreeMap

    • 每个节点需要存储左右子节点、父节点和颜色信息,因此内存开销更大。

6. 适用场景

场景推荐使用
快速查找、插入、删除,不关心顺序✅ HashMap
需要键有序,或进行范围查询(如 subMapheadMaptailMap✅ TreeMap
需要统计排名、区间数据(如分数段统计)✅ TreeMap
键可能为 null✅ HashMap
高并发读写(非线程安全)可考虑 ConcurrentHashMap

7. 线程安全性

  • 两者都不是线程安全的。
  • 在多线程环境下,需要外部同步或使用 Collections.synchronizedMap() 包装,或使用并发容器(如 ConcurrentHashMap)。

TreeSetHashSet

是 Java 集合框架中两种常用的 Set 接口实现,它们都用于存储不重复的元素,但在底层数据结构、排序特性、性能和使用场景上有显著区别。以下是它们之间的详细对比:


1. 底层数据结构

  • HashSet

    • 基于 HashMap 实现。
    • 内部使用一个 HashMap 来存储元素,元素作为 HashMap 的 ,值是一个固定的 ObjectPRESENT)。
    • 依赖 哈希表(Hash Table),通过 hashCode() 和 equals() 方法来判断元素是否重复。
  • TreeSet

    • 基于 TreeMap 实现。
    • 内部使用一个 TreeMap 来存储元素,元素作为 TreeMap 的 ,值是固定的 Boolean.TRUE
    • 底层是 红黑树(Red-Black Tree),一种自平衡的二叉搜索树。

2. 元素排序

  • HashSet

    • 不保证任何顺序
    • 元素的存储顺序是无序的,且可能随着扩容而改变。
    • 如果需要保持插入顺序,可以使用 LinkedHashSet
  • TreeSet

    • 元素是有序的
    • 默认按照元素的 自然顺序(natural ordering) 排序(如字符串按字母顺序,数字按大小)。
    • 也可以通过构造函数传入 Comparator 来自定义排序规则。

3. 时间复杂度

操作HashSetTreeSet
添加(add)O(1) 平均情况O(log n)
删除(remove)O(1) 平均情况O(log n)
查找(contains)O(1) 平均情况O(log n)
  • HashSet 在哈希函数良好、冲突少的情况下性能非常高效。
  • TreeSet 的性能更稳定,但平均比 HashSet 慢。

4. 元素要求

  • HashSet

    • 元素类必须正确重写 hashCode() 和 equals() 方法,否则可能导致重复元素或查找失败。
    • 允许一个 null 元素。
  • TreeSet

    • 元素必须实现 Comparable 接口,或通过 Comparator 提供比较逻辑。
    • 不允许 null 元素(会抛出 NullPointerException),因为排序时无法比较 null

5. 内存占用

  • HashSet

    • 内存开销相对较小,但需要预留容量以减少哈希冲突。
  • TreeSet

    • 每个节点需要存储父节点、左右子节点和颜色信息(红黑树特性),因此内存开销更大。

6. 适用场景

场景推荐使用
快速添加、删除、查找,不关心顺序✅ HashSet
需要元素有序(如排行榜、字典序)✅ TreeSet
需要范围查询(如“找出大于100的所有数”)✅ TreeSet(支持 subSetheadSettailSet
元素可能为 null✅ HashSet
需要统计排名、区间数据✅ TreeSet

7. 线程安全性

  • 两者都不是线程安全的。
  • 在多线程环境下,需要外部同步或使用 Collections.synchronizedSet() 包装。

总结对比表

特性HashSetTreeSet
数据结构哈希表(基于 HashMap)红黑树(基于 TreeMap)
排序无序有序(自然或自定义)
时间复杂度(平均)O(1)O(log n)
null 元素允许一个不允许
元素要求重写 hashCode() 和 equals()实现 Comparable 或提供 Comparator
内存开销较小较大
支持范围操作✅(如 subSetheadSet
  • 使用 HashSet:当你追求高性能,且不需要排序时。
  • 使用 TreeSet:当你需要元素有序或进行范围查询、排名统计等操作时
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值