集合與映射中所存物件屬性改變的問題
来源:1-8 更多哈希冲突的处理方法
慕用8103342
2021-12-01 17:01:45
講師你好:
若我將物件存儲到TreeSet , TreeMap, HashMap(key部分) 底層為樹的容器中,因為節點保存的是引用數據類型,也就是pointer,若是在後續的代碼中變更所儲存物件中的屬性,此時的樹會自動調整其結構以滿足二分搜索樹(紅黑樹)的性質? (物件屬性改變hashcode也會更動數值?) 還是集合或映射(Key值部分)所存儲的數據是要滿足不可改變的條件。
1回答
非常非常好的问题。
在使用 TreeSet,TreeMap 或者 HashMap 的时候,开发者不应该去 update key。但是,从程序的角度,TreeSet,TreeMap 以及 HashMap 没有阻止开发者这么做。所以语法上你可以这么做,但是逻辑上不应该这样做。这就好像语法上,设计一个类,你可以让其 compareTo 等于 0 的逻辑和 equals 的逻辑不等价,但是你不应该这样做。
原因就是你说的,这些容器类不会因为你对 key 的 update,而自动调整结构(HashMap 也不会自动更新哈希值)。
正确的做法是,如果你想 update 一个 key,应该先删除原先的 key 对应的数据,再把新的 key 相应的数据重新添加进去。这不会导致复杂度的改变,虽然进行了两次操作。
==========
要想验证这一点,使用哈希表是非常容易的。
可以试验一下下面的代码:
public class Student {
private String name;
public Student(String name){
this.name = name;
}
@Override
public boolean equals(Object student){
if(this == student) return true;
if(student == null) return false;
if(this.getClass() != student.getClass()) return false;
Student another = (Student)student;
return this.name.equals(another.name);
}
@Override
public int hashCode(){
return name.hashCode();
}
@Override
public String toString(){
return "Student : " + name;
}
public static void main(String[] args) {
HashMap<Student, Integer> map = new HashMap<>();
Student a = new Student("A");
Student b = new Student("B");
map.put(a, 1);
map.put(b, 2);
a.name += "A";
Student aa = new Student("AA");
map.put(aa, 3);
map.forEach((k, v) -> System.out.println("key : " + k + " value : " + v));
System.out.println();
}
}学生类,只有 name 属性,哈希值就是学生名称 name 这个字符串的哈希值。
在 main 中,我先把 name = "A" 的 Student 和 name = "B" 的 Student 添加进哈希表。然后,将 name = "A" 的学生名字改成 "AA"。
现在,我再添加另外一个叫 "AA" 的学生,理论上,哈希表中有两个叫 "AA" 的学生,后一个学生应该覆盖前一个学生。但是,这个操作后,打印哈希表,有三项,其中有两个叫 "AA" 的学生存在在哈希表中。
==========
为什么?因为第一个学生存在了哈希值是 "A" 的 bucket 下。改变这个学生的名字,哈希值不会动态调整,依然在 "A" 的 bucket 下。来了一个叫 "AA" 的学生,和 "A" 的哈希值不产生哈希冲突,所以,直接存在了 "AA" 的 bucket 下。于是,两个叫 "AA" 的学生存在了。
所以,你也可以看到,这相当于引入了 bug。因为 Map 中的数据,key 不应该相等,但通过这个过程,Map 中存在了 key 相等的数据。所以这种操作是应该避免的。
继续加油!:)
相似问题
回答 1