HashMap早知道
第一眼hashmap始终Collection那个地点
HashMap中存储数据的结构是
/** * The table, resized as necessary. Length MUST Always be a power of two. */ transient Entry<K,V>[] table;
上面的英文就不用说了。
原来基础的存储结构式Entry的数组!
至于Entry是HashMap的一个内部类
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; int hash; /** * Creates new entry. */ Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } ..... }
看到里面的这个參数Entry<K,V> next大家应该都明确了,HashMap中每一个Entry键值对都是一个链表!!!
以下我们看看map的put,get,iterator方法及遍历
put方法
public V put(K key, V value) { if (key == null) return putForNullKey(value); //计算key的hash 里面的实现比較麻烦 能够不用理会 int hash = hash(key); //由hash码得到存储位置 计算方法是hash与table.length-1相与 这种优点就是能保证要存放的位置肯定不会超过table的范围 //前面的hash方法与indexFor 我没有细致研究 只是大家能够觉得 两个不同的hash会相应不同的存储位置 int i = indexFor(hash, table.length); //e.next 链表 //假设i的位置上已经有元素了 继续看for循环 //否则就new一个新的Entry for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; //假设要存储的key的hash值与已经存在在那个位置元素的key的hash值相等 而且两个key的内容也相等 //话说这里我看的不是太懂 e本身是一个新的对象 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; //下面两行代码究竟想干什么? e.recordAccess(this); //把要增加的值 给了e 是什么意思? return oldValue; //返回的是之前已经存在的那个键值对里的value } } modCount++; addEntry(hash, key, value, i); return null; } void addEntry(int hash, K key, V value, int bucketIndex) { //假设相应的位置已经有东西了 而且总的容量也到了 就扩容 //size threshold 这里面有点概念性的知识 大家看看源代码就知道了 if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); } void createEntry(int hash, K key, V value, int bucketIndex) { //之前的那个位置的数据(无论有没有)转放到e里面 //如今有个问题 什么时候table[bucketIndex]里面才有数据呢? Entry<K,V> e = table[bucketIndex]; //看构造函数就知道 把e作为新entry的next 拉链法!!! table[bucketIndex] = new Entry<>(hash, key, value, e); size++; } static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; int hash; /** * Creates new entry. */ Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } ..... }
什么时候table[bucketIndex]里面才有数据呢?
换句话说拉链法是怎么实现的呢?我们先看以下的遍历。
hashmap的遍历
package iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Random; import java.util.Iterator; import java.util.HashMap; import java.util.Collection; /* * @desc 遍历HashMap的測试程序。 * (01) 通过entrySet()去遍历key、value,參考实现函数: * iteratorHashMapByEntryset() * (02) 通过keySet()去遍历key、value,參考实现函数: * iteratorHashMapByKeyset() * (03) 通过values()去遍历value。參考实现函数: * iteratorHashMapJustValues() * * @author skywang */ public class HashMapIteratorTest { public static void main(String[] args) { int val = 0; String key = null; Integer value = null; Random r = new Random(); HashMap<String, Integer> map = new HashMap<String, Integer>(); map.put("ss",12); //第一次 会调用addEntry(hash, key, value, i); map.put("ss",13); //第二次 会进入put方法里的for循环 然后直接return map.put("ss",12); //第三次 会进入put方法里的for循环 然后直接return 那么什么时候才会使用拉链法呢 //当若干个key的内容不同可是hashCode同样时 for (int i=0; i<12; i++) { // 随机获取一个[0,100)之间的数字 val = r.nextInt(10); key = String.valueOf(val); value = r.nextInt(5); // 加入到HashMap中 map.put(key, value); } // 通过entrySet()遍历HashMap的key-value iteratorHashMapByEntryset(map) ; // 通过keySet()遍历HashMap的key-value iteratorHashMapByKeyset(map) ; // 单单遍历HashMap的value iteratorHashMapJustValues(map); } /* * 通过entry set遍历HashMap * 效率高! */ @SuppressWarnings("unchecked") private static void iteratorHashMapByEntryset(HashMap<String, Integer> map) { if (map == null) return ; System.out.println("\niterator HashMap By entryset"); String key = null; Integer integ = null; Iterator<?> iter = map.entrySet().iterator(); while(iter.hasNext()) { Map.Entry<String, Integer> entry = (Entry<String, Integer>)iter.next(); key = (String)entry.getKey(); integ = (Integer)entry.getValue(); System.out.println(key+" -- "+integ.intValue()); } } /* * 通过keyset来遍历HashMap * 效率低! */ private static void iteratorHashMapByKeyset(HashMap<String, Integer> map) { if (map == null) return ; System.out.println("\niterator HashMap By keyset"); String key = null; Integer integ = null; Iterator<String> iter = map.keySet().iterator(); while (iter.hasNext()) { key = iter.next(); integ = map.get(key); System.out.println(key+" -- "+integ.intValue()); } } /* * 遍历HashMap的values */ private static void iteratorHashMapJustValues(HashMap<String, Integer> map) { if (map == null) return ; Collection<Integer> c = map.values(); Iterator<Integer> iter= c.iterator(); while (iter.hasNext()) { System.out.println(iter.next()); } } }
拉链法
上面已经说了使用entrySet的方式效率高,大家以后就採用这个吧,另外还提到了什么时候用拉链法看以下这个样例HashMap<Person, Integer> map = new HashMap<Person, Integer>(); map.put(new Person("dlf", 14),12); map.put(new Person("dlf", 15),12); map.put(new Person("sdfe", 16),12);
对于person这个类,我重写了hashCode方法,可是没有重写equals方法;
person的hashCode方法:
public int hashCode() { int h = 0; if (name.equals("dlf")) { return 123456789; } if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }
大家再看看hashMap里面的put方法
for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; //假设要存储的key的hash值与已经存在在那个位置元素的key的hash值相等 而且两个key的内容也相等 //话说这里我看的不是太懂 e本身是一个新的对象 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; //下面两行代码究竟想干什么?当我e.recordAccess(this); //把要增加的值 给了e 是什么意思?
return oldValue; //返回的是之前已经存在的那个键值对里的value } }
map.put(new Person("dlf", 14),12); map.put(new Person("dlf", 15),12);
第二个person的hashcode与第一个person的hashcode是一样的,可是看看上面if条件句的第二部分
(k = e.key) == key || key.equals(k)
此时调用Object的equals方法也就是==,那么==比較的是什么呢?栈里面存储的地址值,两个新new处理的对象地址那自然不同的,所以if条件不满足,跳出for循环;
最后的结果是
iterator HashMap By entryset
dlf 15 -- 12
dlf 14 -- 12
sdfe 16 -- 12
iterator HashMap By keyset
dlf 15 -- 12
dlf 14 -- 12
sdfe 16 -- 12
12
12
12
get方法
public V get(Object key) { if (key == null) return getForNullKey(); // 获取key的hash值 int hash = hash(key.hashCode()); // 在“该hash值相应的链表”上查找“键值等于key”的元素 大家看到了 是在hash相应的位置查找,而不是查找整个table for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } return null; }
iterator方法
我们就看效率最高的entrySet方法
Set<Entry<Person, Integer>> set=map.entrySet(); //为了更清楚些 我分开写 Iterator<?> iter = set.iterator();
最開始调用entrySet方法的时候,entrySet对象还为null,会调用new EntrySet;
待得到set集合后,会再次调用iterator方法
public Set<Map.Entry<K,V>> entrySet() { return entrySet0(); } private Set<Map.Entry<K,V>> entrySet0() { Set<Map.Entry<K,V>> es = entrySet; return es != null ? es : (entrySet = new EntrySet()); } private final class EntrySet extends AbstractSet<Map.Entry<K,V>> { public Iterator<Map.Entry<K,V>> iterator() { return newEntryIterator(); } ....... } Iterator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator(); }
大家看到了最后返回的Iterator是一个EntryIterator。
看EntryIterator的代码,它是继承了HashIterator;
private final class EntryIterator extends HashIterator<Map.Entry<K,V>> { public Map.Entry<K,V> next() { return nextEntry(); //这种方法在HashIterator中定义 } }随后我们在程序里调用
Map.Entry<Person, Integer> entry = (Entry<Person, Integer>)iter.next();
//下面为HashIterator类 //构造函数 //在我们new EntryIterator的时候 就已经调用这个其父类HashIterator的构造函数了 //HashIterator的成员变量在构造函数里面 就已经指到table里第一个元素了 HashIterator() { expectedModCount = modCount; if (size > 0) { // advance to first entry Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } } final Entry<K,V> nextEntry() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); Entry<K,V> e = next; //这个next是构造函数里面就指到table里第一个元素了(第一个不为null的元素) if (e == null) throw new NoSuchElementException(); // 先让next=e.next 然后才推断next是否为空 if ((next = e.next) == null) { //从第一行调用来看 //假设table的第一个Entry(事实上就是一个单链表) 就仅仅有一个元素(其next为空) //让next找到table的下一个元素 Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } current = e; return e; }
有了上面的if ((next = e.next) == null) 这一行,我们就不仅能遍历整个table,还能将table中某个entry中的全部元素也遍历了!不反复,不遗漏。
參考资料
http://www.cnblogs.com/skywang12345/p/3310835.html
我说两句,上面的博客java全部Collection再次源代码分析,博客的主人真乃牛死! 我们必须去看看
【推荐】中国电信天翼云云端翼购节,2核2G云服务器一口价38元/年
【推荐】博客园携手 AI 驱动开发工具商 Chat2DB 推出联合终身会员
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步