1、关于Hash
一般翻译做“散列”,也有译为"哈希"的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出。简单的说,就是hashcode = hash(key), 这种转换是一种压缩映射,散列值的空间通常远小于输入的空间,在查找的时候也能提高效率。
2、Hashmap的原理
java的hashmap是用一个key和value的模式来存取数据,但是本质上,它还是存储在一个数组里面,过程是这样的:首先得到key的hashcode,然后通过index=hashcode%size,得到数组的索引,然后把相应的entry放进数组中。但是这时候会有问题产生,不同的hashcode可能产生相同的index,这时候就会产生冲突,那么怎么解决这个冲突呢?这时候引入一个概念‘桶’,每一个数组元素代表(通过index确定位置)的存储叫一个‘桶’,而每个有相同的index的entry都存储在这个桶中,每个桶中的entry看做一个单向链表的结构来存储数据。
(1)、hashmap.put()的过程,直接上代码:
//hashmap的put方法
public V put(K key, V value) {
K k = maskNull(key);
int hash = hash(k); //通过hash算法得到散列值
int i = indexFor(hash, table.length); //得到数组的index
//遍历table[i]位置的链表,如果有发现符合条件的key,说明该key已经存在,替换掉对应的
//value,否则继续下面的操作
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
if (e.hash == hash && eq(k, e.key)) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
//把新的Entry加入到链表中去
addEntry(hash, k, value, i);
return null;
}
//把新的Entry加入到链表中去
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
//threshold=(int)(capacity*loadFactor);
//capacity为hashmap的容量,默认值为16
//loadFactor的负载因子,默认值为0.75
if (size++ >= threshold)
resize(2 * table.length); //容量翻一翻,内部的数据全部重新排一遍,很消耗资源
}
说到这里,有必要了解一下Entry的结构,它是hashmap的一个内部类,本身就是一个单向列表结构。主要代码如下:
//内部类Entry
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
final int hash;
Entry<K,V> next; //链表结构
/**
* Create new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public K getKey() {
return HashMap.<K>unmaskNull(key);
}
public V getValue() {
return value;
}
..
}
(2)、hashmap的get的过程
//hashmap的get方法
public V get(Object key) {
Object k = maskNull(key);
int hash = hash(k); //得到key的hashcode
int i = indexFor(hash, table.length); //得到数组的index
Entry<K,V> e = table[i];
while (true) { //在对应的桶中遍历
if (e == null)
return null;
//满足此条件,则说明找到了对应的value,返回
//该处说明key的equals()和hashcode()方法的作用,如果key的hashcode每一次都
//相等,那么每一次都要去比较equals方法,equals是很消耗资源的
if (e.hash == hash && eq(k, e.key))
return e.value;
e = e.next;
}
}
3、关于Object的equals()和hashcode()方法
这2个方法在java相关的场合出现的频率确实很高,这里就来讨论一下hashmap和这2个方法的关系。简单的讲,就是当你的对象可能要来做hashmap的key的时候,你就要特别注意了。在缺省情况下,equals方法返回this==obj, hashCode方法的缺省返回对象的内存地址对映于一个整数值来生成。下面举例说明一下:
public class User {
private String name;
public User(String name){
this.name = name;
}
public boolean equals(Object o) {
if (!(o instanceof User)) {
return false;
}
if (o == null) {
return false;
}
User anotherUser = (User) o;
if(anotherUser.name==null){
if(this.name==null){
return true;
}else{
return false;
}
}else{
return anotherUser.name.equals(this.name);
}
}
public static void main(String[] args) {
Map<User,Object> map = new HashMap<User,Object>();
map.put(new User("test"),"test1");
System.out.println(map.get(new User("test")));
}
}
以上代码的运行结果是:null
分析原因:没有重载hashcode()方法,在2次new User("test")的时候,虽然他们的equals方法相等,但是hashcode返回值并不相等。所有在get的时候找不到对应的桶,也自然取不到正确的值。
下面我们重载hashcode方法,如果按照下面的写法会有什么结果呢?
public int hashCode(){
return 0;
}
每次都返回相同的hashcode,这样写运行的时候不会报错,但是效率相当很低下。这样写也就违背了hashmap的初衷,它本身就为想利用散列来块的实现查找,而不用每次都去比较equals方法。分析代码,当很多这个这样的<key,value>组合被put进去的时候,它们被放在了同一个桶中,而当get的时候,每次都要去遍历链表,并且当执行if(e.hash == hash && eq(k, e.key)) 的时候,因为hashcode相等,所有总是要去执行equals方法,这样导致效率低下。
到这里,就会有个疑问,怎样写hashcode方法才是最好的呢?
首先把握住3个原则:1、如果两个对象equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果;
2、根据第一条可以推出,如果两个对象的hashcode方法产生的值不相等,那么equals方法也必定不相等;
3、虽然可以出现,equals方法不相等,hashcode相等的情况。 但是如果提高equals不相等===》
hashcode也不相等的概率,可以提高hashmap的效率。
一种简单常见的hashcode方法:
public int hashCode(){
int hash = 1;
hash = hash * 31 + (this.name == null ? 0 : this.name.hashCode());
return hash;
}
4、关于冲突和避免冲突的方法:
冲突在2种情况下发生,第一,key得到的hashcode是相等的,第二,不同的hashcode,通过hashcode%mapsize后,得到相同的index。 首先强调,要完全避免冲突是不可能的,只有降低发生的概率。重点讨论第二种情况的解决方案:
1、最好能够在定义map的时候能够估计到map的大小,Map map = new HashMap(size),因为rehash的过程是相当消耗的资源的;
2、调整负载因子loadFactor的大小,默认值为0.75,JDK描述这个值是权衡了时间和空间上的综合考虑后取的平均值,loadFactor值越大,空间消耗就越小,时间
就越大;反之,loadFactor越小,时间消耗就越小,空间消耗及越大。loadFactor的值一般在0.5-1之间。
3、map的大小,自定义的size值的时候,最好是素数。因为素数可以降低冲突的概率。(但jdk的默认值mapsize是16)。
分享到:
相关推荐
在本分析中,我们将会详细探讨HashMap在不同负载因子(loadFactor)、循环次数(loop)、哈希表长度(maptablelen)和映射长度(maplen)等条件下的行为和特性。 负载因子(loadFactor)是HashMap中的一个关键参数...
Java HashMap原理分析 Java HashMap是一种基于哈希表的数据结构,它的存储原理是通过将Key-Value对存储在一个数组中,每个数组元素是一个链表,链表中的每个元素是一个Entry对象,Entry对象包含了Key、Value和指向...
hashMap存储分析hashMap存储分析
HashMap 是 Java 中最常用的集合类之一,它是基于哈希表实现的,提供了高效的数据存取功能。HashMap 的核心原理是将键(key)通过哈希函数转化为数组索引,然后将键值对(key-value pair)存储在数组的对应位置。...
哈希映射(HashMap)是Java编程语言中广泛使用的数据结构之一,主要提供键值对的存储和查找功能。HashMap的实现基于哈希表的概念,它通过计算对象的哈希码来快速定位数据,从而实现了O(1)的平均时间复杂度。在深入...
面试中,可能会被问及HashMap的性能优化、内存占用分析、以及在特定场景下的选择,如并发环境、内存敏感的应用等。 总结,HashMap是Java编程中的基础工具,掌握其工作原理和常见面试题,不仅能帮助我们应对面试,更...
面试中,HashMap的源码分析与实现是一个常见的考察点,因为它涉及到数据结构、并发处理和性能优化等多个核心领域。本篇文章将深入探讨HashMap的内部工作原理、关键特性以及其在实际应用中的考量因素。 HashMap基于...
用过Java的都知道,里面有个功能强大的数据结构——HashMap,它能提供键与值的对应访问。不过熟悉JS的朋友也会说,JS里面到处都是hashmap,因为每个对象都提供了map[key]的访问形式。
HashMap导致CPU100% 的分析
hashmap的原理啊思想。
在Java编程语言中,`HashMap`和`HashTable`都是实现键值对存储的数据结构,但它们之间存在一些显著的区别,这些区别主要体现在线程安全性、性能、null值处理以及一些方法特性上。以下是对这两个类的详细分析: 1. ...
通过分析源码,开发者可以深入理解哈希表的工作原理,学习如何在易语言中实现高效的数据结构,这对于提升程序性能和优化内存管理至关重要。同时,这也为自定义数据结构或实现其他哈希表相关的功能提供了基础。
比较分析Vector、ArrayList和hashtable hashmap数据结构
hashMap基本工作原理,图解分析,基础Map集合
1.面试必考之HashMap源码分析与实现 伸缩性角度看HashMap的不足