Map是什么
Java中的map是一种键-值对(key-value)集合(容器)Map 集合中的每一个元素都包含一个键对象和一个值对象。其中,键对象不允许重复,而值对象可以重复,并且值对象还可以是 Map 类型的,就像数组中的元素还可以是数组一样。
Map 接口主要有两个实现类:HashMap 类和 TreeMap 类。其中,HashMap 类按哈希算法来存取键对象,而 TreeMap 类可以对键对象进行排序。
Map的特点
Map :
键值对的集合| 映射的集合
键值对的key的唯一的,去重的,无序的 — > Set
键值对的value,可重复,无序的 — > Collection
一个key只能对应一个value
去重 : key相同value覆盖
Map 接口中提供的常用方法如下:
方法名 | 作用 |
---|---|
V get(Object key) | 返回 Map 集合中指定键对象所对应的值。V 表示值的数据类型 |
V put(K key, V value) | 向 Map 集合中添加键-值对,返回 key 以前对应的 value,如果没有, 则返回 null |
V remove(Object key) | 从 Map 集合中删除 key 对应的键-值对,返回 key 对应的 value,如果没有,则返回null |
Set entrySet() | 返回 Map 集合中所有键-值对的 Set 集合,此 Set 集合中元素的数据类型为 Map.Entry |
返回 Map 集合中所有键-值对的 Set 集合,此 Set 集合中元素的数据类型为 Map.Entry | 返回 Map 集合中所有键对象的 Set 集合 |
Map 遍历方式 :
- Collection values() 返回此映射中包含的值的Collection视图。
- Set keySet() 返回此映射中包含的键的Set视图。
- Set<Map.Entry<K,V>> entrySet() 返回此映射中包含的映射的Set视图。
System.out.println("----------------values()----------------");
Collection<String> values = map.values();
for(String str:values){
System.out.println(str);
}
System.out.println("-------------keySet()-----------------");
Set<Integer> keys = map.keySet();
Iterator<Integer> it = keys.iterator();
while(it.hasNext()){
Integer key = it.next();
String value = map.get(key);
System.out.println(key+"--->"+value);
}
System.out.println("-------------entrySet()-----------------");
Set<Map.Entry<Integer,String>> entrys = map.entrySet();
for (Map.Entry<Integer,String> entry:entrys){
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
TreeMap
TreeMap :
存储键值对数据,key是唯一的,去重的,无序的(存放的顺序与内部真实存储的顺序不保证一致)
-
底层结构 : 红黑树
-
特点 : 根据key做自动升序排序(比较器)
-
去重与升序排序 : 根据key比较规则做去重与排序
-
新增方法 : 新增了一些与比较大小相关的方法
注意 : Map集合下所有的实现类,去重|排序都是根据key实现的,与value无关存储键值对数据,key是唯一的,去重的,无序的(存放的顺序与内部真实存储的顺序不保证一致)
匿名内部类
TreeMap<Person,Double> map = new TreeMap<>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge()-o2.getAge();
}
});
lambda
TreeMap<Person,Double> map = new TreeMap<>((o1, o2)->{
return o1.getAge()-o2.getAge();
});
HashMap :
基于哈希表的Map接口的实现。 此实现提供了所有可选的映射操作,并允许null值和null键。
- 底层结构 : 哈希表(jdk8之前:数组+链表 jdk8及之后:数组+链表+红黑树)
- 特点 : 查询增删效率高,去重根据键值对的key实现
- 应用场景 : 适合应用在存储多个键值对数据的情况下,查询增删效率高应用场景
- 新增方法 : 无新增方法
- 遍历方式 : 1)values() 2)keySet() 3)entrySet()
初始容量 :默认static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; --> 可以通过构造器指定
加载因子 : 默认static final float DEFAULT_LOAD_FACTOR = 0.75f; --> 可以通过构造器指定
扩容阈值 : threshold 存储数据的个数size > 数组的容量*加载因子
扩容机制 : 每次扩容原容量的2倍 newCap = oldCap << 1
哈希表中存储键值对的个数 : size
hashmap存储过程
存储过程 :
-
调用put方法添加键值对key,value数据
-
根据key计算hash值
int hash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
-
调用putVal(hash,key,value)实现存储键值对数据
-
执行putVal方法,第一步就判断哈希表底层的节点数组是否
==
null,或者底层数组的长度==
0,如果是证明这是第一次添加,底层没有数组或者没有带有容量的数组,先调用resize()方法实现创建新数组if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length;
-
根据key的hash值计算位桶索引int index = (n - 1) & hash,判断table[index]
==
null是否存在单向链表的首节点
if ((p = tab[i = (n - 1) & hash])==
null) -
如果=null,证明不存在单向链表,直接创建新节点,存储这一次要添加的键值对数据,直接放在数组table[index]作为单向链表的首节点
tab[i] = newNode(hash, key, value, null); --> new Node<>(hash, key, value, next) -
如果!=null,证明存在单向链表的首节点,遍历这个链表,判断链表中每一个节点的存储的key与这一次要添加的键值对的key比较是否相等,
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
如果相同value覆盖
V oldValue = e.value;
e.value = value;
return oldValue;
如果不相同创建新节点,挂在原单向链表的最后
p.next = newNode(hash, key, value, null); -
如果以上的步骤中执行到了new Node()创建新节点放入哈希表中,数据个数+1,判断是否>扩容阈值threshold,如果满足,就调用resize方法实现扩容
if (++size > threshold)
resize();
注意 :
哈希表底层节点数组的长度为2的整数次幂
当单向链表的节点个数>=8(static final int TREEIFY_THRESHOLD = 8;),并且同时哈希表的节点数组的容量>=64(static final int MIN_TREEIFY_CAPACITY = 64;),就把单向链表优化成红黑树
但是如果数组的容量<64,就调用resize()实现数组的扩容