先看一下下面的这张图(哈希表的一种实现)(奇丑无比0.0)
散列(Hash):散列的思想是将条目(键/值对)分布在一系列存储桶(bucket)中。给定一个键(key),再通过算法计算出索引(index),该索引显示条目的位置。通常可以分为两步完成:
hash = hashfunc(key) //计算key对应hash值
index = hash % array_size //通过取模"%"使index始终位于0~array_size-1(即索引范围始终位于数组中)
负载因子(factor):负载因子=已经插入的数据数量/可以插入的数据总量。对于固定数量的桶,查找时间会随着条目数量的增加而增加,因此无法实现所需的恒定时间。为了实现这个恒定时间,可以在达到负载因子时进行扩容。
扩容:强制散列所有条目,array_size变大了(通常是两倍),从而使index的范围变广了,hash值不同但index相同的概率减小了(如17、33对16取模结果为1、1;而17、33对32取模结果为17、1,entry分布在bucket中分布更均匀了是不是),那index依然相同该怎么办呢,这里(仅)介绍一下链地址法
System.out.println(17%32);
链地址法:长得像上面图示那样,哈希表操作的时间是找到bucket的时间(是常数)加上列表操作的时间。bucket里可以放第一个条目(entry)的索引,也可以放第一个条目,HashMap属于后者,这样做是因为在大多数情况下,指针遍历的次数可以减少1,从而提高访问的缓存效率(该缓存指的是内存,因为链表无法使用CPU缓存,链表无法使用CPU缓存是因为CPU缓存需要连续的地址空间)但缺点是空bucket与entry占用相同的空间(网上是这么说的,但是根据HashMap的情况来看,HashMap会初始化entry数组,但存储在数组内的entry没有空参构造函数,所以最终的结果为null而非初始化的entry,所以缺点方面还是不要太钻牛角尖0.0)
时钟周期(有点跑题了0.0):时钟周期是CPU工作的最小时间单位,1 / 时钟周期 =工作频率,而CPU缓存工作所需要的时钟周期少,内存需要的时钟周期多,由此可见CPU在处理CPU缓存和内存中的相同任务时,所用的时间是不一样的(这也是为什么访问数组比访问链表快的原因之一!详见数组、链表对于内存及CPU访问缓存机制)