Java中的HashMap使用散列来高效的查找和存储值。HashMap内部使用Map.Entry
的形式来保存key和value,使用put(key,value)
方法存储值,使用get(key)
方法查找值。编程
Java中的hashCode()
方法,是顶层对象Object
中的方法,所以Java中全部的对象都会带有hashCode()
方法。
在各类最佳实践中,都会建议在编写本身的类的时候要同时覆盖hashCode()
和equals()
方法,可是在使用散列的数据结构时(HashMap
,HashSet
,LinkedHashSet
,LinkedHashMap
),
若是不为键覆盖hashCode()
和equals()
方法,将没法正确的处理该键。api
hashCode()
方法返回一个int值,这个int值就是用这个对象的hashCode()
方法产生的hash值。数组
在散列表中查找一个值的过程为,先经过键的hashCode()
方法计算hash值,而后使用hash值产生下标并使用下标查找数组,这里为何要用数组呢,由于数组是存储一组元素最快的数据结构,所以使用数组来表示键的信息。数据结构
因为数组的容量(也就是表中的桶位数)是固定的,因此不一样的键能够产生相同的下标,也就是说,可能会有冲突,所以数组多大就不重要了,任何键总能在数组中找到它的位置。ide
数组并不直接保存值,由于不一样的键可能产生相同的数组下标,数组保存的是LinkedList,所以,散列表的存储结构外层是一个数组,容量固定,数组的每一项都是保存着Entry Object
(同时保存key和value)的LinkedList。函数
因为下标的冲突,不一样的键可能会产生相同的bucket location
,在使用put(key,value)
时,若是两个键产生了相同的bucket location
,因为LinkedList的长度是可变的,因此会在该LinkedList中再增长一项Entry Object
,其中保存着key和value。code
键使用hashCode()
方法产生hash值后,利用hash值产生数组的下标,找到值在散列表中的桶位(bucket),也就是在哪个LinkedList中,若是该桶位只有一个的Object,则返回该Value,若是该桶位有多个Object,那么再对该LinkedList中的Entry Object
的键使用equals()
方法进行线性的查询,最后找到该键的值并返回。对象
最后对LinkedList进行线性查询的部分会比较慢,可是,若是散列函数好的话,数组的每一个位置就只有较少的值,所以不是查询整个LinkedList,而是快速地跳到数组的某个位置,只对不多的元素进行比较,这就是HashMap
会如此快的缘由。递归
在知道了散列的原理后咱们能够本身实现一个简单的HashMap
(例子来源于《Java编程思想(第四版)》)get
public class SimpleHashMap<K, V> extends AbstractMap<K, V> { //内部数组的容量 static final int SIZE = 997; //buckets数组,内部是一个链表,链表的每一项是Map.Entry形式,保存着HashMap的值 @SuppressWarnings("unchecked") LinkedList<MapEntry<K, V>>[] buckets = new LinkedList[SIZE]; public V put(K key, V value) { V oldValue = null; //使用hashCode()方法产生hash值,使用hash值与数组容量取余得到数组的下标 int index = Math.abs(key.hashCode()) % SIZE; //若是该桶位为null,则插入一个链表 if (buckets[index] == null) { buckets[index] = new LinkedList<>(); } //得到bucket LinkedList<MapEntry<K, V>> bucket = buckets[index]; MapEntry<K, V> pair = new MapEntry<>(key, value); boolean found = false; ListIterator<MapEntry<K, V>> it = bucket.listIterator(); while (it.hasNext()) { MapEntry<K, V> iPair = it.next(); //对键使用equals()方法线性查询value if (iPair.getKey().equals(key)) { oldValue = iPair.getValue(); //找到了键之后更改键原来的value it.set(pair); found = true; break; } } //若是没找到键,在bucket中增长一个Entry if (!found) { buckets[index].add(pair); } return oldValue; } //get()与put()的工做方式相似 @Override public V get(Object key) { //使用hashCode()方法产生hash值,使用hash值与数组容量取余得到数组的下标 int index = Math.abs(key.hashCode()) % SIZE; if (buckets[index] == null) { return null; } //使用equals()方法线性查找键 for (MapEntry<K, V> iPair : buckets[index]) { if (iPair.getKey().equals(key)) { return iPair.getValue(); } } return null; } @Override public Set<Map.Entry<K, V>> entrySet() { Set<Map.Entry<K, V>> set = new HashSet<>(); for (LinkedList<MapEntry<K, V>> bucket : buckets) { if (bucket == null) { continue; } for (MapEntry<K, V> mpair : bucket) { set.add(mpair); } } return set; } public static void main(String[] args) { SimpleHashMap<String, String> m = new SimpleHashMap<>(); m.putAll(Countries.capitals(25)); System.out.println(m); System.out.println(m.get("ERITREA")); System.out.println(m.entrySet()); } }
若是hashCode()
产生的hash值可以让HashMap中的元素均匀分布在数组中,能够提升HashMap的运行效率。
一个良好的hashCode()
方法首先是能快速地生成hash值,而后生成的hash值能使HashMap中的元素在数组中尽可能均匀的分布,
hash值不必定是惟一的,由于容量是固定的,总会有下标冲突的状况产生。
《Effective Java》中给出了覆盖hashCode()
方法的最佳实践:
把某个非零的常数值,好比17,保存在一个名为result的int类型中。
对于对象中的每一个关键域f(指equals()
方法中涉及的域),完成如下步骤:
为该域计算int类型的散列码c,根据域的类型的不一样,又能够分为如下几种状况:
若是该域是boolean类型,则计算(f?1:0)
若是该域是String类型,则使用该域的hashCode()
方法
若是该域是byte、char、short或int类型,则计算(int)f
若是该域是long类型,则计算(int)(f^>>>32)
若是该域是float类型,则计算Float.floatToIntBits(f)
若是该域是double类型,则计算Double.doubleToLongBits(f)
返回一个long类型的值,再根据long类型的域,生成int类型的散列码
若是该域是一个对象引用,而且该类的equals()
方法经过递归调用equals方式来比较这个域,则一样为这个域递归地调用hashCode()
若是该域是一个数组,则要把每个元素看成单独的域来处理,也就是说递归地应用上述原则
按照公式:result = 31 * result + c,返回result。
写一个简单的类并用上述的规则来覆盖hashCode()
方法
public class SimpleHashCode { private static long counter = 0; private final long id = counter++; private String name; @Override public int hashCode(){ int result = 17; if (name != null){ result = 31 * result + name.hashCode(); } result = result * 31 + (int) id; return result; } @Override public boolean equals(Object o){ return o instanceof SimpleHashCode && id == ((SimpleHashCode)o).id; } }