一、Map接口
映射(map)是一个存储键、值对的对象。给定一个键,可以查询得到他的值,键和值都是对象(基础类型不可以)。
键必须是唯一的,值是可以重复的。
有些映射可以接收null键和null值,而有的不行
下面的接口支持映射:
接口 | 描述 |
Map | 映射唯一关键字给值 |
Map.Entry | 映射中的元素(关键字、值对),这是Map的内部类 |
SortedMap | 扩展Map一遍按关键字升序保持 |
Map接口定义的方法:
V put(K key, K value) 添加键值对(如果已存在键,则返回历史值,否则返回null)
void clear() 清空map
boolean containsKey(Object key) 判断map中是否包含某个 key
boolean containsValue(Object value) 判断map中是否包含某个 value
V get(Object key) 通过key获取value
boolean isEmpty() 判断map中元素个数是否为0
Set keySet() 获取map集合所有的key
V remove(Object key) 通过key删除键值对
int size() 获取map中所有键值对个数
Collections values() 获取map集合所有的value
Set<Map.Entry<K,V>> entrySet() 将map集合转换成Set集合
二、HashMap
HashMap类时基于哈希表的map接口的实现,并允许使用null键和null值
构造方法:
HashMap() | 默认构造方法。数组大小为16. |
HashMap(Map m) | 构造方法。并使用给定map进行赋值 |
HashMap(int capacity) | 构造方法。给定数组的初始大小。 |
HashMap(int capacity,float fillRatio) | 构造方法。给定数组的初始大小和扩容因子。 |
散列映射不保证它的元素的顺序。元素加入散列映射的顺序并不一定是他们被迭代读出的顺序。
自定义类作为HashMap的key的时候,规定要重写类的hashCode() 和 equals() 两个方法。
HashMap通过key的hashCode() 和 equals() 两个方法判断key是否相同,如果key的hashCode()方法相同且equals()也相等,那么HashMap就认为key是相等的。
@AllArgsConstructor
static class Student{
private String name;
private Integer age;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(name, student.name) &&
Objects.equals(age, student.age);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
@Test
public void hashMapTest(){
Student stu1 = new Student("唐三",25);
Student stu2 = new Student("唐昊",70);
Student stu3 = new Student("唐三",25);
Map<Student, String> studentMap = new HashMap<>();
studentMap.put(stu1, "唐三");
studentMap.put(stu2, "唐昊");
studentMap.put(stu3, "唐三2");
System.out.println(studentMap);
}
输出结果:
{Student{name='唐昊', age=70}=唐昊, Student{name='唐三', age=25}=唐三2}
上面的实例中,如果Student类没有重写hashCode() 和 equals() 两个方法,那么就会输出3个元素,因为Object默认的equals()方法对比的是对象的地址,而两个对象的地址是完全不一样的。
三、TreeMap
TreeMap类通过使用红黑树实现Map接口
TreeMap提供按排序顺序存储键值对的有效手段,同时允许快速检索(红黑树查询速度非常快)。
不像散列映射,树映射保证它的元素按关键字升序排序
TreeMap 的构造方法:
TreeMap() | 默认构造方法 |
TreeMap(Comparator comp) | 传入自定义的Comparator |
TreeMap(Map m) | |
TreeMap(SortedMap sm) | |
TreeMap实现SortedMap并且扩展AbstractMap,它本身并没有定义其他方法
TreeMap使用Comparator的compare方法(或者key所属类型实现Comparable接口的compareTo方法)判断key是否相等,key是否相等与hashCode() 和 equals() 两个方法没有任何关系,如果compare()或者compareTo()方法返回0就表示key是相等的。
在创建TreeMap的时候,要么传入Comparator的实现类,要么key的所属类实现Comparable的compareTo方法,二者必须选择一个;且如果二者同时满足,会选择传入的Comparator的实现类进行key的比较。
Comparator#compare:
Comparable#compareTo:
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
示例1:
@Test
public void TreeMapTest(){
Student stu1 = new Student("唐三",25);
Student stu2 = new Student("唐昊",70);
Student stu3 = new Student("唐三",25);
Map<Student, String> studentMap = new TreeMap<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
return 0;
}
});
studentMap.put(stu1, "唐三");
studentMap.put(stu2, "唐昊");
studentMap.put(stu3, "唐三2");
System.out.println(studentMap);
}
输出结果:
{Student{name='唐三', age=25}=唐三2}
注意:上面实例中,Comparator的compare方法虽然实现了,但是没有任何逻辑,直接返回0,所以在put的时候,只有第一个stu1作为key可以存入,剩余的stu2和stu3被认为是和stu1相等的key,所以只是进行了值的替换,而没有新的键值对存储。
修改示例1,示例2:
@Test
public void TreeMapTest(){
Student stu1 = new Student("唐三",25);
Student stu2 = new Student("唐昊",70);
Student stu3 = new Student("唐三",25);
Map<Student, String> studentMap = new TreeMap<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
return s1.getName().compareTo(s2.getName());
}
});
studentMap.put(stu1, "唐三");
studentMap.put(stu2, "唐昊");
studentMap.put(stu3, "唐三2");
System.out.println(studentMap);
}
输出结果:
{Student{name='唐三', age=25}=唐三2, Student{name='唐昊', age=70}=唐昊}
上面示例中stu1和stu2作为key存入了映射中,stu3没有作为key被存入,因为stu3被认为和stu1是相等的key,所以只进行了值的替换。key比较相等时会替换当前值。
示例3:Student类实现Comparable接口
@Getter
@AllArgsConstructor
static class Student implements Comparable<Student>{
private String name;
private Integer age;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(name, student.name) &&
Objects.equals(age, student.age);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public int compareTo(Student o) {
return this.getName().compareTo(o.getName());
}
}
@Test
public void TreeMapTest(){
Student stu1 = new Student("唐三",25);
Student stu2 = new Student("唐昊",70);
Student stu3 = new Student("唐三",25);
Map<Student, String> studentMap = new TreeMap<>();
studentMap.put(stu1, "唐三");
studentMap.put(stu2, "唐昊");
studentMap.put(stu3, "唐三2");
System.out.println(studentMap);
}
输出结果:
{Student{name='唐三', age=25}=唐三2, Student{name='唐昊', age=70}=唐昊}
三、LinkedHashMap
LinkedHashMap 继承自 HashMap,在 HashMap 基础上,通过维护一条双向链表,解决了 HashMap 不能随时保持遍历顺序和插入顺序一致的问题。在一些场景下,该特性很有用,比如缓存。
主要特点:可以保持存入的顺序和遍历的顺序一致(HashMap的遍历顺序不可预知,且会在put新数据的时候发生变动),其余特性与HashMap是一样的。
@Test
public void linkedHashMapTest(){
Student stu1 = new Student("唐三",25);
Student stu2 = new Student("唐昊",70);
Student stu3 = new Student("唐三",25);
Student stu4 = new Student("比比东",50);
Student stu5 = new Student("波塞西",200);
Map<Student, String> studentMap = new LinkedHashMap<>();
studentMap.put(stu1, "唐三");
studentMap.put(stu2, "唐昊");
studentMap.put(stu3, "唐三2");
studentMap.put(stu4, "比比东");
studentMap.put(stu5, "波塞西");
for(Student stu : studentMap.keySet()){
System.out.println(stu + "----------" + studentMap.get(stu));
}
}
输出结果:
Student{name='唐三', age=25}----------唐三2
Student{name='唐昊', age=70}----------唐昊
Student{name='比比东', age=50}----------比比东
Student{name='波塞西', age=200}----------波塞西