Map接口和Collection接口没有任何关系,也是一个顶级接口。
public interface Map<K,V> {}
其中,k:key,V:value。
定义map对象最好指定key和value对应的类型,key和value要求必须是复杂类型,不能采用简单类型。
map接口中有一个内部接口Entry,每个Entry对象用于封装一对key/value,value允许修改,但是key不允许修改。
interface Entry<K,V> {
K getKey();
V getValue();
V setValue(V value);
}
Entry接口中还通过静态方法提供了一组比较器的默认实现,用于规范其中存放数据的规则。map中所有存储的数据都将封装为Entry对象,一个key/value对对应一个Entry对象
例:
Map<String,Integer> map=new HashMap<>(); //String--key,Integer--value
for(int i=0;i<10;i++){
map.put("key-"+i,i);
//按照key值存储数据,key要求必须唯一,如果出现key值重复,则后盖前;value没有任何特殊要求
}
//判断map中的key知否有一个叫做key-9的键
if(map.containsKey("key-9")){
Integer value=map.get("key-9");//按照key值获取key所对应的value值
System.out.println(value);
map.remove("key-9"); //删除对应的key-5的value值
}
System.out.println(map.size());//获取map中存储的key-value对的数量
Set<String> keys=map.keySet();//获取map中所有的key所组成的Set集合
Iterator<String> it=keys.iterator();
while(it.hasNext()){
String key=it.next();
Integer value=map.get(key); //根据key获取key所对应的value值
}
Collection<Integer> values=map.values(); //没有直接提供方法根据value获取对应的key,因为value值没有唯一性的约束
values.forEach(System.out::println);
//导入import java.util.Map.Entry;
Set<Entry<String,Integer>> sets=map.entrySet();
for(Entry<String,Integer> tmp:sets){
String key=tmp.getKey(); //获取一个key-value中的key值
Integer value=tmp.getValue(); //获取一个key-value中的value值
}
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>,
Cloneable, Serializable
如果不指定泛型,则默认key-value的类型都是Object
具体的内部数据存储方式
transient Node<K,V>[] table;
//哈希表的本质就是一个数组,数组中的每个元素称为一个桶,桶里存放的是一个key-value组成的链表或者红黑树
静态内部类Node用于实现了Entry接口,HashMap中存储的key-value对的数据被封装为Node对象,其中key就是存放的键值,用于决定具体的存放位置【桶的位置】;value就是具体存放的数据;hash就是当前Node对象的hash值缓存;next用于指向下一个Node节点【单向链表】
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
static final int TREEIFY_THRESHOLD = 8;
//树化阈值,即链表中的Node个数超过这个值时会自动转换为红黑树
static final int MIN_TREEIFY_CAPACITY = 64;
//最小树形化容量阈值,当哈希表中的所有元素个数>该值时才允许进行树形化链表(即将链表转换为红黑树),否则桶内元素太多时,直接扩容,而不是树形化
static final int UNTREEIFY_THRESHOLD = 6;
//桶的链表还原阈值,就是红黑树转换为单向链表的阈值。当在扩容时,此时所有存储的元素都将重新计算所存放的桶的位置,重新计算后当原有的红黑树中的节点个数<6时,将红黑树转换为单向链表
public HashMap() {
//构建一个空的HashMap,默认使用初始化容积值16,默认负载因子值为0.75。当前Map集合中允许存放的元素个数为【初始化容积值*负载因子】,16*0.75=12,当集合中存放的key-value个数超过12时,会自动进行扩容处理
this.loadFactor = DEFAULT_LOAD_FACTOR;
// all other fields defaulted
}
具体存放数据采用的是Node[]数组,每个数组中的元素称为一个桶bucket,一个桶对应一个hash映射的值,例如0、1等,可能会出现不同的key,但是映射位置相同。
例如16和48映射的位置都是0【hash%16】。
所以采用单向链表存储hash映射值相同的所有数据。为了避免一个单向链过长的问题,所以JDK1.8引入了红黑树,当一个链表上的元素个数大于8时,会自动将链表(O(n))转换为红黑树,以提高检索效率(O(logN));当删除节点使某个红黑树上的节点个数小于阈值(6)时会自动将红黑树转换为单向链表。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean
evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//1、判断table为空或者长度为0,即是否没有元素,如果没有元素则使用resize方法进行扩容。所谓的扩容处理就是将数组的长度加大一倍
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length; //获取扩容后的新数组长度
//2、计算插入存储的数组索引i,
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&((k = p.key)== key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key,value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&((k = e.key) == key || (key != null&&key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
map的具体实现类:HashMap、TreeMap、LinkedHashMap、Hashtable等