前面我们介绍了Collection集合接口,Collection集合的特点是每次进行单个对象的保存,如果现在要进行一对对象(偶对象)的保存就只能使用Map集合来完成,即Map集合中会一次性保存两个对象,且这两个对象的关系:key=value结构。这种结构最大的特点是可以通过key找到对应的value内容。
Map接口概述
首先来观察Map接口定义:
public interface Map<K,V>
在Map接口中有如下常用方法:
Map本身是一个接口,要使用Map需要通过子类进行对象实例化。Map接口的常用子类有如下四个: HashMap、Hashtable、TreeMap、ConcurrentHashMap。
HashMap子类(常用)
HashMap是使用Map集合中最为常用的子类。
例:Map基本操作
public class Test {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "hello");
//观察插入重复的key值时
map.put(1, "java");
map.put(3, "world");
System.out.println(map);
//根据key值取得value
System.out.println(map.get(3));
//观察找一个不存在的key值
System.out.println(map.get(10));
}
}
例:取得Map中所有key信息
剖析HashMap原理
在数据量小的(JDK1.8后阈值为8)时候HashMap是按照链表的模式存储的;当数据量变大之后为了进行快速查找,会将这个链表变为红黑树(均衡二叉树)来进行数据保存,用hash码作为数据定位。
HashMap的特点:
1.底层实现的是链表数组,JDK1.8后又加了红黑树。
2.实现了Map全部的方法。
3.key用Set存放,key值不能重复,key对应的类需要重写hashCode和equals方法。
4.允许空键和空值(但空键只有一个,且放在第一位,value的值可以任意重复或者任意为空)。
5.元素是无序的,而且顺序会不定时改变。
6.插入、获取的时间复杂度基本是O(1)。
7、遍历整个Map需要的时间与桶(数组)的长度成正比(因此初始化时Hash Map的容量不宜太大)。
8.Hash Map为异步处理,非线程安全。
剖析源码
这里请参照大神解析:
HashMap主要特点和关键方法源码解读
https://blog.csdn.net/u011240877/article/details/53351188
HashMap在JDK1.8后新增的红黑树结构
https://blog.csdn.net/u011240877/article/details/53358305
Hashtable子类
JDK1.0提供有三大主要类:Vector、Enumeration、Hashtable。Hashtable是最早实现这种二院偶对象数据结构,后期的设计也让其与Vector一样多实现了Map接口而已。
例:观察Hashtable
public class Test{
public static void main(String[] args) {
Map<Integer, String> map = new Hashtable<>();
map.put(1, "hello");
//观察不能插入重复的key
map.put(1, "java");
map.put(2, "hello");
map.put(3, "world");
System.out.println(map);
}
}
HashMap与Hashtable的区别
ConcurrentHashMap子类
ConcurrentHashMap的特点 = Hashtable的线程安全性 + HashMap的高性能,在使用ConcurrentHashMap处理的时候,既可以保证多个线程更新数据的同步,又可以保证很高效的查询速度。ConcurrentHashMap不允许key为null。
首先来看ConcurrentHashMap的定义
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V> implements ConcurrentMap<K,V>,Serializable
例:使用ConcurrentHashMap
public class Test{
public static void main(String[] args) {
Map<Integer, String> map = new ConcurrentHashMap<>();
map.put(1, "hello");
//观察不能插入重复的key
map.put(1, "java");
map.put(2, "hello");
map.put(3, "world");
System.out.println(map);
}
}
使用起来很简单,因为ConcurrentHashMap也是Map接口的子类。下面我们来分析一下ConcurrentHashMap的工作原理。
如果采用一定的算法,将保存的大量数据平均分在不同的桶(数据区域),这样在进行数据查找的时候就可以避免全局的数据扫描。
例:数据分桶
public class Test{
public static void main(String[] args) {
for(int i = 0; i < 10; i++) {
Random random = new Random();
int temp = random.nextInt(5555);
int result = temp % 3;
switch(result) {
case 0:
System.out.println("第0桶: " + temp);
break;
case 1:
System.out.println("第1桶: " + temp);
break;
case 2:
System.out.println("第2桶: " + temp);
break;
}
}
}
}
采用了分桶之后每一个数据中必须有一个明确的分桶标记,我们一般采用hashCode()。
数据更新的时候只锁更新的对应区域,其他区域的访问不受影响。
Map集合使用Iterator输出
Map接口与Collection接口不同,Collection接口有iterator()方法可以很方便的取得Iterator对象来输出,而Map接口本身并没有此方法。下面我们首先来观察Collection接口与Map接口数据保存的区别:
在Map接口里面有一个重要的方法,将Map集合转为Set集合:
public Set<Map.Entry<K, V>> entrySet();
Map要想调用Iterator接口输出,走的是一个间接使用的模式,如下图:
例:通过Iterator输出Map集合
public class Test{
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "hello");
map.put(2, "java");
map.put(3, "hello");
map.put(4, "world");
//将Map集合转为Set集合
Set<Map.Entry<Integer, String>> set = map.entrySet();
//获取Iterator对象
Iterator<Map.Entry<Integer, String>> iterator = set.iterator();
//输出
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
关于Map中key的说明
在之前使用Map集合的时候使用的都是系统类作为key(Integer,String等)。实际上用户也可采用自定义类作为key。
这个时候一定要记得覆写Object类的hashCode()与equals()方法。
例:观察自定义类作为Key,系统类作为Value的情况(覆写)
class Person{
private Integer age;
private String name;
public Person(Integer age, String name) {
this.age = age;
this.name = name;
}
public String toString() {
return "Person{ " + "age = " + age +
"name = " + name + " }";
}
public boolean equals(Object o) {
if(this == o) return true;
if(o == null || this.getClass() != o.getClass())
return false;
Person person = (Person)o;
return Objects.equals(this.name, person.name)
&& Objects.equals(this.age, person.age);
}
public int hashCode() {
return Objects.hash(age, name);
}
public Integer getAge() {
return this.age;
}
public String getName() {
return this.name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
}
public class Test{
public static void main(String[] args) {
Map<Person, String> map = new HashMap<>();
map.put(new Person(15, "zhangsan"), null);
System.out.println(map);
}
}
实际开发来讲,我们一般都是采用系统类(String,Integer等)作为Key值,这些系统类都帮助用户覆写好了hashCode()与equals()方法。
TreeMap子类
TreeMap是一个可以排序的Map子类,它是按照Key的内容排序的。
例:观察TreeMap的使用
public class Test{
public static void main(String[] args) {
Map<Integer, String> map = new TreeMap<>();
map.put(2, "c");
map.put(0, "a");
map.put(1,"b");
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区别。