zoukankan      html  css  js  c++  java
  • 关于HashMap

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

      

  • 相关阅读:
    AcWiing 翻硬币
    AcWing 飞行员兄弟 二进制枚举
    AcWing 费解的开关 二进制枚举
    vue发送短信逻辑
    使用celery异步发送短信
    celery配置与基本使用
    celery原理与组件
    短信验证接口
    图片验证码接口
    编写注册接口
  • 原文地址:https://www.cnblogs.com/DFX339/p/11233814.html
Copyright © 2011-2022 走看看