你们都知道,HashMap的是key-value(键值对)组成的,这个key既能够是基本数据类型对象,如Integer,Float,同时也能够是本身编写的对象,那么问题来了,这个做为key的对象是否可以改变呢?或者说key可否是一个可变的对象?若是能够该HashMap会怎么样?html
可变对象java
可变对象是指建立后自身状态能改变的对象。换句话说,可变对象是该对象在建立后它的哈希值(由类的hashCode()方法能够得出哈希值)可能被改变。shell
为了能直观的看出哈希值的改变,下面编写了一个类,同时重写了该类的hashCode()方法和它的equals()方法【至于为何要重写equals方法能够看博客:http://www.cnblogs.com/0201zcr/p/4769108.html】,在查找和添加(put方法)的时候都会用到equals方法。数组
在下面的代码中,对象MutableKey的键在建立时变量 i=10 j=20,哈希值是1291。安全
而后咱们改变实例的变量值,该对象的键 i 和 j 从10和20分别改变成30和40。如今Key的哈希值已经变成1931。ide
显然,这个对象的键在建立后发生了改变。因此类MutableKey是可变的。函数
让咱们看看下面的示例代码:测试
public class MutableKey { private int i; private int j; public MutableKey(int i, int j) { this.i = i; this.j = j; } public final int getI() { return i; } public final void setI(int i) { this.i = i; } public final int getJ() { return j; } public final void setJ(int j) { this.j = j; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + i; result = prime * result + j; return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof MutableKey)) { return false; } MutableKey other = (MutableKey) obj; if (i != other.i) { return false; } if (j != other.j) { return false; } return true; } }
测试:this
public class MutableDemo { public static void main(String[] args) { // Object created MutableKey key = new MutableKey(10, 20); System.out.println("Hash code: " + key.hashCode()); // Object State is changed after object creation. key.setI(30); key.setJ(40); System.out.println("Hash code: " + key.hashCode()); } }
结果:spa
Hash code: 1291
Hash code: 1931
只要MutableKey 对象的成员变量i或者j改变了,那么该对象的哈希值改变了,因此该对象是一个可变的对象。
HashMap如何存储键值对
HashMap底层是使用Entry对象数组存储的,而Entry是一个单项的链表。当调用一个put()方法将一个键值对添加进来是,先使用hash()函数获取该对象的hash值,而后调用indexFor方法查找到该对象在数组中应该存储的下标,假如该位置为空,就将value值插入,若是该下标出不为空,则要遍历该下标上面的对象,使用equals方法进行判断,若是遇到equals()方法返回真的则进行替换,不然将其插入,源码详解可看:http://www.cnblogs.com/0201zcr/p/4769108.html。
查找时只须要查询经过key值获取获取hash值,而后找到其下标,遍历该下标下面的Entry对象便可查找到value。【具体看下面源码及其解释】
若是HashMap Key的哈希值在存储键值对后发生改变,Map可能再也查找不到这个Entry了。
public V get(Object key) { // 若是 key 是 null,调用 getForNullKey 取出对应的 value if (key == null) return getForNullKey(); // 根据该 key 的 hashCode 值计算它的 hash 码 int hash = hash(key.hashCode()); // 直接取出 table 数组中指定索引处的值, for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; // 搜索该 Entry 链的下一个 Entr e = e.next) // ① { Object k; // 若是该 Entry 的 key 与被搜索 key 相同 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } return null; }
上面是HashMap的get()方法源码,经过上面咱们能够知道,若是 HashMap 的每一个 bucket 里只有一个 Entry 时,HashMap 能够根据索引、快速地取出该 bucket 里的 Entry;在发生“Hash 冲突”的状况下,单个 bucket 里存储的不是一个 Entry,而是一个 Entry 链,系统只能必须按顺序遍历每一个 Entry,直到找到想搜索的 Entry 为止——若是刚好要搜索的 Entry 位于该 Entry 链的最末端(该 Entry 是最先放入该 bucket 中),那系统必须循环到最后才能找到该元素。
同时咱们也看到,判断是否找到该对象,咱们还须要判断他的哈希值是否相同,假如哈希值不相同,根本就找不到咱们要找的值。
若是Key对象是可变的,那么Key的哈希值就可能改变。在HashMap中可变对象做为Key会形成数据丢失。
下面的例子将会向你展现HashMap中有可变对象做为Key带来的问题。
import java.util.HashMap; import java.util.Map; public class MutableDemo1 { public static void main(String[] args) { // HashMap Map<MutableKey, String> map = new HashMap<>(); // Object created MutableKey key = new MutableKey(10, 20); // Insert entry. map.put(key, "Robin"); // This line will print 'Robin' System.out.println(map.get(key)); // Object State is changed after object creation. // i.e. Object hash code will be changed. key.setI(30); // This line will print null as Map would be unable to retrieve the // entry. System.out.println(map.get(key)); } }
输出:
Robin null
如何解决
在HashMap中使用不可变对象。在HashMap中,使用String、Integer等不可变类型用做Key是很是明智的。
咱们也能定义属于本身的不可变类。
若是可变对象在HashMap中被用做键,那就要当心在改变对象状态的时候,不要改变它的哈希值了。咱们只须要保证成员变量的改变能保证该对象的哈希值不变便可。
在下面的Employee示例类中,哈希值是用实例变量id来计算的。一旦Employee的对象被建立,id的值就不能再改变。只有name能够改变,但name不能用来计算哈希值。因此,一旦Employee对象被建立,它的哈希值不会改变。因此Employee在HashMap中用做Key是安全的。
import java.util.HashMap; import java.util.Map; public class MutableSafeKeyDemo { public static void main(String[] args) { Employee emp = new Employee(2); emp.setName("Robin"); // Put object in HashMap. Map<Employee, String> map = new HashMap<>(); map.put(emp, "Showbasky"); System.out.println(map.get(emp)); // Change Employee name. Change in 'name' has no effect // on hash code. emp.setName("Lily"); System.out.println(map.get(emp)); } } class Employee { // It is specified while object creation. // Cannot be changed once object is created. No setter for this field. private int id; private String name; public Employee(final int id) { this.id = id; } public final String getName() { return name; } public final void setName(final String name) { this.name = name; } public int getId() { return id; } // Hash code depends only on 'id' which cannot be // changed once object is created. So hash code will not change // on object's state change @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Employee other = (Employee) obj; if (id != other.id) return false; return true; } }
输出
Showbasky
Showbasky
致谢:感谢您的耐心阅读!