【Java实战】Map集合从概念到底层逻辑(面试必清)

#新星杯·14天创作挑战营·第11期#

Map集合

  1. Map是一个接口
  2. Map称为双列集合,格式: [key=value,…],一次需要存一对数据作为一个元素。
  3. key=value:一个键值对对象
  4. 键不能重复,值可以重复,一一对应。

集合体系的特点

  1. Map系列集合的特点都是由键决定的,值只是一个附属品,值是不做要求的
  2. HashMap:无序,不重复,无索引;
  3. LinkedHashMap;有序,不重复,无索引
  4. TreeMap:按照大小默认排序,不重复,无索引
public class MapTest{
  public static void main(String[] args){
   Map<String,Integer> map=new HashMap<>();
   map.put("手表"100);
   map.put(null,null);
   System.out.println(map);
}
}

说明:
如果元素重复,会覆盖原来的值。

常用方法

int size()
获取集合的大小.
public int size()

  System.out.println(map.size());  

public void clear()
清空集合

    map.clear(); 
    System.out.println(); 

public booleanisEmpty()
判断集合是否为空,为空返回true,否则为flase

    System.out.println(map.isEmpty());

public V get(Object key)
根据键获取对应值

int v1=map.get("手表")System.out.println(v1); 

说明: V对应一开始声明的键的类型 如果键不存在,返回null;

public V remove(Object key)根据键删除整个元素(会返回值)

System.out.println(map.remove("手表"));

public boolean coniainKey(Oabject key)
判断是否包含某个键

System.out.println(map.containsKey("手表"))

public boolean containsValue(Object value)判断是否包含某个值

    System.out.println(map.containValue("100")); 

public Set<> keySet()获取Map集合中全部键

   Set<String> keys=map.keySet(); 

public Collection<> values()获取Map的全部值

Collection<Integer>
    values=map.values(); 

拓展:
putAll()

Map<String,Integer> map1=new HashMap<>();
map1.put("java",10);
Map<String,Integer> map2=new HashMap<>();
map2.pit("C",20);
map.putAll(map2);

说明:
会把map2集合中的元素都倒入map1,但map2中元素仍存在。

Map遍历方式

方式一键找值
先获取Map集合全部的键,再通过遍历找到值。
集合支持引用类型的数据,要用包装类

public class MapTestpublic static void main(String[] args)MapString,Double> map=new HashMap<>();
    map.put("张三"163.3);
    map.put("李四"165.8);
    map.put("王玉"169.5);
    //获取集合所有键
    SetString> keys=map.keySet();
    System.out.println(keys);

    for(String key:keys)//根据键获取值
    double value=map.get(key);
    System.out.println(key+"==="+value);
}
    }
}

方式二键值对
把键值对看成一个整体进行
调用entrySet()方法,键值对变成Entry类型才可以使用增强for
getKey()获取键
getValue()获取值

public class MapTestpublic static void main(String[] args)MapString,Double> map=new HashMap<>();
    map.put("张三"163.3);
    map.put("李四"165.8);
    map.put("王玉"169.5);
   //调用Map集合提供的entrySet方法,把Map类型集合转换成键值对类型的Set集合
   //entries[(张三=163.3),...]
   Set<Map.Entry<String,Double>> entries=map.entrySet();
   for(Map.Entry<String,Double> entry:entries){
   String key=entry.getKey();
   double value=entry.getValue();
   System.out.println(key+"---->"+value);
}
}
    }
}

方式三Lambda表达式
JDK 1.8开始使用(较简单)
遍历方法:
default void forEach(BiConsumer<? suoer K,? super V> action)

public class MapTestpublic static void main(String[] args)MapString,Double> map=new HashMap<>();
    map.put("张三"163.3);
    map.put("李四"165.8);
    map.put("王玉"169.5);
 
    map.forEach((k,v)->{
  System.out.println(k+"--->"+v)
})}
}
    }
}

代码解释:
BiConsumer是一个接口,可以使用匿名内部类(临时实现接口)

map.forEach(new BiConsumer<String,Double>{
  @Override
  public void accept(String k,Double v){
  System.out.println(k+"---->"+v);
}
});

底层逻辑:
利用第二种方式在遍历

default void forEach(BiConsumer<? suoer K,? super V> action){
Objects.requireNonNull(action);
for(Map.Entry<String,Double> entry:entries){
 
   try{
   String key=entry.getKey();
   double value=entry.getValue();
   System.out.println(key+"---->"+value);
   }catch(illegalStateException ise){
   throw new ConcurrentModificationExcepton(ise);
}
action.accept(key,value);
}

案例:

需求:
80名学生,选四个景点(A,B,C,D),每个学生只能选一个景点,统计出哪个景点想去的人最多。

public class Test{
  public static void main(String[] args){}{
  //1.把80个学生选择的景点拿到程序中来
  List<String> data=new ArrayList<>();
  String[] selects={"A","B","C","D"};
  Random r=new Random();
  for(int i=1;i<=80;i++){
int index=r.nextInt(4); //0 1 2 3
data.add(selects[index]);
}
System.ou.println(data);

//2.统计每个景点的选票
Map<String,Integer> result=new HashMap<>();
for(String s:data){
  //Map是否包含景点
  if(result.containKey(s)){
   result.put(s,resullt.get(s)+1);
}else{
  result.put(s,1);
}
}
System.out.println(map);
}
}

说明:
会输出data和Map集合
示例:

[A,A,B,C,D,D,C,.....]
{A=25,B=19,C=12,D=24}

总结:需要数据一一对应,可以使用Map

HashMap

HasnMap和 HashSet的底层原理一样都是基于哈希表
Set系列底层就是基于Map类型实现的,只是Set集合中只要键数据。

在这里插入图片描述
底层逻辑:

public class MapTestpublic static void main(String[] args)MapString,Double> map=new HashMap<>();
    map.put(new Student("zhangsan",25,165.4),1);
    map.put(new Student("lisi",24,168.4),2);
     
    }

HashMap中,重复性主要涉及键(key)和值(value)的重复性问题:

键的重复性

  • 唯一性要求HashMap 中键是唯一的。当使用 put(K key, V value) 方法插入键值对时,如果插入的键已经存在于 HashMap 中,那么对应的旧值会被新值替换,并且 put 方法会返回旧值。例如:
import java.util.HashMap;
import java.util.Map;

public class HashMapKeyDuplication {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("apple", 10);
        Integer oldValue = map.put("apple", 20);
        System.out.println("旧值: " + oldValue); // 输出: 旧值: 10
        System.out.println("当前值: " + map.get("apple")); // 输出: 当前值: 20
    }
}
  • 判断键是否存在:可以使用 containsKey(Object key) 方法来判断 HashMap 中是否已经存在某个键。例如:
import java.util.HashMap;
import java.util.Map;

public class HashMapKeyExistence {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("banana", 5);
        boolean hasBanana = map.containsKey("banana");
        boolean hasOrange = map.containsKey("orange");
        System.out.println("是否包含banana键: " + hasBanana); // 输出: 是否包含banana键: true
        System.out.println("是否包含orange键: " + hasOrange); // 输出: 是否包含orange键: false
    }
}

值的重复性

  • 允许重复HashMap 的值是允许重复的。不同的键可以映射到相同的值。例如:
import java.util.HashMap;
import java.util.Map;

public class HashMapValueDuplication {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("cherry", 15);
        map.put("peach", 15);
        System.out.println(map); // 输出: {cherry=15, peach=15}
    }
}
  • 查找具有特定值的键:由于值可以重复,要查找具有特定值的所有键并不直接。一种常见的方法是遍历 entrySet(),检查每个键值对的值是否匹配目标值。例如:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class FindKeysByValue {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("mango", 25);
        map.put("kiwi", 30);
        map.put("pineapple", 25);

        int targetValue = 25;
        List<String> keysWithTargetValue = new ArrayList<>();

        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            if (entry.getValue().equals(targetValue)) {
                keysWithTargetValue.add(entry.getKey());
            }
        }

        System.out.println("值为 " + targetValue + " 的键: " + keysWithTargetValue); 
        // 输出: 值为 25 的键: [mango, pineapple]
    }
}

总结来说,HashMap 中键必须唯一,值可以重复。理解这些重复性特点对于正确使用 HashMap 进行数据存储和操作非常重要。

HashMap 的功能实现高度依赖于键对象的 hashCode()equals() 方法。这两个方法在确保 HashMap 正常工作以及高效性方面起着至关重要的作用。

正确重写 hashCode() 和 equals() 的重要性
值可以重复,需要保证一致性

  • 一致性:如果一个类作为 HashMap 的键,必须确保重写 equals()hashCode() 方法时遵循一致性原则。即如果两个对象通过 equals() 方法比较相等,那么它们的 hashCode() 方法返回值必须相同。反之,两个对象 hashCode() 方法返回值相同,它们不一定相等(因为哈希冲突是不可避免的)。
import java.util.HashMap;
import java.util.Map;

class KeyObject {
    private int id;

    public KeyObject(int id) {
        this.id = id;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        KeyObject keyObject = (KeyObject) o;
        return id == keyObject.id;
    }

    @Override
    public int hashCode() {
        return id;
    }
}

public class HashMapHashCodeEquals {
    public static void main(String[] args) {
        Map<KeyObject, String> map = new HashMap<>();
        KeyObject key1 = new KeyObject(1);
        KeyObject key2 = new KeyObject(1);

        map.put(key1, "Value for key1");
        String value = map.get(key2);
        System.out.println(value); // 输出: Value for key1
    }
}

在上述示例中,KeyObject 类重写了 equals()hashCode() 方法。由于 key1key2id 相同,它们的 hashCode() 返回值相同,并且 equals() 方法返回 true,所以在 HashMap 中被视为同一个键。

如果没有正确重写 hashCode()equals() 方法,可能会导致 HashMap 无法正确存储和检索键值对,出现逻辑错误或性能问题。例如,如果两个相等的对象 hashCode() 返回值不同,它们可能会被存储在哈希表的不同位置,导致无法通过 get() 方法正确获取对应的值。

LinkedHashMap

基于哈希表+双链表
底层逻辑:

public class MapTest{
  public static void main(String[] args){
   Map<String,Integer> map=new HashMap<>();  //无序
   Map<String,Integer> map=new 	LinkedHashMap<>();//有序
   
   map.put("手表"100);
   map.put(null,null);
   System.out.println(map);
}
}

TreeMap

按照键的大小排序
基于哈希表+红黑树

  • TreeMap 是 Java 中一个基于红黑树实现的有序 Map 接口实现类。它会对存储的键进行排序,这样在遍历 TreeMap 时,键值对会按照键的排序顺序呈现。
    在这里插入图片描述
    实现自定义排序
    实现 Comparable 接口自定义排序
    • 步骤说明
      • 首先定义一个类作为 TreeMap 的键类型,这个类要实现 Comparable 接口。Comparable 接口只有一个方法 compareTo,我们在这个方法里定义排序规则。
      • 例如下面的代码,定义了一个 CustomKey 类作为键类型:
import java.util.TreeMap;

// 自定义键类型实现Comparable接口
class CustomKey implements Comparable<CustomKey> {
    private int value;

    public CustomKey(int value) {
        this.value = value;
    }

    @Override
    public int compareTo(CustomKey other) {
        // 这里定义按照value从小到大排序
        // 如果 this.value 小于 other.value,返回 -1,表示 this 在 other 之前
        // 如果 this.value 等于 other.value,返回 0,表示两者相等
        // 如果 this.value 大于 other.value,返回 1,表示 this 在 other 之后
        return Integer.compare(this.value, other.value);
    }

    @Override
    public String toString() {
        return String.valueOf(value);
    }
}

public class TreeMapCustomSort1 {
    public static void main(String[] args) {
        TreeMap<CustomKey, String> treeMap = new TreeMap<>();
        treeMap.put(new CustomKey(3), "Value for 3");
        treeMap.put(new CustomKey(1), "Value for 1");
        treeMap.put(new CustomKey(2), "Value for 2");

        for (Map.Entry<CustomKey, String> entry : treeMap.entrySet()) {
            System.out.println(entry.getKey() + " : " + entry.getValue());
        }
    }
}
  • 运行结果分析
    • 运行这段代码,TreeMap 会按照 CustomKey 类中 compareTo 方法定义的规则对键进行排序。因为 compareTo 方法定义的是按照 value 从小到大排序,所以输出结果会是键值对按照 1 : Value for 12 : Value for 23 : Value for 3 的顺序显示。

使用 Comparator 接口自定义排序

  • 步骤说明
    • 定义一个实现 Comparator 接口的类。Comparator 接口有一个 compare 方法,我们在这个方法里定义排序规则。
    • 然后在创建 TreeMap 时,把这个实现 Comparator 接口的类的实例作为参数传递给 TreeMap 的构造函数。
    • 例如下面的代码:
import java.util.Comparator;
import java.util.TreeMap;

class CustomComparator implements Comparator<Integer> {
    @Override
    public int compare(Integer a, Integer b) {
        // 这里定义按照值从大到小排序
        // 如果 a 小于 b,返回 1,表示 a 在 b 之后
        // 如果 a 等于 b,返回 0,表示两者相等
        // 如果 a 大于 b,返回 -1,表示 a 在 b 之前
        return b - a;
    }
}

public class TreeMapCustomSort2 {
    public static void main(String[] args) {
        TreeMap<Integer, String> treeMap = new TreeMap<>(new CustomComparator());
        treeMap.put(3, "Value for 3");
        treeMap.put(1, "Value for 1");
        treeMap.put(2, "Value for 2");

        for (Map.Entry<Integer, String> entry : treeMap.entrySet()) {
            System.out.println(entry.getKey() + " : " + entry.getValue());
        }
    }
}
  • 运行结果分析
    • 运行这段代码,TreeMap 会按照 CustomComparator 类中 compare 方法定义的规则对键进行排序。由于 compare 方法定义的是按照 Integer 值从大到小排序,所以输出结果会是键值对按照 3 : Value for 32 : Value for 21 : Value for 1 的顺序显示。

总结:实现 Comparable 接口是让键类型自身具备排序能力,而使用 Comparator 接口是在外部定义排序规则,并且可以为同一个键类型定义多种不同的排序规则,这两种方法都能实现对 TreeMap 排序方式的自定义。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值