精华内容
下载资源
问答
  • HashMap底层原理

    2019-09-28 11:52:32
    HashMap底层原理
  • HashMap 底层原理

    2020-05-23 14:53:40
  • hashmap底层原理

    2020-08-06 23:10:46
    hashmap底层原理 hashmap在Java中被频繁使用到 ,了解hashmap底层原理有利于我们理解它的存储过程及快速使用。 1.哈希表 首先我们来认识一下哈希表,哈希表:是根据关键码值(Key value)而直接进行访问的数据结构。也...

    hashmap底层原理

    hashmap在Java中被频繁使用到 ,了解hashmap底层原理有利于我们理解它的存储过程及快速使用。

    1.哈希表

    首先我们来认识一下哈希表,哈希表:是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
     在哈希表中进行添加,删除,查找等操作,性能非常高,不考虑哈希冲突的情况下,一次定位即可完成,时间复杂度为O(1).
     在两种物理存储结构中:顺序存储结构和链式存储结构,在数组中根据下标查找某个元素,一次就可以找到元素,哈希表的主干就是数组,这样提高了查找速度。

    2.hashmap的数据结构

    HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个object类型的key-value键值对。对采用数组+链表的形式,充分运用了数组查询快,链表插入删除快的特性。
    数组:查询速度快,可以根据索引查询;但插入和删除比较困难;
    链表:查询速度慢,需要遍历整个链表,但插入和删除操作比较容易。
    红黑树:一种二叉树,高效的检索效率,当链表达到一定长度后,链表就会变成红黑树,当链表长度大于8的时候,将后面的数据存在二叉树中
    在这里插入图片描述
    自己实现简易的hashmap

    import java.util.HashMap;

    public class WngMap {
    HashMap<String, WngMap> ap=new HashMap<>();
    SxtEntry[] arr=new SxtEntry[990];
    int size;

    public void put(Object key,Object value){
    	SxtEntry e=new SxtEntry(key, value);
    	for (int i = 0; i < size; i++) {
    		if(arr[i].key.equals(key)){
    			arr[i].value=value;
    		}
    	}
    	arr[size++]=e;
    }
    public Object get(Object key){
    	for (int i = 0; i < size; i++) {
    		if (arr[i].key.equals(key)) {
    			return arr[i].value;
    		}
    	}
    	return null;
    }
    public boolean contentKey(Object key){
    	for (int i = 0; i < size; i++) {
    		if(arr[i].key.equals(key)){
    			return true;
    		}
    	}
    	return false;
    }
    
    public boolean contentVlaue(Object value){
    	for (int i = 0; i < size; i++) {
    		if(arr[i].value.equals(value)){
    			return true;
    		}
    	}
    	return false;
    }
    
    
    public static void main(String[] args) {
    	WngMap w=new WngMap();
    	w.put("wng", "xq");
    	w.put("wngsy", "ali");
    	System.out.println(w.contentKey("wng"));
    	System.out.println(w.get("wngsy"));
    }
    

    }

    class SxtEntry{
    Object key;
    Object value;

    public SxtEntry(Object key, Object value) {
    	super();
    	this.key = key;
    	this.value = value;
    }
    

    }

    
    

    通过例子来看一下它的存储过程,
    在这里插入图片描述 如上图采用除留余数法来理解下hashmap的存储过程,对于散列表长为m的散列函数公式为:f( key ) = key mod p ( p ≤ m ) mod(取余)
      因为有10条记录,我们就先让p=10,现在要把第一个元素51放入hashmap中,51%10=1 所以把它放入数组下标为1的链表中,链表中第一个位置已经存放1,把51接着在链表中的第二个位置中。

    3.哈希冲突

    如果两个不同的元素通过哈希函数得出的实际存储地址相同,要进行插入的时候,发现位置已经被其他元素占用了,这就是所谓的哈希冲突,也叫哈希碰撞。哈希函数的设计至关重要,哈希函数会尽可能地保证 计算简单和散列地址分布均匀,但是,数组是一块连续的固定长度的内存空间,再好的哈希函数也不能保证得到的存储地址绝对不发生冲突。采用链地址法可以解决哈希冲突,也就是数组+链表的方式,如上面所说的它的存储过程。
    在这里插入图片描述当不同对象计算出来的哈希值相同时,反生哈希冲突,链表增加一个节点,next指向下一个节点 。

    4. 为什么重写equals方法需同时重写hashCode方法?

    若我们把数组比作桶,桶中的list装有元素,equels直接比较的是元素。而我们在要比较桶中的元素是首先要用hashCode方法找到桶,才能用equals比较桶元素是否相等。否则可能出现取出的值为null的情况。这就是重写equals时也要同时覆盖hashcode”的原因。

    展开全文
  • Hashmap底层原理

    2021-01-04 13:03:12
    Hashmap底层原理 什么是散列法:散列法(Hashing)是一种将字符组成的字符串转换为固定长度(一般是更短长度)的数值或索引值的方法,称为散列法,也叫哈希法。 由于通过更短的哈希值比用原始值进行数据库搜索更...
    Hashmap底层原理
     
    什么是散列法:散列法(Hashing)是一种将字符组成的字符串转换为固定长度(一般是更短长度)的数值或索引值的方法,称为散列法,也叫哈希法。
    由于通过更短的哈希值比用原始值进行数据库搜索更快,这种方法一般用来在数据库中建立索引并进行搜索,同时还用在各种解密算法中。
     
    什么是HashMap:HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。HashMap储存的是键值对,HashMap很快。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
     
    HashMap 内部结构:可以看作是数组和链表结合组成的复合结构,数组被分为一个个桶(bucket),每个桶存储有一个或多个Entry对象,每个Entry对象包含三部分key(键)、value(值),next(指向下一个Entry),通过哈希值决定了Entry对象在这个数组的寻址;哈希值相同的Entry对象(键值对),则以链表形式存储。如果链表大小超过树形转换的阈值(TREEIFY_THRESHOLD= 8),链表就会被改造为树形结构。 因为链表中元素太多的时候会影响查找效率,所以当链表的元素个数达到8的时候使用链表存储就转变成了使用红黑树存储,原因就是红黑树是平衡二叉树,在查找性能方面比链表要高.
     
     
     
    查询时间复杂度:HashMap的本质可以认为是一个数组,数组的每个索引被称为桶,每个桶里放着一个单链表,一个节点连着一个节点。很明显通过下标来检索数组元素时间复杂度为O(1),而且遍历链表的时间复杂度是O(n),所以在链表长度尽可能短的前提下,HashMap的查询复杂度接近O(1)
    数组的特点是:寻址容易,插入和删除困难;
    链表的特点是:寻址困难,插入和删除容易。
    HashMap的工作原理 :HashMap是基于散列法(又称哈希法)的原理,使用put(key, value)存储对象到HashMap中。
        1、 当程序试图将一个key-value对放入HashMap中时,程序首先根据该 key的 hashCode() 返回值决定该 Entry 的存储位置
            在Object类中有一个方法:
    public native int hashCode();
    该方法用native修饰,所以是一个本地方法,所谓本地方法就是非java代码,这个代码通常用c或c++写成,在java中可以去调用它。
        调用这个方法会生成一个int型的整数,我们叫它哈希码,哈希码和调用它的对象地址和内容有关.
            2、 如果两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry的 value,但key不会覆盖
            3、  新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。
            4、 如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部
    HashCode:
        什么是哈希码:在Java中,哈希码代表对象的特征。
        哈希码产生的依据:哈希码并不是完全唯一的,它是一种算法,让同一个类的对象按照自己不同的特征尽量的有不同的哈希码,但不表示不同的对象哈希码完全不同。
        String的hashcode方法
            s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
        选择31的理由:
            第一,31是一个不大不小的质数,是作为 hashCode 乘子的优选质数之一。另外一些相近的质数,比如37、41、43等等,也都是不错的选择。那么为啥偏偏选中了31呢?请看第二个原因。一般在设计哈希算法时,会选择一个特殊的质数。至于为啥选择质数,我想应该是可以降低哈希算法的冲突率。
        第二、数字31有一个很好的特性,即乘法运算可以被移位和减法运算取代,来获取更好的性能:31 * i == (i << 5) - i,现代的 Java 虚拟机可以自动的完成这个优化。31可以被 JVM 优化,31 * i = (i << 5) - i
     
       equals 以及 ==拓展:
            equals方法是java.lang.Object类的方法
            1、对于字符串变量
                '=='比较的是变量本身的值,即在内存中的首地址
                (java中,对象首地址是它在内存中存放的起始地址,后面的地址是各个属性的地址)
                'equals()'比较字符串中包含的内容是是否相同
                String的equals方法因为重写了Object的equals方法,所以可以比较字符串的内容,而StringBuffer因为没重写equals方法,直接继承了Object的equals方法,所以比较的是地址。
                例子:
                    String s1,s2,s3 = "abc", s4 ="abc" ;
                    s1 = new String("abc");
                    s2 = new String("abc");
     
     
                    s1==s2   是 false      //两个变量的内存地址不一样,也就是说它们指向的对象不 一样,
                    s1.equals(s2) 是 true    //两个变量的所包含的内容是abc,故相等。
            2、对于非字符串
                equals方法对于字符串来说是比较内容的,而对于非字符串来说是比较,其指向的对象是否相同的。
                == 比较符也是比较指向的对象是否相同的也就是对象在对内存中的的首地址。
            3、基本类型比较
            (字符类型char,布尔类型boolean以及数值类型byte、short、int、long、float、double)
                只能用==比较,不能用equals
            4、包装类比较
            (Character、Boolean、Byte、Short、Integer、Long、Float、Double)
            装箱:基本数据类型转换为包装类
            拆箱:包装类转换为基本数据类型
                ==是比较地址的,equals是比较内容的
     
     
        hashcode()拓展:    
        为什么还要用equals和hashcode一起比较?    
        重写的equal比较的比较全面比较复杂,效率比较低;hashcode只需要生成一个hash值进行比较就可以了,效率很高。
        hashCode()并不是完全可靠,有时候不同的对象他们生成的hashcode也会一样(生成hash值得公式可能存在的问题),所以hashCode()只能说是大部分时候可靠,并不是绝对可靠,所以我们可以得出:
             1.equal()相等的两个对象他们的hashCode()肯定相等,也就是用equal()对比是绝对可靠的。
             2.hashCode()相等的两个对象他们的equal()不一定相等,也就是hashCode()不是绝对可靠的。
        解决方案是:每当需要对比的时候,首先用hashcode先去对比,如果hashcode不一样,则表示这两个对象肯定不相等,如果hashcode相同,再比较它们的equal,如果相同,则真相同,提高了效率。
            
    HashMap的扩容:
            HashMap中有两个重要的参数:初始容量大小和加载因子(loadFactor的默认值为0.75),初始容量大小是创建时给数组分配的容量大小,默认值为16,用数组容量大小乘以加载因子得到一个值,一旦数组中存储的元素个数超过该值就会调用rehash方法将数组容量增加到原来的两倍,专业术语叫做扩容。
            在做扩容的时候会生成一个新的数组,原来的所有数据需要重新计算哈希码值重新分配到新的数组,所以扩容的操作非常消耗性能。
     
    展开全文
  • hashMap底层原理

    2019-11-27 12:21:43
    HashMap底层实现采用了哈希表,这是一种非常重要的数据结构。对于我们以后理解很多技术都非常有帮助(比如:redis数据库的核心技术和HashMap一样 数据结构中由数组和链表来实现对数据的存储,他们各有特点。 (1) 数组...

    HashMap底层实现采用了哈希表,这是一种非常重要的数据结构。对于我们以后理解很多技术都非常有帮助(比如:redis数据库的核心技术和HashMap一样

    数据结构中由数组和链表来实现对数据的存储,他们各有特点。

    (1) 数组:占用空间连续。 寻址容易,查询速度快。但是,增加和删除效率非常低。

    (2) 链表:占用空间不连续。 寻址困难,查询速度慢。但是,增加和删除效率非常高。

    那么,我们能不能结合数组和链表的优点(即查询快,增删效率也高)呢? 答案就是“哈希表”。 哈希表的本质就是“数组+链表”。

    ▪ Hashmap基本结构讲解

    哈希表的基本结构就是“数组+链表”。打开HashMap源码,发现有如下核心内容:

    其中的Node<K, V>[] table 就是HashMap的核心数组结构,我们也称之为“位桶数组”。我们再继续看Node是什么:

    静态内部类Node
    一个Node类存储了:

    1. key:键对象

    2. value:值对象

    3. next:下一个节点

    4. hash: 键对象的hash值

    显然每一个Node对象就是一个单向链表结构,我们使用图形表示一个Node对象的典型示意:

    HashMap的简单示意图:

    ▪ 存储数据过程put(key,value)

    明白了HashMap的基本结构后,我们继续深入学习HashMap如何存储数据。此处的核心是如何产生hash值,该值用来对应数组的存储位置。

    我们的目的是将”key-value两个对象”成对存放到HashMap的Node[]数组中。参见以下步骤:

    (1) 获得key对象的hashcode

    首先调用key对象的hashcode()方法,获得hashcode。

    (2) 根据hashcode计算出hash值(要求在[0, 数组长度-1]区间)

    hashcode是一个整数,我们需要将它转化成[0, 数组长度-1]的范围。我们要求转化后的hash值尽量均匀地分布在[0,数组长度-1]这个区间,减少“hash冲突”

    i. 一种极端简单和低下的算法是:

    hash值 = hashcode/hashcode;

    也就是说,hash值总是1。意味着,键值对对象都会存储到数组索引1位置,这样就形成一个非常长的链表。相当于每存储一个对象都会发生“hash冲突”,HashMap也退化成了一个“链表”。

    ii. 一种简单和常用的算法是(相除取余算法):

    hash值 = hashcode%数组长度

    这种算法可以让hash值均匀的分布在[0,数组长度-1]的区间。 早期的HashTable就是采用这种算法。但是,这种算法由于使用了“除法”,效率低下。JDK后来改进了算法。首先约定数组长度必须为2的整数幂,这样采用位运算即可实现取余的效果:hash值 = hashcode&(数组长度-1)。

    iii. 采用JDK源码中的hash算法:

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    

    (3) 生成Node对象

    如上所述,一个Node对象包含4部分:key对象、value对象、hash值、指向下一个Node对象的引用。我们现在算出了hash值。下一个Node对象的引用为null。

    (4) 将Node对象放到table数组中

    如果本Node对象对应的数组索引位置还没有放Node对象,则直接将Node对象存储进数组;如果对应索引位置已经有Node对象,则将已有Node对象的next指向本Node对象,形成链表。

    总结如上过程:

    当添加一个元素(key-value)时,首先计算key的hash值,以此确定插入数组中的位置,但是可能存在同一hash值的元素已经被放在数组同一位置了,这时就添加到同一hash值的元素的后面,他们在数组的同一位置,就形成了链表,同一个链表上的Hash值是相同的,所以说数组存放的是链表。 JDK8中,当链表长度大于8时,链表就转换为红黑树,这样又大大提高了查找的效率。

    ▪ 取数据过程get(key)

    我们需要通过key对象获得“键值对”对象,进而返回value对象。明白了存储数据过程,取数据就比较简单了,参见以下步骤:

    (1) 获得key的hashcode,通过hash()散列算法得到hash值,进而定位到数组的位置。

    (2) 在链表上挨个比较key对象。 调用equals()方法,将key对象和链表上所有节点的key对象进行比较,直到碰到返回true的节点对象为止。

    (3) 返回equals()为true的节点对象的value对象。

    明白了存取数据的过程,我们再来看一下hashcode()和equals方法的关系:

    Java中规定,两个内容相同(equals()为true)的对象必须具有相等的hashCode。因为如果equals()为true而两个对象的hashcode不同;那在整个存储过程中就发生了悖论。

    ▪ 扩容问题

    HashMap的位桶数组,初始大小为16。实际使用时,显然大小是可变的。如果位桶数组中的元素达到(0.75*数组 length), 就重新调整数组大小变为原来2倍大小。

    扩容很耗时。扩容的本质是定义新的更大的数组,并将旧数组内容挨个拷贝到新数组中。

    ▪ JDK8将链表在大于8情况下变为红黑二叉树

    JDK8中,HashMap在存储一个元素时,当对应链表长度大于8时,链表就转换为红黑树,这样又大大提高了查找的效率。

    展开全文
  • HashMap底层原理.md

    2021-06-16 10:57:05
    HashMap底层原理.md

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,113
精华内容 2,045
关键字:

hashmap底层原理