Collection集合的特点是每次进行单个对象的保存,如果现在要进行一对对象(偶对象)的保存就只能使用Map集合来完成,即Map集合中会一次性保存两个对象,且这两个对象的关系:key=value结构。这种结构最大的特点是可以通过key找到对应的value内容。
1、Map接口描述
首先来观察一下Map接口的定义:
public interface Map<K,V>
在Map接口中有如下常用方法:
Map本身是一个接口,要使用Map需要通过子类进行对象实例化。Map接口的常用子类有如下四个:HashMap、Hashtable、TreeMap、ConcurrentHashMap。
2、HashMap子类(常用)
HashMap 是一个采用哈希表实现的键值对集合,继承自 AbstractMap,实现了 Map 接口 。
HashMap 的特殊存储结构使得在获取指定元素前需要经过哈希运算,得到目标元素在哈希表中的位置,然后再进行少量比较即可得到元素,这使得 HashMap 的查找效率贼高。
当发生 哈希冲突(碰撞)的时候,HashMap 采用 拉链法 进行解决,因此 HashMap 的底层实现是 数组+链表,如下图 所示:
范例:Map的基本操作
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "hello");
// key重复
map.put(1, "Hello");
map.put(3, "world");
map.put(2, "World");
// key=null, value=null
map.put(null, null);
System.out.println(map);
// 根据key取得value
System.out.println(map.get(2));
// 查找不存在的key,返回null
System.out.println(map.get(4));
}
}
范例:取得Map中所有的key信息
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "hello");
// key重复
map.put(1, "Hello");
map.put(3, "world");
map.put(2, "World");
// 取得Map中所有的key信息
Set<Integer> set = map.keySet();
Iterator<Integer> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
3、HashMap 的特点
结合平时使用,可以了解到 HashMap 大概具有以下特点:
- 底层实现是 链表数组,JDK 8 后又加了 红黑树
- 实现了 Map 全部的方法
- key 用 Set 存放,所以想做到 key 不允许重复,key 对应的类需要重写 hashCode 和 equals 方法
- 允许空键和空值(但空键只有一个,且放在第一位,下面会介绍)
- 元素是无序的,而且顺序会不定时改变
- 插入、获取的时间复杂度基本是 O(1)(前提是有适当的哈希函数,让元素分布在均匀的位置)
- 遍历整个 Map 需要的时间与 桶(数组) 的长度成正比(因此初始化时 HashMap 的容量不宜太大)
- 两个关键因子:初始容量、加载因子
4、Hashtable子类
JDK1.0提供有三大主要类:Vector、Enumeration、Hashtable。Hashtable是最早实现这种二院偶对象数据结构,后期的设计也让其与Vector一样多实现了Map接口而已。
范例:Hashtable的使用
import java.util.Hashtable;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<Integer, String> map = new Hashtable<>();
map.put(1, "hello");
// key重复
map.put(1, "Hello");
map.put(3, "world");
map.put(2, "World");
// key、value 均不允许为 null
// map.put(null, null);
// map.put(null, "Java");
// map.put(4, null);
System.out.println(map);
}
}
HashMap与Hashtable的区别:
5、ConcurrentHashMap子类
ConcurrentHashMap 从 JDK1.5 之后引入,继承了AbstractMap,实现了 ConcurrentMap 接口。
ConcurrentHashMap的特点 = Hashtable的线程安全性+ HashMap的高性能,在使用ConcurrentHashMap处理的时候,既可以保证多个线程更新数据的同步,又可以保证很高效的查询速度。ConcurrentHashMap不允许key为null。
首先看一下 ConcurrentHashMap 的定义:
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
implements ConcurrentMap<K,V>, Serializable
范例:ConcurrentHashMap的使用
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Main {
public static void main(String[] args) {
Map<Integer, String> map = new ConcurrentHashMap<>();
map.put(1, "hello");
// key重复
map.put(1, "Hello");
map.put(3, "world");
map.put(2, "World");
System.out.println(map);
}
}
使用起来很简单,因为ConcurrentHashMap也是Map接口的子类。下面我们来分析一下ConcurrentHashMap的工作原理。
如果采用一定的算法,将保存的大量数据平均分在不同的桶(数据区域),这样在进行数据查找的时候就可以避免全局的数据扫描。
范例:数据分桶
import java.util.Random;
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
Random random = new Random();
int temp = random.nextInt(5555); // 产生一个5555以内的随机数
int result = temp % 3; // 分成3个桶
switch (result) {
case 0:
System.out.println("[第一桶]:" + temp);
break;
case 1:
System.out.println("[第二桶]:" + temp);
break;
case 2:
System.out.println("[第三桶]:" + temp);
break;
}
}).start();
}
}
}
采用了分桶之后每一个数据中必须有一个明确的分桶标记,我们一般采用hashCode()。
6、Map集合使用Iterator输出
Map接口与Collection接口不同,Collection接口有iterator()方法可以很方便的取得Iterator对象来输出,而Map接口本身并没有此方法。下面我们首先来观察Collection接口与Map接口数据保存的区别:
在Map接口里面有一个重要的方法,将Map集合转为Set集合:
public Set<Map.Entry<K, V>> entrySet();
Map要想调用Iterator接口输出,走的是一个间接使用的模式,如下图:
范例:通过Iterator输出Map集合
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "hello");
map.put(4, "xatu");
map.put(2, "world");
map.put(3, "Hello");
// 1.将Map集合转为Set集合
Set<Map.Entry<Integer, String>> set = map.entrySet();
// 2.获取Iterator对象
Iterator<Map.Entry<Integer, String>> iterator = set.iterator();
// 3.输出
while (iterator.hasNext()) {
// 4.取出每一个Map.Entry对象
Map.Entry<Integer, String> entry = iterator.next();
// 5.取得key和value
System.out.println(entry.getKey()+ " = " + entry.getValue());
}
}
}
以上就是Map使用Iterator输出的标准代码。
7、关于Map中Key的说明
在之前使用Map集合的时候使用的都是系统类作为key(Integer,String等)。实际上用户也可采用自定义类作为key。
这个时候一定要记得覆写Object类的hashCode()与equals()方法。
范例:观察自定义类作为Key,系统类作为Value的情况(未覆写)
import java.util.HashMap;
import java.util.Map;
class Person {
private Integer age;
private String name;
public Person(Integer age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Person{" + "age = " + age + ", name = '" + name + '\'' + '}';
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
Map<Person, String> map = new HashMap<>();
map.put(new Person(18, "张三"), "Peter");
System.out.println(map.get(new Person(18, "张三")));
}
}
此时的输出结果为null
范例:修改上面的代码,覆写hashCode()与equals()方法
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
class Person {
private Integer age;
private String name;
public Person(Integer age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Person{" + "age = " + age + ", name = '" + name + '\'' + '}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(age, person.age) &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(age, name);
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
Map<Person, String> map = new HashMap<>();
map.put(new Person(18, "张三"), "Peter");
System.out.println(map.get(new Person(18, "张三")));
}
}
实际开发来讲,我们一般都是采用系统类(String,Integer等)作为Key值,这些系统类都帮助用户覆写好了
hashCode()与equals()方法。
8、TreeMap子类
TreeMap是一个可以排序的Map子类,它是按照Key的内容排序的。
范例:观察 TreeMap 的使用
import java.util.Map;
import java.util.TreeMap;
public class Main {
public static void main(String[] args) {
Map<Integer, String> map = new TreeMap<>();
map.put(2, "C");
map.put(1, "c");
map.put(4, "b");
map.put(3, "A");
System.out.println(map);
}
}
这个时候的排序处理依然按照的是Comparable接口完成的。
结论:有Comparable出现的地方,判断数据就依靠compareTo()方法完成,不再需要equals()与hashCode()
Map集合小结:
1. Collection保存数据的目的一般用于输出(Iterator),Map保存数据的目的是为了根据key查找,找不到返
回null。
2. Map使用Iterator输出(Map.Entry的作用)。
3. HashMap数据结构一定要理解(链表与红黑树组成)以及HashMap与Hashtable区别。