一、概念
在Java中,Map 是一种用于存储键值对的数据结构。它提供了一种通过键来快速查找和访问值的方式。Map 接口有多个实现类,在常用的实现类中包括 HashMap、TreeMap 和 LinkedHashMap。
Map集合的特点:
(1)Map 是由键值对(Key-Value)组成的数据结构。每个键(Key)都是唯一的,而值(Value)可以重复 。如果键重复了,那么后面的值会替换前面的值。
(2)Map集合中的元素,Key和Value的数据类型可以相同,也可以不同,且Map 的键和值都可以为 null。
(3)Map里的Key和Value是一一对应的,并且Map 中的键值对没有固定的顺序。
二、Map中的常用方法
(1)public V put(K key, V value):指定的键和值添加到Map集合中,返回值是V。
public static void main(String[] args) {
/* public V put (K key,V value) 返回值为:V
* 如果要存储的键值对,key不重复返回值V是null
* 如果要存储的键值对,key重复返回值V是被替换的value值
*/
Map<String, String> map=new HashMap<>();
map.put("学生1","张三");
String v=map.put("学生2","李四");
String v1=map.put("学生1","张三");
System.out.println(v+","+v1);
System.out.println(map);//替换了前面的值
}
(2) public V remove(Object key) : 把指定的键所对应的键值对元素在Map集合中删除,返回被删除的元素的值。
public static void main(String[] args) {
/* public V remove(Object key) 返回值:V
* 如果key存在,返回被删除的值
* 如果key不存在,返回null
* */
Map<String,Integer> map = new HashMap<>();
map.put("张三",1001);
map.put("李四",1002);
map.put("王伟",1003);
map.put("老六",1004);
System.out.println(map);
Integer t=map.remove("王伟");
Integer t1=map.remove("老九");
System.out.println("t:"+t+",t1:"+t1);
System.out.println(map);
}
(3) public V get(Object key):根据指定的键,在Map集合中获取对应的值。
public static void main(String[] args) {
/* public V remove (Object key):
* 如果key存在,返回对应的value值,如果key不存在,返回null
*/
Map<Integer, String> map = new HashMap<>();
map.put(2023,"七月");
map.put(2024,"八月");
map.put(2025,"九月");
map.put(2026,"十月");
String v1=map.get(2024);
String v2=map.get(2021);
System.out.println("v1:"+v1+",v2:"+v2);
}
(4)public boolean containsKey(Object key) : 判断该集合中是否包含有此键。
public static void main(String[] args) {
/* boolean containsKey (Object key):
* 包含返回true,不包含返回false;
*/
Map<Integer, String> map = new HashMap<>();
map.put(2023,"七月");
map.put(2024,"八月");
map.put(2025,"九月");
map.put(2026,"十月");
boolean v1=map.containsKey(2023);
boolean v2=map.containsKey(2017);
System.out.println("v1:"+v1+",v2:"+v2);
}
(5)public boolean containsValue(Object value): 判断该集合中是否包含有此值。(参上)
(6)public Set<K> keySet() :获取Map集合中所有的键,存储到Set集合中。
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(2023,"七月");
map.put(2026,"八月");
map.put(2025,"九月");
map.put(2021,"十月");
Set<Integer> set = map.keySet();//使用keySet()方法获取所有的键并存储到Set集合中。
// 遍历输出所有的键
for(Integer a : set) {
System.out.println(a);
}
}
(7)public Collection<V> values() :获取Map集合中所有的值,存储到Collection集合中。
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("2023","七月");
map.put("2026","八月");
map.put("2025","九月");
map.put("2021","十月");
// 使用 values() 方法获取所有的值并存入到Collection集合中。
Collection<String> value=map.values();
for(String v:value){
System.out.println(v);
}
}
(8)public Set<Map.Entry<K,V>> entrySet() :获取到Map集合中所有的键值对,并将它们存储到一个 Set 集合中。
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("2023","七月");
map.put("2026","八月");
map.put("2025","九月");
map.put("2021","十月");
// 使用 entrySet() 方法获取所有的键值对,存入Set集合中
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println("K:"+key+",V:"+value);
}
}
(9)public static interface Map.Entry<K,V> :表示一个键值对的对象 ,可以把键值对包装成一个对象,该对象类型就是Entry类型。
public static void main(String[] args) {
// 创建一个键值对对象
Map.Entry<String, Integer> entry = new MyEntry<>("张三", 1001);
// 使用 Entry 对象获取键和值
String key = entry.getKey();
Integer value = entry.getValue();
// 输出键和值
System.out.println("Key: " + key);
System.out.println("Value: " + value);
}
static class MyEntry<K, V> implements Map.Entry<K, V> {
private K key;
private V value;
public MyEntry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
}
三、Map集合的遍历方式
(1)键找值法(键集遍历)
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("张三",1001);
map.put("李四",1002);
map.put("王伟",1003);
map.put("老六",1004);
for(String key : map.keySet()) {
Integer value =map.get(key);
System.out.println("K:"+key+",V:"+value);
}
}
(2)键值对循环遍历
public static void main(String[] args){
Map<String, Integer> map = new HashMap<>();
map.put("张三",1001);
map.put("李四",1002);
map.put("王伟",1003);
map.put("老六",1004);
Set<Map.Entry<String, Integer>> entries = map.entrySet();
// 使用foreach循环遍历键值对
for(Map.Entry<String, Integer> entry : entries) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println("V:"+key+",K:"+value);
}
}
(3)迭代器遍历
public static void main(String[] args){
Map<String, Integer> map = new HashMap<>();
map.put("张三",1001);
map.put("李四",1002);
map.put("王伟",1003);
map.put("老六",1004);
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Integer> entry = iterator.next();
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println("K:"+key+",V:"+value);
}
}
四、Map接口常用的实现类
(1)HashMap
HashMap是基于哈希表实现的Map,提供快速的插入、删除和查找操作。它不保证元素的顺序,允许使用null键和null值。
4.1.1、特点:
<1>哈希表实现:HashMap内部使用哈希表数据结构,通过计算键的哈希码来确定元素在哈希表中的位置,从而实现快速的插入、删除和查找操作。平均情况下,这些操作的时间复杂度为O(1)。
<2>键值对存储:HashMap存储的数据是键值对(Key-Value)形式,每个键都是唯一的,可以用来查找对应的值。它允许使用null作为键,也允许使用null作为值。
<3>无序性:HashMap不保证元素的顺序。即使插入的顺序是固定的,遍历时得到的元素顺序也不一定相同。如果需要有序的Map,可以使用TreeMap或LinkedHashMap。\
<4>非线程安全:HashMap是非线程安全的,如果多个线程同时操作同一个HashMap对象,可能会导致不可预期的结果。如果需要在并发环境中使用Map,可以考虑使用ConcurrentHashMap。
<5>扩容与负载因子:HashMap在内部数组的填充程度达到一定比例(负载因子)后会触发扩容,以保持较低的哈希冲突率。扩容涉及到重新计算哈希码和重新分配元素存储位置,可能会导致性能略微下降。可以在创建HashMap时指定初始容量和负载因子,以适应不同的场景需求。
4.1.2、HashMap存储自定义类型键值:
HashMap存储自定义类型键值,Map集合保证key是唯一的:作为key的元素,必须重写hashCode方法和equals方法,以保证key唯一。
class Personbest{
private String name;
private int age;
public Personbest(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Personbest [name=" + name + ", age=" + age + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Personbest other = (Personbest) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
public class Test18 {
public static void main(String[] args){
HashMap<Personbest,String> map = new HashMap<>();
Personbest best1 = new Personbest("张三",18);
Personbest best2 = new Personbest("李四",20);
Personbest best3 = new Personbest("王伟",8);
Personbest best4 = new Personbest("老六",66);
map.put(best1,"十七");
map.put(best2,"七七");
map.put(best3,"五五");
map.put(best4,"四六");
String v1=map.get(best4);
String v2=map.get(best3);
System.out.println(v1);
System.out.println(v2);
System.out.println(map);
System.out.println(map.size());
}
}
在上述示例中,Person类正确实现了hashCode()和equals()方法,允许将其作为HashMap的键。通过相同属性的键对象可以成功获取对应的值。
注意:当自定义类型的属性发生变化时,可能导致哈希码和相等性判断发生改变,如果已经将此对象作为HashMap的键使用,可能会导致无法正确地获取值。因此,在将对象用作HashMap的键之前,应尽量确保其属性不可变或不会被修改。
(2)LinkedHashMap
LinkedHashMap继承自HashMap,通过双向链表维护了元素的插入顺序或访问顺序。它保留了元素的插入顺序或访问顺序,并且支持快速插入、删除和查找操作。
特点:
<1>有序性:LinkedHashMap保持元素的插入顺序或访问顺序(可以通过构造函数参数来选择)。即使在遍历时,得到的元素顺序也是按照插入或访问的顺序。
<2>哈希表与双向链表结合:LinkedHashMap内部通过哈希表和双向链表两种数据结构相结合来实现。哈希表用于快速定位元素,双向链表用于维护元素的插入顺序或访问顺序。
<3>高效性能:LinkedHashMap对于插入、删除和查找操作具有快速的性能,平均情况下的时间复杂度为O(1)。同时,由于使用了双向链表来维护顺序,对于迭代遍历操作也能够快速地获取有序的元素。
<4>非线程安全:与HashMap一样,LinkedHashMap也是非线程安全的。如果在并发环境中需要使用LinkedHashMap,可以考虑使用ConcurrentLinkedHashMap。
<5>可指定访问顺序:LinkedHashMap提供了两种顺序模式,即插入顺序和访问顺序。在插入顺序模式下,元素的顺序按照它们被添加的顺序进行排列。在访问顺序模式下,元素的顺序会根据被访问的顺序进行调整,每次访问一个元素时,该元素会被移动到链表的尾部。