标签:hashmap 单链表 遍历 hash iterator
先看看hashmap在整个Collection中的位置
HashMap中存储数据的结构是
/** * The table, resized as necessary. Length MUST Always be a power of two. */ transient Entry<K,V>[] table;
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; } ..... }
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++; }
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()); } } }
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);
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; }
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);
12
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; }
我们就看效率最高的entrySet方法
Set<Entry<Person, Integer>> set=map.entrySet(); //为了更清楚些 我分开写 Iterator<?> iter = 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(); }
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的源代码剖析了一遍,博客主人真乃牛人呀! 大家一定要去看看
标签:hashmap 单链表 遍历 hash iterator
原文地址:http://blog.csdn.net/dlf123321/article/details/40426229