散列算法与散列码(实例讲解)
发布时间 - 2026-01-11 02:56:09 点击率:次一、引入
/**
* Description:新建一个类作为map的key
*/
public class Groundhog
{
protected int number;
public Groundhog(){
}
public Groundhog(int number)
{
this.number = number;
}
@Override
public String toString()
{
return "Groundhog{" + "number=" + number + '}';
}
}
/**
* Description:新建一个类作为map的value
*/
public class Prediction
{
private boolean shadow=Math.random() > 0.5;
@Override
public String toString()
{
if (shadow) return "Six more weeks of Winter";
else return "Early Spring!";
}
}
/**
* Description:测试类
*/
public class SpringDetector
{
public static void detectSpring(Class grondHotClass) throws Exception{
Constructor constructor = grondHotClass.getConstructor(new Class[]{int.class});
Map map=new HashMap();
for (int i=0;i<10;i++){
map.put(constructor.newInstance(new Object[]{new Integer(i)}),new Prediction());
}
System.out.println("map="+map);
Groundhog groundhog=(Groundhog)constructor.newInstance(new Object[]{new Integer(3)});
System.out.println(groundhog);
if (map.containsKey(groundhog)) {//查找这个key是否存在
System.out.println((Prediction)map.get(groundhog));
}else {
System.out.println("key not find:"+groundhog);
}
}
public static void main(String[] args)
{
try {
detectSpring(Groundhog.class);
} catch (Exception e) {
e.printStackTrace();
}
}
}
看这个结果,问题就来了,map中明明存在Groudhog{number=3},为什么结果显示的是Key not find呢??问题出在哪里呢?原来是Groudhog类没有重写hashCode()方法,所以这里是使用Object的hashCode()方法生成散列码,而他默认是使用对象的地址计算散列码。因此,由Groudhog(3)生成的第一个实例的散列码与Groudhog(3)生成的散列码是不同的,所以无法查找到 key。但是仅仅重写hashCode()还是不够的,除非你重写equals()方法。原因在于不同的对象可能计算出同样的hashCode的值,hashCode 的值并不是唯一的,当hashCode的值一样时,就会使用equals()判断当前的“键”是否与表中的存在的键“相同”,即“
如果两个对象相同,那么他们的hashCode值一定相同。 如果两个对象的hashCode值相同,他们不一定相同。 正确的equals()方法必须满足下列5个条件: 1、自反性: x.equals(x) 一定成立。 2、对称性: 如果x.equals(y)成立,那么y.equals(x)也一定成立。 3、传递性:如果x.equals(y)=true ,y.equals(z)=true,那么x.equals(z)=true也成立。 4、一致性:无论调用x.equal(y)多少次,返回的结果应该保持一致。 5、对任何不是null的x,x.equals(null)一定返回false。二、理解hashCode()
散列的价值在于速度:散列使得查询得以快速执行。由于速度的瓶颈是对“键”进行查询,而存储一组元素最快的数据结构是数组,所以用它来代表键的信息,注意:数组并不保存“键”的本身。而通过“键”对象生成一个数字,将其作为数组的下标索引。这个数字就是散列码,由定义在Object的hashCode()生成(或成为散列函数)。同时,为了解决数组容量被固定的问题,不同的“键”可以产生相同的下标。那对于数组来说?怎么在同一个下标索引保存多个值呢??原来数组并不直接保存“值”,而是保存“值”的 List。然后对 List中的“值”使用equals()方法进行线性的查询。这部分的查询自然会比较慢,但是如果有好的散列函数,每个下标索引只保存少量的值,只对很少的元素进行比较,就会快的多。
不知道大家有没有理解我上面在说什么。不过没关系,下面会有一个例子帮助大家理解。不过我之前一直被一个问题纠结:为什么一个hashCode的下标存的会有多个值?因为hashMap里面只能有唯一的key啊,所以只能有唯一的value在那个下标才对啊。这里就要提出一个新的概念哈希冲突的问题,借用网上的一个例子:
比如:数组的长度是5。这时有一个数据是6。那么如何把这个6存放到长度只有5的数组中呢。按照取模法,计算6%5,结果是1,那么就把6放到数组下标是1的位置。那么,7就应该放到2这个位置。到此位置,哈希冲突还没有出现。这时,有个数据是11,按照取模法,11%5=1,也等于1。那么原来数组下标是1的地方已经有数了,是6。这时又计算出1这个位置,那么数组1这个位置,就必须储存两个数了。这时,就叫哈希冲突。冲突之后就要按照顺序来存放了。所以这里Java中用的解决方法就是在这个hashCode上存一个List,当遇到相同的hashCode时,就往这个List里add元素就可以了。这才是hash原理的精髓所在啊!哈哈、纠结我一天。
三、HashMap的性能因子
容量(Capacity):散列表中的数量。
初始化容量(Initial capacity):创建散列表时桶的数量。HashMap 和 HashSet都允许你在构造器中制定初始化容量。
尺寸(Size):当前散列表中记录的数量。
负载因子(Load factor):等于"size/capacity"。负载因子为0,表示空的散列表,0.5表示半满的散列表,依次类推。轻负载的散列表具有冲突少、适宜插入与适宜查询的特点(但是使用迭代器遍历会变慢)。HashMap和hashSet的构造器允许你制定负载因子。这意味着,当负载达到制定值时,容器会自动成倍的增加容量,并将原有的对象重新分配,存入新的容器内(这称为“重散列”rehashing)。HashMap默认的负载因子为0.75,这很好的权衡了时间和空间的成本。
备注:为使散列分布均衡,Java的散列函数都使用2的整数次方来作为散列表的理想容量。对现代的处理器来说,除法和求余是最慢的动作。使用2的整数次方的散列表,可用掩码代替除法。因为get()是使用最多的操作,求余数的%操作是其开销的大部分,而使用2的整数次方可以消除此开销(也可能对hashCode()有些影响)
四、怎么重写hashCode()
现在的IDE工具中,一般都能自动的帮我们重写了hashCode()和equals()方法,但那或许并不是最优的,重写hashCode()有两个原则:
必须速度快,并且必须有意义。也就是说,它必须基于对象的内容生成散列码。
应该产生分布均匀的散列码。如果散列码都集中在一块,那么在某些区域的负载就会变得很重。
下面是怎么写出一份像样的hashCode()的基本指导:
1、给int变量result 赋予某个非零值常量,例如 17。
2、为每个对象内每个有意义的属性f (即每个可以做equals()的属性)计算出一个 int 散列码c:
3、合并计算得到的散列值:result=37*result+c;
4、返回 result;
5、检查hashCode()最后生成的结果,确保相同的对象有相同的散列码。
五、自定义HashMap
下面我们将自己写一个hashMap,便于了解底层的原理,大家如果看的懂下面的代码,也就很好的理解了hashCode的原理了。
/**
* Description:首先新建一个类作为map中存储的对象并重写了hashCode()和equals()方法
*/
public class MPair implements Map.Entry,Comparable
{
private Object key,value;
public MPair(Object key,Object value)
{
this.key = key;
this.value=value;
}
@Override
public int compareTo(Object o)
{
return ((Comparable)key).compareTo(((MPair)o).key);
}
@Override
public Object getKey()
{
return key;
}
@Override
public Object getValue()
{
return value;
}
@Override
public int hashCode()
{
int result = key != null ? key.hashCode() : 0;
result = 31 * result + (value != null ? value.hashCode() : 0);
return result;
}
@Override
public boolean equals(Object o)
{
return key.equals(((MPair)o).key);
}
@Override
public Object setValue(Object v)
{
Object result=value;
this.value=v;
return result;
}
@Override
public String toString()
{
return "MPair{" + "key=" + key + ", value=" + value + '}';
}
public class SimpleHashMap extends AbstractMap
{
private static final int SZ=3;//定一个初始大小的哈希表容量
private LinkedList[] linkedLists=new LinkedList[SZ];//建一个hash数组,用linkedList实现
public Object put(Object key,Object value){
Object result=null;
int index=key.hashCode() % SZ;//对key的值做求模法求出index
if (index<0) index=-index;
if (linkedLists[index]==null) linkedLists[index]=new LinkedList();//如果这个index位置没有对象,就新建一个
LinkedList linkedList = linkedLists[index];//取出这个index的对象linkedList
MPair mPair = new MPair(key,value);//新建要存储的对象mPair
ListIterator listIterator = linkedList.listIterator();
boolean found =false;
while (listIterator.hasNext()){//遍历这个index位置的List,如果查找到跟之前一样的对象(根据equals来比较),则更新那个key对应的value
Object next = listIterator.next();
if (next.equals(mPair)){
result = ((MPair) next).getValue();
listIterator.set(mPair);//更新动作
found=true;
break;
}
}
if (!found) linkedLists[index].add(mPair);//如果没有找到这个对象,则在这index的List对象上新增一个元素。
return result;
}
public Object get(Object key){
int index = key.hashCode() % SZ;
if (index<0) index=-index;
if (linkedLists[index]==null) return null;
LinkedList linkedList = linkedLists[index];
MPair mPair=new MPair(key,null);//新建一个空的对象值,因为equals()的比较是看他们的key是否相等,而在List中的遍历对象的时候,是通过key来查找对象的。
ListIterator listIterator = linkedList.listIterator();
while (listIterator.hasNext()){
Object next = listIterator.next();
if (next.equals(mPair)) return ((MPair)next).getValue();//找到了这个key就返回这个value
}
return null;
}
@Override
public Set<Entry> entrySet()
{
Set set=new HashSet();
for (int i=0;i<linkedLists.length;i++){
if (linkedLists[i]==null) continue;
Iterator iterator = linkedLists[i].iterator();
while (iterator.hasNext()){
set.add(iterator.next());
}
}
return set;
}
public static void main(String[] args)
{
SimpleHashMap simpleHashMap=new SimpleHashMap();
simpleHashMap.put("1", "1");
simpleHashMap.put("2", "2");
simpleHashMap.put("3","3");
simpleHashMap.put("4","4");//这里有四个元素,其中key是1和key是4的index是一样的,所以index为1的List上面存了两个元素。
System.out.println(simpleHashMap);
Object o = simpleHashMap.get("1");
System.out.println(o);
Object o1 = simpleHashMap.get("4");
System.out.println(o1);
}
}
六、结语
不知道大家理解了没?整了我一天,终于还算大概理解了其中的原理了。文笔比较粗糙,大家凑活看吧,毕竟,不会做饭的作家不是好程序员啊!哈哈...... 或者,可能我有很多理解的不到位的地方,还请大家不吝指教!
# 散列算法
# 重写
# 新建一个
# 就会
# 遍历
# 很好
# 会有
# 计算出
# 多个
# 能有
# 写了
# 求出
# 有意义
# 数了
# 的是
# 不吝指教
# 他们的
# 来了
# 还没有
# 有个
# 在这个
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
Laravel Admin后台管理框架推荐_Laravel快速开发后台工具
浅谈redis在项目中的应用
Laravel项目结构怎么组织_大型Laravel应用的最佳目录结构实践
Laravel如何集成Inertia.js与Vue/React?(安装配置)
EditPlus中的正则表达式实战(6)
WordPress 子目录安装中正确处理脚本路径的完整指南
昵图网官网入口 昵图网素材平台官方入口
高端智能建站公司优选:品牌定制与SEO优化一站式服务
Laravel怎么实现一对多关联查询_Laravel Eloquent模型关系定义与预加载【实战】
高端云建站费用究竟需要多少预算?
香港服务器网站测试全流程:性能评估、SEO加载与移动适配优化
jQuery validate插件功能与用法详解
JavaScript Ajax实现异步通信
Chrome浏览器标签页分组怎么用_谷歌浏览器整理标签页技巧【效率】
如何在万网利用已有域名快速建站?
Laravel如何配置中间件Middleware_Laravel自定义中间件拦截请求与权限校验【步骤】
网站制作软件有哪些,制图软件有哪些?
北京网站制作费用多少,建立一个公司网站的费用.有哪些部分,分别要多少钱?
美食网站链接制作教程视频,哪个教做美食的网站比较专业点?
Claude怎样写约束型提示词_Claude约束提示词写法【教程】
怎么用AI帮你设计一套个性化的手机App图标?
如何在搬瓦工VPS快速搭建网站?
Laravel如何使用Socialite实现第三方登录?(微信/GitHub示例)
Laravel Session怎么存储_Laravel Session驱动配置详解
潮流网站制作头像软件下载,适合母子的网名有哪些?
高防服务器租用如何选择配置与防御等级?
Laravel怎么使用Session存储数据_Laravel会话管理与自定义驱动配置【详解】
C语言设计一个闪闪的圣诞树
,网页ppt怎么弄成自己的ppt?
PythonWeb开发入门教程_Flask快速构建Web应用
Laravel Fortify是什么,和Jetstream有什么关系
手机怎么制作网站教程步骤,手机怎么做自己的网页链接?
javascript事件捕获机制【深入分析IE和DOM中的事件模型】
如何快速打造个性化非模板自助建站?
Swift中switch语句区间和元组模式匹配
如何在企业微信快速生成手机电脑官网?
Laravel怎么实现软删除SoftDeletes_Laravel模型回收站功能与数据恢复【步骤】
如何实现建站之星域名转发设置?
网站制作企业,网站的banner和导航栏是指什么?
Laravel如何使用Blade模板引擎?(完整语法和示例)
Laravel怎么配置.env环境变量_Laravel生产环境敏感数据保护与读取【方法】
,南京靠谱的征婚网站?
韩国代理服务器如何选?解析IP设置技巧与跨境访问优化指南
微信小程序 闭包写法详细介绍
Laravel如何发送邮件_Laravel Mailables构建与发送邮件的简明教程
如何用花生壳三步快速搭建专属网站?
惠州网站建设制作推广,惠州市华视达文化传媒有限公司怎么样?
Laravel怎么配置S3云存储驱动_Laravel集成阿里云OSS或AWS S3存储桶【教程】
谷歌浏览器如何更改浏览器主题 Google Chrome主题设置教程
php静态变量怎么调试_php静态变量作用域调试技巧【解答】

