【原创】重写hashCode()方法可能引起的内存溢出问题

 

  我们在设计开发工程中,经常会用到HashMap,HashSet,HashTable等Java类,如果查看这些类的add,put等方法时,会发现这些方法都是基于参数对象的hashCode方法返回值经过一系列运算后,确定对象在哈希表中的位置。如果我们没有重写hashCode方法且所有父类也没有重写过Object的该方法,hashCode方法将使用Object类中native的hashCode方法,经验证,此方法产生的haseCode与子对象中的成员字段值无关系。

  下面通过一段代码,分析下如果重写hashCode方法,在使用不当时会怎样引起内存泄漏问题。

//代码1
public class HashSetOOM {
	public static void main(String[] args) {
		HashSet<MyClassA> set=new HashSet<MyClassA>();
		for(int i=0;i<1000;i++){//如果你的虚拟机最大堆设置的比较大,可以调整这里的循环次数,多加几个对象进去
			MyClassA a=new MyClassA();
			a.data=new byte[2048000];
			a.x=i;
			set.add(a);
			a.x=i+1;
			set.remove(a);
			a.hashCode();
		}
	}
}
/**
 * 我们定义一个类,该为了让该类的对象占用较大的空间,字段中使用一个byte[]数组。
 * 此处重写了hasecode方法,方法使用eclipse自动生成代码。
 * 同时重写equals方法,同样使用eclipse自动生成的代码
 */
class MyClassA{
	byte[] data;
	int x;
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + Arrays.hashCode(data);
		result = prime * result + x;
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		MyClassA other = (MyClassA) obj;
		if (!Arrays.equals(data, other.data))
			return false;
		if (x != other.x)
			return false;
		return true;
	}
}

   通常,运行上边代码1,都会出现OOM情况。虽然,我们在循环的时候调用了set的remove方法。但是,事实情况下对象并没有从set中删除掉。我们查看add和remove方法时,会发现查找对象所在hash表中的位置时,是使用了这样一条逻辑:对象的“hashcode&tab.length-1”,操作是实际是表中该位置上的对象。如果,我们按上述代码修改后,hashcode将会发生改变,此时在进行remove的时候,可能将会出现两种情况:

  1、由于找不到此hashcode对应的index,remove不做任何操作。

  2、从hashset中删除了一个与之修改后生成对象的hashcode相等的对象。例如有这样一个类:

 

class Persion{
	String name;//姓名
	int age;//年龄
	int weight;//体重
	public Persion(String name,int age,int weight){
		this.name=name;
		this.age=age;
		this.weight=weight;
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result + weight;
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Persion other = (Persion) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (weight != other.weight)
			return false;
		return true;
	}
}
   我们定义了如下两个对象

 

Person person1=new Person("张三",60,70);//一个年龄为60岁,体重为70公斤的张三

Person person2=new Person("张三",71,60);//一个年龄为71岁,体重为60公斤的张三

将这两个对象都add到hashset中,后发现,person2的年龄错了,要修改成70岁,于是乎我们按上边的代码执行时,person1被从hashset中remove掉了,只是我们不想看到的。

 

  经过查阅资料及求助于各大搜索引擎发现,这种问题在设计层面竟然没有好的办法可以规避。出问题的点就在:重写hashcode方法后,修改成员字段数值会影响hashcode值,进而影响到对象在哈希表中的位置,致使remove方法无法找到原来的对象,但是,对象有在哈希表中有引用,hash对象又没有被释放。垃圾回收器发现其还有引用,又不会回收该对象。于是,内存泄漏了。

  总结:重写hashcode方法的类对象,在放入hash表后,一定要谨慎操作对象字段属性值。如果要修改,先从哈希表中删除该对象,修改后再放入哈希表。

  

 

 

<think>好的,我需要回答用户关于何时需要重写Java HashMap中的hashCode和equals方法问题。首先,我得回忆一下HashMap的工作原理。HashMap通过键的哈希码来存储和检索值,所以当用户自定义对象作为键时,默认的hashCode和equals可能不符合需求。 用户提供的引用中有几个关键点。引用[1]和[2]提到,Object类的默认equals比较的是内存地址,而hashCode生成的是内存地址相关的值。如果两个逻辑上相等的对象因为地址不同而被视为不同,就会导致HashMap无法正确工作。比如,用户自定义的Key类如果没有重写这两个方法,即使id相同,HashMap也会认为它们是不同的键,导致重复插入,如引用[3]的例子。 接着,引用[4]和[5]强调,重写hashCode需要确保相等的对象有相同的哈希值,而equals则用于处理哈希冲突时的精确比较。如果只重写其中一个,HashMap可能出现无法检索到已存在的键或者存储重复键的情况。 现在需要将这些点整理成用户容易理解的回答。首先说明默认方法的局限性,然后解释何时需要重写:当自定义对象作为键,并且希望逻辑相等而非地址相等时。还要提到集合框架的使用场景,比如HashSet内部也依赖HashMap,同样需要重写这些方法。 还要注意示例和代码引用的正确性。比如引用[3]中的例子,虽然Key类重写hashCode,但没重写equals,导致containsKey检查失败,最终导致内存泄漏。这个例子很好地说明了问题。 最后,生成相关问题时要覆盖重写的原因、不重写的后果、方法之间的关系,以及实际应用中的例子。确保问题能帮助用户深入理解主题。</think>### 何时需要重写HashMap中的hashCode和equals方法? 在Java中,当**自定义对象作为HashMap的键**时,必须重写`hashCode`和`equals`方法以确保逻辑正确性。以下是具体场景和原因: --- #### 1. **默认方法的局限性** - **默认行为**: Object类的`equals`方法比较对象内存地址(`this == obj`),而`hashCode`生成与内存地址相关的哈希值[^1]。 - **问题**: 若两个对象逻辑相等但内存地址不同(例如两个`Key(id=5)`实例),默认方法会将其视为不同键,导致HashMap无法正确检索或覆盖值[^2]。 --- #### 2. **需要重写的场景** - **自定义对象作为键**: 例如,用`Student(id, name)`类作为键存储学生成绩。若需通过`id`判断相等性,则需重写`hashCode`和`equals`。 - **集合框架的使用**: `HashSet`、`HashMap`等依赖哈希表的集合,要求逻辑相等的对象必须具有相同的哈希值[^4]。 --- #### 3. **不重写的后果** - **哈希冲突导致数据重复**: 若仅重写`hashCode`而不重写`equals`,哈希冲突时可能无法正确比较对象(如引用[3]中的`Key`类),导致重复存储相同逻辑的键。 - **内存泄漏或性能问题**: 如引用[3]的示例中,未重写`equals`导致`containsKey`检查失败,HashMap会不断插入新键,最终内存溢出。 --- #### 4. **重写原则** - **一致性**: 若`a.equals(b)`为`true`,则`a.hashCode() == b.hashCode()`必须成立[^4]。 - **哈希分布均匀性**: `hashCode`应尽量减少哈希冲突,提升HashMap性能[^5]。 - **覆盖equals的逻辑**: 例如,通过对象的属性(如`id`或`name`)判断相等性。 --- #### 示例代码 ```java class Student { private int id; private String name; @Override public int hashCode() { return Objects.hash(id, name); // 基于属性生成哈希值 } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Student other = (Student) obj; return id == other.id && Objects.equals(name, other.name); // 属性比较 } } ``` ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值