热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

HashMap源码分析与实现

面试的时候经常会遇见诸如:“java中的HashMap是怎么工作的”,“HashMap的get和put内部的工作原理”这样的问题。本文将用一个简单的例子来解释下HashMap内部的工作原理。每当

面试的时候经常会遇见诸如:“java中的HashMap是怎么工作的”,“HashMap的get和put内部的工作原理”这样的问题。本文将用一个简单的例子来解释下HashMap内部的工作原理。每当hashmap扩容的时候需要重新去add Entry对象,需要重新hash,然后放入我们新的entry table数组里面。如果在工作中,已经知道hashmap需要存多少值,几千或者几万的时候,最好新指定题目的扩容大小,防止在put的时候进行再次扩容很多次。


一、源码分析

1、初始化参数

在hashmap中我们必须要知道的参数有:初始化容量大小,默认是1 <<4,也就是16,

static final int DEFAULT_INITIAL_CAPACITY = 1 <<4; 

加载因子0.75f,

    static final float DEFAULT_LOAD_FACTOR = 0.75f;

hash在什么时候扩容?

put的时候会去做扩容,当大于3/4的时候就会。偶数扩容  ,  2*16    2*32   2*64 
2、put方法
 public V put(K key, V value) {        if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}

modCount++;
addEntry(hash, key, value, i);
return null;
}


对key做null检查。如果key是null,会被存储到table[0],因为null的hash值总是0。

key的hashcode()方法会被调用,然后计算hash值。hash值用来找到存储Entry对象的数组的索引。有时候hash函数可能写的很不好,所以JDK的设计者添加了另一个叫做hash()的方法,它接收刚才计算的hash值作为参数。如果你想了解更多关于hash()函数的东西,可以参考:hashmap中的hash和indexFor方法

indexFor(hash,table.length)用来计算在table数组中存储Entry对象的精确的索引。

如果两个key有相同的hash值(也叫冲突),他们会以链表的形式来存储。所以,这里我们就迭代链表。


如果在刚才计算出来的索引位置没有元素,直接把Entry对象放在那个索引上。
如果索引上有元素,然后会进行迭代,一直到Entry->next是null。当前的Entry对象变成链表的下一个节点。
如果我们再次放入同样的key会怎样呢?逻辑上,它应该替换老的value。事实上,它确实是这么做的。在迭代的过程中,会调用equals()方法来检查key的相等性(key.equals(k)),如果这个方法返回true,它就会用当前Entry的value来替换之前的value。
对于put的值,我们可以来看一个例子:


public class demo {
    
     public static void main(String[] args ) {
          HashMap hashMap = new HashMap();
         Object put1 = hashMap .put( "aa" , "30" ) ;
         Object put2 = hashMap .put( "aa" , "31" ) ;
         System. out .println( put1 + ":" + put2 );
    }
}

当put两个相同的key的时候,这个put返回的值是oldValue,也就是说我这里打印出来的 结果就是  null:30


put返回的值是oldValue。key一样value会覆盖
如果hash key重复,value是不会覆盖的( 经过hash算法返回的值如果重复了)。

对key进行hash处理的时候其实也是进行的位移处理,我们来看到hash的源码。

 final int hash(Object k) {        int h = hashSeed;        if (0 != h && k instanceof String) {            return sun.misc.Hashing.stringHash32((String) k);        }        h ^= k.hashCode();        // This function ensures that hashCodes that differ only by        // constant multiples at each bit position have a bounded        // number of collisions (approximately 8 at default load factor).        h ^= (h >>> 20) ^ (h >>> 12);        return h ^ (h >>> 7) ^ (h >>> 4);    }
3、get方法

当你传递一个key从hashmap总获取value的时候:对key进行null检查。如果key是null,table[0]这个位置的元素将被返回。
key的hashcode()方法被调用,然后计算hash值。
indexFor(hash,table.length)用来计算要获取的Entry对象在table数组中的精确的位置,使用刚才计算的hash值。
在获取了table数组的索引之后,会迭代链表,调用equals()方法检查key的相等性,如果equals()方法返回true,get方法返回Entry对象的value,否则,返回null。

public V get(Object key) {        if (key == null)            return getForNullKey();        Entry entry = getEntry(key);        return null == entry ? null : entry.getValue();    }

    final Entry getEntry(Object key) {        if (size == 0) {            return null;        }        int hash = (key == null) ? 0 : hash(key);        for (Entry e = table[indexFor(hash, table.length)];             e != null;             e = e.next) {            Object k;            if (e.hash == hash &&                ((k = e.key) == key || (key != null && key.equals(k))))                return e;        }        return null;    }

4、Entry


hashmap table  :数组+链接   的数据结构 



void resize(int newCapacity) {        Entry[] oldTable = table;        int oldCapacity = oldTable.length;        if (oldCapacity == MAXIMUM_CAPACITY) {            threshold = Integer.MAX_VALUE;            return;        }//创建Entry对象的数组        Entry[] newTable = new Entry[newCapacity];//赋值        transfer(newTable, initHashSeedAsNeeded(newCapacity));        table = newTable;        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);    }

二、手写hashmap

通过前面的内容,我们已经知道了hashmap的原理了,我们现在来自己写一个hashmap。

1、map接口

public interface Map {		public V put(K k,V v);		public V get(K k);		public int size();		public interface  Entry{		public K getKey();		public V getValue();	}}

2、实现类

public class HashMap  implements Map {	//初始容量	private static int defaultLength=16;   	//加载因子	private static double defaultLoader=0.75;	private Entry[]  table=null;	private int size=0;		public HashMap() {		this(defaultLength, defaultLoader);	}	public HashMap(int length,double loader){		defaultLength=length;		defaultLoader=loader;		table=new Entry[defaultLength];	}			public HashMap(Entry[] table, int size) {		super();		this.table = table;		this.size = size;	}	public V put(K k, V v) {		size++;		int index=hash(k);		Entry  entry=table[index];		if(entry==null){			table[index]=newEntry(k,v,null);		}else{			table[index]=newEntry(k,v,entry);		}		return  (V)table[index].getValue();	}	private Entry newEntry(K k, V v, Entry next) {		return new Entry(k, v, next);	}	public int hash(K k){		int m=defaultLength;		int i=k.hashCode()%m;		return i>=0?i:-i;	}					public V get(K k) {		int index=hash(k);		if(table[index]==null){			return null;		}		//在数组中查找		return find(k,table[index]) ;	}	public V find(K k, Entry entry) {		if(k==entry.getKey() || k.equals(entry.getKey())){						if(entry.next!=null){				//System.out.println("1oldValue:"+entry.next.getValue());				}									return  (V) entry.getValue();		}else{			if(entry.next!=null){				//System.out.println("2oldValue:"+entry.next.getValue());				return find(k, entry.next);			}		}		return null;	}	public int size() {				return size;	}		class Entry  implements  Map.Entry{		K k;		V v;		Entry  next;				public Entry(K k, V v, Entry next) {			super();			this.k = k;			this.v = v;			this.next = next;		}		public K getKey() {						return k;		}		public V getValue() {						return v;		}		public K getK() {			return k;		}		public void setK(K k) {			this.k = k;		}		public V getV() {			return v;		}		public void setV(V v) {			this.v = v;		}		public Entry getNext() {			return next;		}		public void setNext(Entry next) {			this.next = next;		}			}	}

3、通过一个main函数,来测试一下,我们写的这个hashmap是否正确。

public static void main(String[] args) {		Map map=new HashMap();		map.put("sss", 30);		map.put("sss", 31);		System.out.println(map.get("sss"));	}
注意,引入包的时候要注意,不要引入jdk框架的map和hash包,要引入我们自己写的这个包。


HashMap有一个叫做Entry的内部类,它用来存储key-value对。
上面的Entry对象是存储在一个叫做table的Entry数组中。
table的索引在逻辑上叫做“桶”(bucket),它存储了链表的第一个元素。
key的hashcode()方法用来找到Entry对象所在的桶。
如果两个key有相同的hash值,他们会被放在table数组的同一个桶里面。


---------------------------------------------------------------------------------------------------

文章出处: http://blog.csdn.net/sdksdk0/article/details/79299286
作者:朱培 ID:sdksdk0
---------------------------------------------------------------------------------------------------



推荐阅读
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 提升Python编程效率的十点建议
    本文介绍了提升Python编程效率的十点建议,包括不使用分号、选择合适的代码编辑器、遵循Python代码规范等。这些建议可以帮助开发者节省时间,提高编程效率。同时,还提供了相关参考链接供读者深入学习。 ... [详细]
  • 本文由编程笔记#小编为大家整理,主要介绍了logistic回归(线性和非线性)相关的知识,包括线性logistic回归的代码和数据集的分布情况。希望对你有一定的参考价值。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 成功安装Sabayon Linux在thinkpad X60上的经验分享
    本文分享了作者在国庆期间在thinkpad X60上成功安装Sabayon Linux的经验。通过修改CHOST和执行emerge命令,作者顺利完成了安装过程。Sabayon Linux是一个基于Gentoo Linux的发行版,可以将电脑快速转变为一个功能强大的系统。除了作为一个live DVD使用外,Sabayon Linux还可以被安装在硬盘上,方便用户使用。 ... [详细]
author-avatar
艺卓显示、巴可投影
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有