Map集合
- Map是一个接口
- Map称为双列集合,格式: [key=value,…],一次需要存一对数据作为一个元素。
- key=value:一个键值对对象
- 键不能重复,值可以重复,一一对应。
集合体系的特点
- Map系列集合的特点都是由键决定的,值只是一个附属品,值是不做要求的
- HashMap:无序,不重复,无索引;
- LinkedHashMap;有序,不重复,无索引
- 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 MapTest{
public static void main(String[] args){
Map<String,Double> map=new HashMap<>();
map.put("张三",163.3);
map.put("李四",165.8);
map.put("王玉",169.5);
//获取集合所有键
Set<String> 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 MapTest{
public static void main(String[] args){
Map<String,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 MapTest{
public static void main(String[] args){
Map<String,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 MapTest{
public static void main(String[] args){
Map<String,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()
方法。由于 key1
和 key2
的 id
相同,它们的 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 1
、2 : Value for 2
、3 : 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 3
、2 : Value for 2
、1 : Value for 1
的顺序显示。
- 运行这段代码,
总结:实现 Comparable
接口是让键类型自身具备排序能力,而使用 Comparator
接口是在外部定义排序规则,并且可以为同一个键类型定义多种不同的排序规则,这两种方法都能实现对 TreeMap
排序方式的自定义。