HashMap

HashMap
链表和数组的特点
  • 链表和数组作为算法中的两个基本数据结构,在程序设计过程中经常用到。尽管两种结构都可以用来存储一系列的数据,但又各有各的特点。

  • 数组的优势,在于可以方便的遍历查找需要的数据。在查询数组指定位置(如查询数组中的第4个数据)的操作中,只需要进行1次操作即可,时间复杂度为O(1)。但是,这种时间上的便利性,是因为数组在内存中占用了连续的空间,在进行类似的查找或者遍历时,本质是指针在内存中的定向偏移。然而,当需要对数组成员进行添加和删除的操作时,数组内完成这类操作的时间复杂度则变成了O(n)。

  • 链表的特性,使其在某些操作上比数组更加高效。例如当进行插入和删除操作时,链表操作的时间复杂度仅为O(1)。另外,因为链表在内存中不是连续存储的,所以可以充分利用内存中的碎片空间。

散列表
什么是散列表?

散列表也叫hash表 ,是根据关键码值而进行直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射也叫散列函数,存放记录的数组叫散列表。
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则成表M为Hash表,函数f(key)为哈希函数。

使用散列表的注意事项:
  • key-value成对数据添加到散列表

  • key不可以重复,value可以重复

  • key-value规定为一个条目(Entry)

  • 散列表中散列数组的大小称为容量

  • key-value数量称为size

  • size/容量称为加载因子,要小于75%,如果大于75%,会自动扩容

  • 自动扩容会影响put添加性能,可以事先通过设置初始容量,提高put的性能

  • 散列表的默认初始容量是16,默认负载因子是75%,建议不要修改负载因子

为什么要用散列表?
  • 数组的特点是:寻址容易,插入和删除困难;

  • 链表的特点是:寻址困难,插入和删除容易;

  • 散列表综合两者的特性,是一种寻址容易,插入和删除也容易的数据结构。散列表也叫哈希表

hashcode

String 类重写hashcode()方法,也就是先把String字符串转为字符数组,然后遍历每一个字符,根据每一个字符的Ascii码进行迭代计算。

 public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

hashcode计算公式:

s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]

  System.out.println("abcd".hashCode());//2987074
  System.out.println(97*31*31*31+98*31*31+99*31+100);//2987074

之所以使用 31,是因为他是一个奇素数。如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为与2相乘等价于移位运算(低位补0)。使用素数的好处并不很明显,但是习惯上使用素数来计算散列结果。 31 有个很好的性能,即用移位和减法来代替乘法,可以得到更好的性能: 31 * i == (i << 5) - i, 现代的 VM可以自动完成这种优化。

HashMap

HashMap是实现了Map接口的key-value集合,实现了所有map的操作,允许key和value为空,底层数据结构是哈希表结构(数组+链表+红黑树)(jdk1.8后添加了红黑树),不能保证映射的顺序,不能保证存入时的顺序能永久不变,并且HashMap不是线程安全的。

HashMap的数据结构

在这里插入图片描述

hash算法
static final int hash(Object key) {
    int h;
  	// 判断key是否为null, 如果为null,则直接返回0;
  	// 如果不为null,则返回(h = key.hashCode()) ^ (h >>> 16)的执行结果
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  • 首先根据key.hashcode()获取一个int类型的h值也就是hashcode值。
  • 然后将这个h值向右移位16位
  • 然后进行异或操作

这样操作的目的就是为了减少hash冲突。保证了对象的hashCode的32位值只要有一位发生改变,整个hash()返回值就会改变。尽可能的减少碰撞。

HashMap的存取实现
  • 存储(put操作):当程序试图将一个key-value对放入HashMap中时,程序首先会根据hash(key)返回值决定该Entry的存储位置:如果两个Entry的hash值相同,那么他们的存储位置就相同。详细流程:存储,也就是put操作,首先我们在存储的时候肯定存储的是一个键值对,在进行put操作时,首先会根据key调用hash(key)方法来确定在散列表中存储的位置,如果这个位置不存在,那就直接添加。如果这个位置已经存在,那么就根据equals()方法来判断这个key的值是否和已经存在位置上的key的值是否相同,如果相同,那个就覆盖这个位置上已经存在key-value对对象。如果key值和这个位置上的key值不相同,那么就是我们常说的hash冲突了。解决hash冲突的方法有三种:(1)开放定址法(2)拉链法 (3)再哈希法 在HashMap中解决hash冲突使用的就是拉链法,拉链法,就是将所有hash值相同的链接到一张链表中。接着上面,当hash冲突时说明这张链表上已经存在一个节点了,然后第二个key进来时会作为这张链表的第二个节点被存储进去。依次类推下去。当链表的元素数量大于8时,链表就会转变为红黑树。然后按照树的方式存储。
  • 读取(get操作):从HashMap中获取元素时首先根据hash(key),找到数组中对应的位置,然后通过key的equals方法在对应的链表中找到需要的元素。详细过程:在进行get操作时,首先会根据hash(key)方法找到存储对象的位置,然后获取到值对象,当然如果出现多个key的hash(key)值相同(hash冲突)情况,会通过equals方法比较链表中所有Entry对象中的key值,直到找到正确的节点,获取到正确的值。
  • 总结
    HashMap在底层将key-value当成一个整体来进行处理,这个整体就是一个Entry对象。通过一个Entry[]数组来保存所有的key-value对,当需要存储一个Entry对象时,会根据hash(key)来决定其在数组中的存储位置,再根据equals方法决定其在数组位置上链表的存储位置。

参考:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值