文章目录
Map
Map(映射)是一种用于存储键值对的数据结构。在 Java 中,Map 是一个接口,它定义了一组方法来操作和处理键值对。
Map 接口的常见实现类包括 HashMap
、TreeMap
、LinkedHashMap
等。
1.以下是一些常用的 Map 接口的方法:
1.put(key, value)
: 将指定的键值对存储到 Map 中。
2.get(key)
: 根据键获取对应的值。
3.containsKey(key)
: 判断 Map 中是否包含指定的键。
4.containsValue(value)
: 判断 Map 中是否包含指定的值。
5.remove(key)
: 根据键移除对应的键值对。
6.size()
: 返回 Map 中键值对的数量。
7.keySet()
: 返回包含所有键的集合。
8.values()
: 返回包含所有值的集合。
9.entrySet()
: 返回包含所有键值对的集合。
2.Map 的特点包括:
- 键不允许重复,每个键只能对应一个值。
- 可以通过键快速查找值,具有高效的查找能力。
- 键和值可以是任意的对象,但在使用时需要正确实现 hashCode() 和 equals() 方法以确保准确的存取。
- 不保证元素的顺序,具体的顺序取决于具体的实现类。
使用 Map 可以方便地根据键来获取对应的值,常用于存储和检索映射关系的场景,如缓存、配置信息等。
3.HashMap
- 允许使用null键和null值,与HashSet一样,不保证映射的顺序。
- 所有的key构成的集合是Set:无序的、不可重复的。所以, key所在的类要重写:equals()和hashCode()
- 所有的value构成的集合是Collection:无序的、可以重复的。所以, value所在的类要重写: equals()
- 一个key-value构成一个entry
- 所有的entry构成的集合是Set:无序的、不可重复的
- HashMap 判断两个key 相等的标准是:两个 key 通过 equals() 方法返回 true,hashCode 值也相等。
- HashMap判断两个 value相等的标准是:两个 value 通过 equals() 方法返回 true。
3.1 JDK7以前的存储结构
JDK1.7前哈希桶里不会自动形成红黑树,如果出现哈希冲突,就会一直增加链表长度 。这样就会导致一个链表太长 遍历时间就会变长
3.2 JDK 8 存储结构
- DK1.8HashMap新变化
- HashMap map = new HashMap();//默认情况下,先不创建长度为16的数组
- 当首次调用map.put()时,再创建长度为16的数组
- 数组为Node类型,在jdk7中称为Entry类型
- 形成链表结构时,新添加的key-value对在链表的尾部(七上八下)
- 当数组指定索引位置的链表长度>8时,且map中的数组的长度> 64时,此索引位置上的所有key-value对使用红黑树进行存储。
源码中三个重要方法
- resize:扩容
- treeifyBin:树化
- untreeify:链化
3.3 HashMap中添加的元素分配哈希桶中的过程
1.添加的元素先通过hashcode获得一个哈希值
2.用add往里面添加的时候,会通过扰动函数
给这个元素赋予一个新的hashcode,这样可以降低哈希码冲突
3.通过新的hashcode
和哈希桶数量
(bucketCount)取余(%)来确定这个元素的哈希桶位置
4.将元素存储到对应的哈希桶里
3.4 HashMap的容量为什么必须是2的n次幂
HashMap
使用n-1 & hash
得到槽位地址,这个运算n必须是2的n次幂,结论当容量是2的n次幂的时候(16,32…)
hash % n = (n-1) & hash
,因为位运算性能高
如果初始容量不是2的n次幂,HashMap调用tableSizeFor自动转换成大于这个数最小的2的n次幂
4. LinkedHashMap
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
LinkedHashMap 是 Java 中的一种特殊类型的哈希表,它继承自 HashMap,同时保持了插入顺序。具体来说,LinkedHashMap 使用一个双向链表来维护键值对的顺序,这样可以保证迭代遍历时的顺序与插入顺序一致。
与普通的 HashMap 不同,LinkedHashMap 提供了以下特性:
- 迭代顺序:迭代 LinkedHashMap 时,元素的顺序与插入顺序相同。
- 访问顺序:可以通过构造函数或设置
accessOrder
参数为 true,使得LinkedHashMap
按照访问顺序进行排序。每次访问一个键值对,无论是读取还是写入操作,都会将该键值对移动到链表末尾,使其成为最近访问的元素。 - 有序性:LinkedHashMap 可以按照插入顺序或访问顺序排序,从而保证键值对的顺序性。
LinkedHashMap 是线程不安全的,如果需要在多线程环境下使用,请考虑使用线程安全的实现或进行适当的同步操作。
5. TreeMap
- TreeSet使用TreeMap实现,只是value使用静态空对象,只是用key实现TreeSet
- TreeMap存储 Key-Value 对时, 需要根据 key-value 对进行排序。TreeMap 可以保证所有的 Key-Value 对处于有序状态。
- TreeSet底层使用红黑树结构存储数据
- TreeMap 的 Key 的排序:
- 自然排序: TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
- 定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现Comparable 接口
- TreeMap判断两个key相等的标准:两个key通过compareTo()方法或者compare()方法返回0,1,-1
- 0:对象相等,添加失败
- -1:比对象小,添加到左边
- 1:比对象打,添加到右边
6. 自然排序
- 类实现Comparable接口,进行排序
- 若一个类实现了Comparable接口,就意味着该类支持排序。实现了Comparable接口的类的对象的列表或数组可以通过Collections.sort或Arrays.sort进行自动排序。
- 此外,实现此接口的对象可以用作有序映射中的键或有序集合中的集合,无需指定比较器
user类实现 Comparable
public class User implements Comparable {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//按照姓名从大到小排列,年龄从小到大排列
@Override
public int compareTo(Object o) {
if(o instanceof User){
User user = (User)o;
// return -this.name.compareTo(user.name);
// int compare = -this.name.compareTo(user.name);
int compare = this.name.compareTo(user.name);
if(compare != 0){
return compare;
}else{
return Integer.compare(this.age,user.age);
}
}else{
throw new RuntimeException("输入的类型不匹配");
}
}
}
测试类
public class TreeMapDemo1 {
public static void main(String[] args) {
User u1 = new User("ggg", 35);
User u2 = new User("ggg", 35);
User u3 = new User("ccc", 83);
User u4 = new User("ccc", 30);
User u5 = new User("ccc", 74);
User u6 = new User("eee", 39);
User u7 = new User("fff", 40);
User u8 = new User("aaa", 40);
User u9 = new User("bbb", 40);
TreeMap map = new TreeMap();
map.put(u1, "a");
map.put(u2, "a");
map.put(u3, "a");
map.put(u4, "a");
map.put(u5, "a");
map.put(u6, "a");
map.put(u7, "a");
map.put(u8, "a");
map.put(u9, "a");
for (Object o : map.entrySet()) {
System.out.println(o);
}
}
}
7. 定制排序
Comparator是比较接口,我们如果需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口),那么我们就可以建立一个“该类的比较器”来进行排序,这个“比较器”只需要实现Comparator接口即可。也就是说,我们可以通过实现Comparator来新建一个比较器,然后通过这个比较器对类进行排序
注意:
- 1、若一个类要实现Comparator接口:它一定要实现compare(T o1, T o2) 函数,但可以不实现 equals(Object obj) 函数
- 2、int compare(T o1, T o2) 是“比较o1和o2的大小”。返回“负数”,意味着“o1比o2小”;返回“零”,意味着“o1等于o2”;返回“正数”,意味着“o1大于o2”。
public class TreeMapDemo1 {
public static void main(String[] args) {
User u1 = new User("ggg", 35);
User u2 = new User("ggg", 35);
User u3 = new User("ccc", 83);
User u4 = new User("ccc", 30);
User u5 = new User("ccc", 74);
User u6 = new User("eee", 39);
User u7 = new User("fff", 40);
User u8 = new User("aaa", 40);
User u9 = new User("bbb", 40);
Comparator com = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User){
User u1 = (User) o1;
User u2 = (User) o2;
return -Integer.compare(u1.getAge(), u2.getAge());
// int compare = u1.getName().compareTo(u2.getName());
// if(compare != 0){
// return compare;
// }else{
// return Integer.compare(u1.getAge(), u2.getAge());
// }
}else{
throw new RuntimeException("输入的类型不匹配");
}
}
};
TreeMap map = new TreeMap(com);
map.put(u1, "a");
map.put(u2, "a");
map.put(u3, "a");
map.put(u4, "a");
map.put(u5, "a");
map.put(u6, "a");
map.put(u7, "a");
map.put(u8, "a");
map.put(u9, "a");
for (Object o : map.entrySet()) {
System.out.println(o);
}
}
}
user类
public class User {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
8. Comparable和Comparator区别比较
Comparable是排序接口,若一个类实现了Comparable接口,就意味着“该类支持排序”。而Comparator是比较器,我们若需要控制某个类的次序,可以建立一个“该类的比较器
”来进行排序。
Comparable相当于“内部比较器
”,而Comparator相当于“外部比较器”。
两种方法各有优劣, 用Comparable 简单, 只要实现Comparable 接口的对象直接就成为一个可以比较的对象,但是需要修改源代码。 用Comparator 的好处是不需要修改源代码, 而是另外实现一个比较器, 当某个自定义的对象需要作比较的时候,把比较器和对象一起传递过去就可以比大小了, 并且在Comparator 里面用户可以自己实现复杂的可以通用的逻辑,使其可以匹配一些比较简单的对象,那样就可以节省很多重复劳动了。