文章目录
- 1、Map
- 2、HashMap
- 2.1 实现原理
- 2.2 Hash函数解析
- 2.3 tableSizeFor函数解析
- 3、TreeMap
- 3.1 实现原理
- 3.2 Comparator和Comparable
1、Map
Map是用于存储键值对key/value的集合接口,明确地,
1、键key具有唯一性,而key所对应的值value可以重复,那么则存在不同的key指向同一个value的情况,以及先后定义同一个键key,前者value被后者value覆盖的情况;
public static void main(String[] args) {
Map<String,Person> m=new HashMap<>();
Person p=new Person(1,"zhangsan");
//不同的key指向同一个value;
m.put("key1", p);
m.put("key2",p);
System.out.println(m);//{key1=Person [id=1, name=zhangsan], key2=Person [id=1, name=zhangsan]}
//同一个key指向不同的value;
p=new Person(2,"lisi");
m.put("key2", p);
System.out.println(m);//{key1=Person [id=1, name=zhangsan], key2=Person [id=2, name=lisi]}
}
2、Map没有提供直接由索引获取元素的通用方法,而是通过get(K key)方法返回value值;
public static void main(String[] args) {
Map<String,Person> m=new HashMap<>();
Person p=new Person(1,"zhangsan");
m.put("1",p );
Person p1=m.get("1");
System.out.println(p1);//Person [id=1, name=zhangsan]
}
3、Map无法直接遍历键值对,而是先转化为集合Collection,然后再遍历;
public static void main(String[] args) {
Map<String,Person> m=new HashMap<>();
m.put("zhangsan",new Person(1,"zhangsan"));
m.put("lisi", new Person(2,"lisi"));
m.put("wangwu", new Person(3,"wangwu"));
System.out.println("第一种、遍历键集合的迭代器------------------------------------------------");
Iterator<String> iterator=m.keySet().iterator();
while(iterator.hasNext()) {
String key=iterator.next();
System.out.println("key: "+key+" value: "+m.get(key));
}
System.out.println("第二种、遍历键集合Set方式------------------------------------------------");
for(String key:m.keySet()) {
System.out.println("key:"+key+" value:"+m.get(key));
}
System.out.println("第三种、遍历键值对集合Set方式-----------------------------------------");
for(Map.Entry<String, Person> entry :m.entrySet()) {
System.out.println("key:"+entry.getKey()+" value:"+entry.getValue());
}
}
4、Map接口的常用实现类有HashMap和TreeMap;
2、HashMap
2.1 实现原理
快速CRUD,那么为什么说快速呢?
因为散列表数据结构是由数组和链表结合实现的,我们都知道数组对改查操作的时间复杂度为O(1),可实现随机索引查找功能,而链表在增删方面的时间复杂度也为O(1),所以散列表充分发挥了两者的优势,只要哈希函数构造合理,冲突不显著下,速度就会很快;
当需要通过put(K,V)插入一条键值对时,在考虑直通车的情况下(K不重复,且链长小于TREEIFY_THRESHOLD-1),简单理解,首先将键值对封装成Node<k,V>对象,然后通过k的hash函数映射到具体的桶bucket,最后将Node移至链尾;
2.2 Hash函数解析
Public indexFor(object key,int length){
Return key.hashCode() & length-1;
}
在JDK8之前Key是通过indexFor函数来映射到对应桶bucket,在通常情况下认为散列表的bucket长度length不会超过16位,那么会导致该indexFor函数没有充分利用到哈希码的高16位,所以在JDK8以后改用了hash函数;
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
符号说明 : ^:按位异否,>>>:无符号右移 ,&:按位与;
hashCode()函数返回int类型整数,其包含4个字节,32位bit,h>>>16是为了获得高16位,并与低16位进行异或操作h^(h>>>16),得到新的Key哈希码,这样就会充分利用到了Key原始哈希码的高16位,最后在各函数(putVal、removeNode等)中将hash()函数生产的新哈希码hash与bucket长度进行按位与操作后确定桶的位置;
2.3 tableSizeFor函数解析
static final int tableSizeFor(int cap) {
int n = cap - 1;
//n发生按位或、无符号右移动作
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
//MAXIMUM_CAPACITY =2^30
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
符号说明
|:按位或,>>>:无符号右移,n|=n>>>1 可转化为n=n | (n>>>1);
tableSizeFor函数是用来初始化散列表中桶长度大小的,当我们创建一个初始化桶大小initialCapacity=18的HashMap对象时将会调用到tableSizeFor函数,用于调整initialCapacity使得桶的大小为2的5次幂值;
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;//负载因子
this.threshold = tableSizeFor(initialCapacity);//调整容器大小为逼近initialCapacity的2的幂次
}
Eg:整型十进制18转化为二进制为:0000 0000 0000 0000 000 0000 0001 0010
n|=n>>>1 :
0000 0000 0000 0000 000 0000 0001 0010 | 0000 0000 0000 0000 000 0000 000 1001=0000 0000 0000 0000 000 0000 0001 1011
n|=n>>>2 :
0000 0000 0000 0000 000 0000 0001 1011 | 0000 0000 0000 0000 000 0000 0000 0110=0000 0000 0000 0000 000 0000 0001 1111
n |= n >>> 4:
0000 0000 0000 0000 000 0000 0001 1111 | 0000 0000 0000 0000 000 0000 0000 0001=0000 0000 0000 0000 000 0000 0001 1111
….
最终 n=0000 0000 0000 000 0000 0001 1111,转化为十进制后n+1=32=2^5
3、TreeMap
3.1 实现原理
期待更新中….)
3.2 Comparator和Comparable
由于TreeMap插入的Key是经过排序的,所以要求插入Key时,要么创建Map对象时就传递好对Key排序的Comparator对象,要么Key实现Comparable接口,否则会报运行时错误ClassCastException;
public static void main(String[] args) {
//Person未实现Comparable接口
Map<Person,String> treeMap=new TreeMap<>();
treeMap.put(new Person(1,"张三"), "zhangsan");
treeMap.put(new Person(2,"李四"),"lisi");
System.out.println(treeMap);
}
发生异常如下:
两种解决方法:
- 第一种是,定义Comparator的实现类PersonComparator或者写Comparator的匿名类,然后传入TreeMap的构造方法中;
package xw.zx.collection;
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
import xw.zx.model.Person;
public class MapDemo01 {
static class PersonComparator implements Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
return o1.getId()-o2.getId();
}
}
public static void main(String[] args) {
//1、创建TreeMap时传入Comparator的实现对象PersonComparator;
//Map<Person,String> treeMap=new TreeMap<>(new PersonComparator());
//2、创建TreeMap时传入Comparator匿名类
Map<Person,String> treeMap1=new TreeMap<>(new Comparator<Person>(){
@Override
public int compare(Person o1, Person o2) {
return o1.getId()-o2.getId();
}
}) ;
treeMap1.put(new Person(1,"张三"), "zhangsan");
treeMap1.put(new Person(2,"李四"),"lisi");
System.out.println(treeMap1);//{Person [id=1, name=张三]=zhangsan, Person [id=2, name=李四]=lisi}
}
}
- 第二种方法,让Person类实现Comparable接口;
public class Person implements Comparable<Person>{
@Override
//实现Comparable的compareTo方法
public int compareTo(Person o) {
// TODO Auto-generated method stub
return id-o.getId();
}
}