`
future009
  • 浏览: 2921 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

HashMap分析

阅读更多

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)。

0
1
分享到:
评论

相关推荐

    HashMap 分析

    在本分析中,我们将会详细探讨HashMap在不同负载因子(loadFactor)、循环次数(loop)、哈希表长度(maptablelen)和映射长度(maplen)等条件下的行为和特性。 负载因子(loadFactor)是HashMap中的一个关键参数...

    java HashMap原理分析

    Java HashMap原理分析 Java HashMap是一种基于哈希表的数据结构,它的存储原理是通过将Key-Value对存储在一个数组中,每个数组元素是一个链表,链表中的每个元素是一个Entry对象,Entry对象包含了Key、Value和指向...

    hashMap存储分析

    hashMap存储分析hashMap存储分析

    HashMap源码分析

    HashMap 是 Java 中最常用的集合类之一,它是基于哈希表实现的,提供了高效的数据存取功能。HashMap 的核心原理是将键(key)通过哈希函数转化为数组索引,然后将键值对(key-value pair)存储在数组的对应位置。...

    hashmap实现原理

    哈希映射(HashMap)是Java编程语言中广泛使用的数据结构之一,主要提供键值对的存储和查找功能。HashMap的实现基于哈希表的概念,它通过计算对象的哈希码来快速定位数据,从而实现了O(1)的平均时间复杂度。在深入...

    hashmap面试题_hashmap_

    面试中,可能会被问及HashMap的性能优化、内存占用分析、以及在特定场景下的选择,如并发环境、内存敏感的应用等。 总结,HashMap是Java编程中的基础工具,掌握其工作原理和常见面试题,不仅能帮助我们应对面试,更...

    面试必考之HashMap源码分析与实现

    面试中,HashMap的源码分析与实现是一个常见的考察点,因为它涉及到数据结构、并发处理和性能优化等多个核心领域。本篇文章将深入探讨HashMap的内部工作原理、关键特性以及其在实际应用中的考量因素。 HashMap基于...

    重载toString实现JS HashMap分析

    用过Java的都知道,里面有个功能强大的数据结构——HashMap,它能提供键与值的对应访问。不过熟悉JS的朋友也会说,JS里面到处都是hashmap,因为每个对象都提供了map[key]的访问形式。

    HashMap导致CPU100% 的分析

    HashMap导致CPU100% 的分析

    HashMap源码分析-思维导图.xmind

    hashmap的原理啊思想。

    HashMap与HashTable的区别(含源码分析)

    在Java编程语言中,`HashMap`和`HashTable`都是实现键值对存储的数据结构,但它们之间存在一些显著的区别,这些区别主要体现在线程安全性、性能、null值处理以及一些方法特性上。以下是对这两个类的详细分析: 1. ...

    HashMap类.rar

    通过分析源码,开发者可以深入理解哈希表的工作原理,学习如何在易语言中实现高效的数据结构,这对于提升程序性能和优化内存管理至关重要。同时,这也为自定义数据结构或实现其他哈希表相关的功能提供了基础。

    比较分析Vector、ArrayList和hashtable hashmap数据结构

    比较分析Vector、ArrayList和hashtable hashmap数据结构

    hashMap基本工作原理,图解分析,基础Map集合

    hashMap基本工作原理,图解分析,基础Map集合

    1.面试必考之HashMap源码分析与实现 伸缩性角度看HashMap的不足

    1.面试必考之HashMap源码分析与实现 伸缩性角度看HashMap的不足

Global site tag (gtag.js) - Google Analytics