哈希表也叫散列表
它的底层是通过数组也存储元素的,他里面有个hash函数,将一个key传给hash函数,他会利用这个key生成这个key对应的索引,他的复杂度是O(1),所以得用哈希表来去完成映射的添加,搜索,删除的话,总的来说时间复杂度是O(1)级别的.
hash函数也叫散列函数
哈希表内部的数组元素,很多地方也叫Bucket(桶),整个数组叫个Buckets或者BucketArray
哈希冲突--也叫哈希碰撞
就是二个不同的key,经过哈希函数,计算出相同的结果.
解决方法:
1 开放定址法:按照一定规则向其它地址探测,直到遇到空桶.比如线性探测,假如现在的索引是3,3有值就向4探测,如果还有就继续向下,还比如平方探测.就是1 2 3 ...的平方进行探测.
2:再哈希法,设计多个哈希函数
3:链地址法,HashMap就是这样操作的
JDK1.8是怎么解决哈希冲突的
默认使用的是单身链表将元素串起来
在添加元素时,可能会由单向链表转为红黑树来存储元素
比如当哈希表的容量>=64且单向链表的节点数量大于8时就会由单向链表转为红黑树来存储元素
当红黑树节点数量少到一定程度时,又会转为单向链表
所以JDK1.8中的哈表是由链表加上红黑树来解决冲突的.
为会么使用单向链表而不用双向
1.当哈希冲突的时候会往链表尾部里添加节点,为什么添加到尾部,是因为当冲突的时候它要在链表里不断对比key是不是一样,所以要一直对比下去,找到了就替换没有找到就添加到尾部.
2.单链表比双链表少一个指针,可以节省内存空间
哈希函数中实现步骤是怎样的(hashCode)
1:先生成key的哈希值,必须是整数
2:让这个哈希值跟数组的大小进行相关运算,生成一个索引值.(hashCode(key)%table.length)
2.1:为了提高效率,官方是通过&运算来取代%运算的,前提是数组的长度必须设计为2的幂
2.2:&运算的特点,二个都为1结果才为1,其它都为0;(hashCode(key)&(table.length-1))
2.3:为什么要-1,因为长度为2的的幂,比如16二进制就是10000,减一个1就变成了01111,这个时候通过hash值来跟01111做&运算的话得出的结果必然是小于等于01111.比如算出的hash值为1000101010 这个时候table的长度为01111这个时候算出来的值就是1010.肯定不会超过数组的长度-1;
良好的哈希函数设计:让哈希值更加均匀分布 ,减少哈希冲突次数 ,从而提高哈希表的性能
如果生成key的哈希值
key的种类常见的有:整数,浮点数,字符串,自定义对象,不同种类的key哈希值生成的方式不一样,但目标是一致的,尽量让每个key的哈希值是唯一的,尽量让Key的所有信息参加运算.
在Java中的Hashmap的Key必须实现hashCode,equals方法,也允许key为null
整数: 直接把整数当哈希值
浮点数:Float.floatToIntBits(float),将浮点数转为int.将存储的二进制转为整数
Long: Long是8个字节占64位 官方算法是((int)(value^(value>>>32)))意思就是先将Long类型的value无符号右移32位,再和value做^运算,就是高32位和低32位混合在一起计算出哈希值.强制转换成int就是把高32位给忽略掉
^运算(异或):相同为0不同为1---- |或运算: 有1个为1就是1
Double: 就是将Double先转为Long,然后操作跟Long一样
字符串: 遍历字符串每次都要执行这个行代码hashcode= hashcode*31+char(遍历出来的) 31=(2>>5)-1
Object(自定义对象): 如果不重写hashCode方法,则是用内存地址来计算的,如果有特殊需求则可以重写hashCode方法,但是最好利用自定义对象里所有成员变量来计算hashCode值.比如自定义一个Person对象,里面有age,height,name则可以这样写
int hashCode = Integer.hashCode(age);
hashCode = hashCode*31+Float.hashCode(height);
hashCode = hashCode*31+(name!=null)?name.hashCode():0);
return hashCode;
如果自己实现了hashCode方法,最好复写equals方法,用于判断key是不是同一个key但是要遵循几个准则
1.必须保证equals方法为true的那么他们的hashCode必须要相等