HashMap原理解析

用了好久的HashMap呀,但是一直都是只会用而已,根本就不太懂里面是啥 怎么实现的...  最近终于稍微深入了解了一下 。

 

1.首先我们来讲一下 HashMap的基本使用:存储数据 与 获取数据

>>>创建map
HashMap<String,String> map = new HashMap<String,String>();

>>>存储数据
map.put("name","zhangsan");
map.put("age", "18");
map.put("sex", "male");

>>>获取数据方式1 通过keyset遍历
Iterator iterator = map.keySet().iterator();
while(iterator.hasNext()){
  String key = (String) iterator.next();
  String value = map.get(key);
  System.out.println("KeySet遍历:key="+key+",value="+value);
}

>>>获取数据方式2 通过entryset遍历
Iterator entryIterator = map.entrySet().iterator();
while(entryIterator.hasNext()){
  Entry entry = (Entry) entryIterator.next();
  String key = (String) entry.getKey();
  String value = (String) entry.getValue();
  System.out.println("EntrySet遍历:key="+key+",value="+value);
}

***遍历方式 keyset 与 entryset 的比较

keySet: 通过 next()方法获取到map的key,然后再通过 map.get(key) 获取对应的value值 (存到Set集合里面的只是map集合的 key)

entrySet: 通过 next()方法得到 Entry对象,通过entry.getKey(), entry.getValue() 分别就能获取到对应的值 (存到Set集合里面的是 Entry对象,包含集合的 key,value)

综合来说: entrySet 比 keySet遍历方式更高效些,建议使用 方式2 entrySet 遍历map集合。

 

HashMap使用代码示例: HashMapApp.java 

package com.study.thread.juc_thread.base;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;

public class HashMapApp {

	public static void main(String[] args) {
		//创建map
		HashMap<String,String> map = new HashMap<String,String>();
		
		//存储数据
		map.put("name","zhangsan");
		map.put("age", "18");
		map.put("sex", "male");
		
		//获取数据方式1  通过keyset遍历
		Iterator iterator = map.keySet().iterator();
		while(iterator.hasNext()){
			String key = (String) iterator.next();
			String value = map.get(key);
			System.out.println("KeySet遍历:key="+key+",value="+value);
		}
		
		System.out.println("---------------遍历方式分割线------------------");
		
		//获取数据方式2 通过entryset遍历
		Iterator entryIterator = map.entrySet().iterator();
		while(entryIterator.hasNext()){
			Entry entry = (Entry) entryIterator.next();
			String key = (String) entry.getKey();
			String value = (String) entry.getValue();
			System.out.println("EntrySet遍历:key="+key+",value="+value);
		}
				

	}

}

  

 2. 接下来我们来看 HashMap的初始化 ,也就是创建map对象

  默认的容量值为: 16   

  默认的负载因子: 0.75

    容量: 即map集合的初始化大小,之后会根据存储对象多少以及 负载因子的值来进行扩容

  负载因子: 即当map集合存储的数据超过容量的 0.75时,可能会发生扩容操作

       例如: 容量为 16 ,负载因子为  0.75 , 当存储数据量大于12,发生hash冲突,且每个map的数组中每个bucket下都已有值了,则会发生扩容 (大小变为原来集合的 2倍)

  ***推荐指定容量创建map,因为扩容会很消耗资源。 初始容量值 = (需要存储的对象 / 负载因子) +1

  

HashMap提供了四个初始化方法,如下

    /**
       1.指定初始化容量,负载因子
     */
    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);
    }

    /**
     指定初始化容量值
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
      无参的构造方法
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

    /**
      指定map
     */
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

  

3.然后我们来看一下HashMap如何存储数据,这里我们就需要了解HashMap的数据结构了(如何解决hash冲突)。

HashMap的数据结构为: 数组 + 链表 / 红黑树

HashMap存储数据主要是根据 key 的 hashcode 来找到对应的位置存储,这里就有可能会发生哈希冲突,HashMap通过使用链表来处理hash冲突。

假设 我们的HashMap初始容量为 6 ,负载因子为 0.75 

存储数据

map.put("key1","value1");
map.put("key2","value2");
map.put("key3","value3");
map.put("key4","value4");
map.put("key5","value5");
map.put("key6","value6");
map.put("key7","value7");
map.put("key8","value8"); 

分别调用hashcode方法,计算出每个key的hash值,然后通过 hash值 % 容量 ,决定每个数据存储在数组中的位置

 

 从上图中 我们可知, key5 ,key7 ,key8 发生了hash冲突,所以会在数组下标 4的位置处建立一个链表,存储这些冲突的数据

这里存储冲突数据根据JDK版本不同,加入的位置也不同,之前是在添加在链表的头结点的(添加后需要重新移动链表),新版本的JDK做了优化 将数据添加在链尾。

JDK8的时候,还使用了红黑树来存储冲突数据。  当冲突数据 > 8 时,链表会转换为红黑树; 当冲突数据  < 6时,红黑树又会转换为链表

 

 

4.最后我们来看一下HashMap如何获取数据。 

HashMap获取数据,主要逻辑就是 map.get(key) 得到对应的value值;

>>map会先调用 key的 hashCode()方法,得到hash值,

>>根据hash值数组中找到对应的位置

>>如果存在冲突数据,则遍历链表,通过比较key值来获取对应value值,直至找到为止。

 

这里我们主要来讲一下为什么新建对象要复写equals方法时还要复写hashCode()方法

新建一个实体类 Person,里面有两个属性 姓名与年龄,我们先不复写它的equals() 以及hashcode方法  //这里为了测试方便,我就直接将Person类定义为内部类了

创建两个Person对象 per1 ,per2,设定这两个对象的姓名与年龄都相同。

分别使用 equals  , ==   判断这两个对象是否相等,// 我们都知道 == 比较的是对象的引用,在堆中这两个对象per1 per2肯定是不同的引用,所以 == 的结果会为false ; 实际应用中,equals我们应该是期望结果为true的~

创建一个map集合,将per1作为key存储进去,理论上per1应该是等于per2的,所以我们使用per2作为key去map集合里面取出对应的值应该是没有问题的,在执行下方代码之前先思考一下结果~

package com.study.thread.juc_thread.base;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.net.ssl.HandshakeCompletedEvent;

public class HashMapTest {

	public static void main(String[] args){
		
		Person per1 = new HashMapTest().new Person();
		per1.setAge(18);
		per1.setName("dfx");
		
		Person per2 = new HashMapTest().new Person();
		per2.setAge(18);
		per2.setName("dfx");
		
		System.out.println("per1 == per2 结果为:"+(per1 == per2));
		System.out.println("per1.equals(per2)结果为:"+per1.equals(per2));
		
		System.out.println("per1的hashcode:"+per1.hashCode());
		System.out.println("per2的hashcode:"+per2.hashCode());
		
		//创建map
		HashMap map = new HashMap();
		map.put(per1, "Hello World!");
		System.out.println("map获取value值:"+map.get(per2));
		
	}
	
	class Person {
		private String name;
		private int age;
		
		public Person(){}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public int getAge() {
			return age;
		}

		public void setAge(int age) {
			this.age = age;
		}
	
	}
}

  执行结果:

从结果中我们可以发现 per1 不等于 per2 , 且per1的hashcode与per2的hashcode不同,所以最后也没有办法使用per2作为key将map的value取出来。

 

接下来我们在内部类Person里添加如下两个方法 ,也就是复写Person对象的equals与hashcode方法,如果不复写就默认使用父类 Object的equals/hashcode方法。

          @Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + age;
			result = prime * result + ((name == null) ? 0 : name.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			Person other = (Person) obj;
			
			if (age != other.age)
				return false;
			if (name == null) {
				if (other.name != null)
					return false;
			} else if (!name.equals(other.name))
				return false;
			return true;
		}

  

再执行一遍上方代码,结果如下:

 

在复写equals方法后,我们new出来的Person对象,属性值相同,这两个对象equals为true

在复写hashCode方法后,per1 与 per2的hash值相等了,

根据我们今天讲的HashMap原理,存储的时候是通过 计算key的hash值,取数据的时候也是通过计算key的hashcode然后去找对应的数据

所以最后我们能使用map.get(per2)来获取 key为per1存储的值现在理解了为什么要复写equals以及hashcode方法了吧 哈哈哈~

 

完整测试类如下:

package com.study.thread.juc_thread.base;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.net.ssl.HandshakeCompletedEvent;

public class HashMapTest {

	public static void main(String[] args){
		
		Person per1 = new HashMapTest().new Person();
		per1.setAge(18);
		per1.setName("dfx");
		
		Person per2 = new HashMapTest().new Person();
		per2.setAge(18);
		per2.setName("dfx");
		
		System.out.println("per1 == per2 结果为:"+(per1 == per2));
		System.out.println("per1.equals(per2)结果为:"+per1.equals(per2));
		
		System.out.println("per1的hashcode:"+per1.hashCode());
		System.out.println("per2的hashcode:"+per2.hashCode());
		
		//创建map
		HashMap map = new HashMap();
		map.put(per1, "Hello World!");
		System.out.println("map.get(per2)获取value值:"+map.get(per2));
		
	}
	
	class Person {
		private String name;
		private int age;
		
		public Person(){}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public int getAge() {
			return age;
		}

		public void setAge(int age) {
			this.age = age;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + age;
			result = prime * result + ((name == null) ? 0 : name.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			Person other = (Person) obj;
			
			if (age != other.age)
				return false;
			if (name == null) {
				if (other.name != null)
					return false;
			} else if (!name.equals(other.name))
				return false;
			return true;
		}

		
	
	}
}

  

 

 posted on 2019-07-23 20:05  阿叮339  阅读(251)  评论(0编辑  收藏  举报