Java 手撕HashMap

2022-08-27 11:37:37

HashMap

Interface Map<K,V> 部分实现类:

  1. LinkHashMap不允许重复,记录顺序
  2. Hashtable底层是一个哈希表,是一个线程的安全集合,速度慢(不可以存 Null)
  3. HashMap底层哈希表,多线程不安全的,速度快。可以存取 Null

类似还有TreeMap:默认按照key递增排序entry实体。当然也可以重写比较器,可以参考前面文章。

HashCode与Equals关系

关于HashCode的官方文档定义

hashcode方法返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。 

hashCode 的常规协定是: 
在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。 
如果根据equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。 
以下情况不是必需的:如果根据equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。 
实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。) 

当equals方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

以上这段官方文档的定义,我们可以抽出成以下几个关键点

1、hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;

2、如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同;

3、如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点;

4、两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。

再归纳一下就是hashCode是用于查找使用的,而equals是用于比较两个对象的是否相等的。

1.hashcode是用来查找的,如果你学过数据结构就应该知道,在查找和排序这一章有
例如内存中有这样的位置01234567  
而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,如果不用hashcode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。
但如果用hashcode那就会使效率提高很多。
我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,然后把我们的类存放在取得得余数那个位置。比如我们的ID为998的余数为1,那么我们就把该类存在1这个位置,如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。这样,以后在查找该类时就可以通过ID除8求余数直接找到存放的位置了。2.但是如果两个类有相同的hashcode怎么办那(我们假设上面的类的ID不是唯一的),例如9除以817除以8的余数都是1,那么这是不是合法的,回答是:可以这样。那么如何判断呢?在这个时候就需要定义 equals了。
也就是说,我们先通过 hashcode来判断两个类是否存放某个桶里,但这个桶里可能有很多类,那么我们就需要再通过 equals 来在这个桶里找到我们要的类。
那么。重写了equals(),为什么还要重写hashCode()呢?
想想,你要在一个桶里找东西,你必须先要找到这个桶啊,你不通过重写hashcode()来找到桶,光重写equals()有什么用啊

注意:

  • equals方法:默认调用Object类的equals方法,即比较两个对象的引用地址是否相同
  • 重写了hashcode方法后,也需要重写equals方法

比如
创建两个Person类对象,判断二者是否为同一对象,或者是否重复排除重名

publicclassPerson{private String name;privateint age;publicPerson(String name,int age){this.name= name;this.age= age;}// 重写hashcode方法publicinthashCode(){return age%10;}publicfinalstaticvoidmain(String[] args){
		Person p1=newPerson("猪猪侠",18);
		Person p2=newPerson("猪猪侠",18);
		Set<Person> set=newHashSet<Person>();
		set.add(p1);
		set.add(p2);
		System.out.println(p1.hashCode()== p2.hashCode());
		System.out.println(p1.equals(p2));
		System.out.println(set);}}

结果是

true
false
set 包含两个人

然而我们认为p1 p2是重复的,因此 需要重写equals方法。

摘自博客原文链接
感谢!

重写HashCode与Equals方法

  • HashMap存储自定义的键Value:
  • String类重写了HashCodeEquals方法,可以保证Key唯一

示例:

publicclassPerson{private String name;privateint age;/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */@Overridepublic StringtoString(){return"Person[name="+ name+", age="+ age+"]";}/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */@OverridepublicinthashCode(){finalint prime=31;int result=1;
		result= prime* result+ age;// 处理冲突,数据结构中有介绍
		result= prime* result+((name== null)?0: name.hashCode());return result;}/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */@Overridepublicbooleanequals(Object obj){if(this== obj)returntrue;if(obj== null)returnfalse;if(getClass()!= obj.getClass())returnfalse;
		Person other=(Person) obj;if(age!= other.age)returnfalse;if(name== null){if(other.name!= null)returnfalse;}elseif(!name.equals(other.name))returnfalse;returntrue;}/**
	 * 
	 */publicPerson(){super();}/**
	 * @param name
	 * @param age
	 */publicPerson(String name,int age){super();this.name= name;this.age= age;}/**
	 * @return the name
	 */public StringgetName(){return name;}/**
	 * @param name the name to set
	 */publicvoidsetName(String name){this.name= name;}/**
	 * @return the age
	 */publicintgetAge(){return age;}/**
	 * @param age the age to set
	 */publicvoidsetAge(int age){this.age= age;}}

现在两个对象就完全相等了,上面的HashSet中也只存放了一个对象

Map的迭代器

Map集合没有实现Iterable接口,所以map集合不能直接使用增强for循环,如果需要使用增强for循环需要借助于Collection的集合。

Map.Entry< K,V >

  • 地图条目(键值对)/(实体集)。Map.entrySet()方法返回地图的集合视图,其元素属于此类。
    获取对映射条目的引用的唯一方法是从该集合视图的迭代器
  • 这些Map.Entry对象仅在迭代期间有效; 更正式地,如果在迭代器返回条目之后修改了后备映射,则映射条目的行为是未定义的,除了通过映射条目上的setValue操作。

在这里插入图片描述

  • Map.entrySet() 方法 返回的是:Set<Map.Entry<K,V>> 对象。将map中的每个entry对象(键值对)取出来,放进set集合中
// 创建HashMap集合对象,使用多态
Map<String,Integer> map=newHashMap<String,Integer>();// 使用put方法进行添加元素
map.put("帅",521);
map.put("柳",520);// 使用Map集合中的entrySet方法,将map中的每个entry对象取出来,放进set集合中
Set<Map.Entry<String,Integer>> set=map.entrySet();

遍历方法

  • 使用迭代器遍历集合
// 遍历set集合获取每一个Entry对象,使用迭代器遍历集合
Iterator<Map.Entry<String,Integer>> it= set.iterator();while(it.hasNext()){// Map.Entry<K, V> 获取实体对象
	Map.Entry<String,Integer> entry=it.next();// 使用entry方法getKey和getValue获取键和值
	String key= entry.getKey();
	Integer value= entry.getValue();
	System.out.println(key+value);}
  • 使用增强for遍历
//使用增强for,使用entry方法getKey和getValue获取键和值for(Map.Entry<String, Integer> entry: set){
	String key= entry.getKey();
	Integer value= entry.getValue();
	System.out.println(key+value);}// 或者使用 Entry<K,V>
Set<Entry<String, Integer>> sets= maps.entrySet();for(Entry<String, Integer> key: sets){
	String value= maps.get(key);// 获取Value值
	System.out.println(key+value);}
  • keySet() + for循环遍历
//使用KeySet和for遍历Map集合	.使用Map集合中的keySet方法将map中的key放入Set集合中,
Set<String> set= map.keySet();for(String key: set){int value=map.get(key);//获取Value值
	System.out.println(key+value);}

借鉴学习大神们的博客
Address1
Address2
感谢!

参考大佬文章,及官方API,向优秀的人致敬!
加油!!!

  • 作者:ZSYL
  • 原文链接:https://zsyll.blog.csdn.net/article/details/116405017
    更新时间:2022-08-27 11:37:37