Java总结_百宝袋集合类(4)Map


在这里插入图片描述

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 实现原理

  在JDK8源码中可以看到,HashMap是基于散列表来实现数据的快速CRUD,那么为什么说快速呢?
  因为散列表数据结构是由数组和链表结合实现的,我们都知道数组对改查操作的时间复杂度为O(1),可实现随机索引查找功能,而链表在增删方面的时间复杂度也为O(1),所以散列表充分发挥了两者的优势,只要哈希函数构造合理,冲突不显著下,速度就会很快;
在这里插入图片描述
  当需要通过put(K,V)插入一条键值对时,在考虑直通车的情况下(K不重复,且链长小于TREEIFY_THRESHOLD-1),简单理解,首先将键值对封装成Node<k,V>对象,然后通过k的hash函数映射到具体的桶bucket,最后将Node移至链尾;考虑在一般情况下时,put方法可参考该篇博客详解put流程图;

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 实现原理

  TreeMap是通过红黑树(平衡二叉树)来实现键值对的CRUD操作的,所以需要理解红黑树的实现原理后,才能真正地看懂源码(期待更新中….

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();
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值