Java 深入集合--HashMap

一、HashMap介绍:

    上面一篇介绍了hashTable,这里HashMap的作用就不多啰嗦了。HashMap 实现的功能和hashTable 差不多,具体实现和功能我们从源码进行分析。

二、源码分析:

2.1 类实现:

      public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>,        Cloneable, Serializable{...}

      这里我们可以看到继承了 AbstractMap<K,V>  还实现了 Map<K,V>,其他的参考hashTable,其实 AbstractMap<K,V>  去看源码知道已经实现了Map<K,V> 接口,为什么这里还需要去实现呢?很多人这里有有疑问,觉得很啰嗦,下面我谈一下介绍,和自己的看法.

      Map 接口提供了键值对 这类型数据的通用的方法,而AbstractMap 这是对Map实现的抽象类,我们知道一般抽象类都是用来定义一个概念,一种通用的,不指向具体事物。 

      这里举个例子:假设我们知道动物都有  eat(food)  的行为,相当于Map. 然后我们设计一个抽象类去实现 void eat(food) {System.out.println(food);} 相当于AbstractMap。然后这个时候我们去实现各个动物比如:人,熊猫等等,都去继承这个抽象类,相当于hashMap ,  TreeMap 一样,这样我们就可以省略eat 这个通用的实现了。当然实际上这些通用实现可能不满足我们的需求,那么我就要从写。

      事实上这里还是没明白为什么 HashMap 还要实现Map接口,因为抽象类已经实现了啊。为啥还要实现呢?其实你进入AbstractMap ,仔细观察会发现:

       public abstract Set<Entry<K,V>> entrySet(); 

       这里其实没有对 entrySet 进行实现的,相当于复制了Map的接口方法。我们从JDK 解释中明白:

       1.AbstractMap 提供 Map 接口的骨干实现,以最大限度地减少实现此接口所需的工作。

       2.要实现不可修改的映射,编程人员只需扩展此类并提供 entrySet 方法的实现即可。

       第一句话很好理解,提供方便,第二句话强调了 实现不可修改的映射,也就是说我们继承了AbstractMap ,实现entrySet()方法,那么当前类的集合就是不可修改的了。具体点说,就是你调用 put(),putAll(),remove(),clear() 方法,数据都会存在,并且put()还会报错,具体请看源码和API。

        好了,说了这么多,其实是简单解释了AbstractMap 的作用。而实现Map 接口,从功能上来说,没啥作用,因为去掉也没影响。但是从接口上来说,我们可以从JAVA 集合的关系图中接口图就能看出,Map Collection Itertor 是最顶层的东西,我们习惯面向接口编程,这样我们的hashMap 也能能直接指向Map 接口,整个结构更加的完整,这是我的想法,请补充指正!下面继续进行 代码的详细研究吧!

 public HashMap() {
        
        this.loadFactor = DEFAULT_LOAD_FACTOR;// 0.75
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY];// 16
        init();
    }
// 这里我们new HashMap 无论带参数 或者不带参数,主要都是设置增长因子0.75
// 当前增长的位置
// 以及Entry 的数组,这里可以参看hashTable ,下面继续看 Entry 类
static class Entry<K,V> implements Map.Entry<K,V> {
        // 这是定义的内部类,Key value 内部属性,
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

        public final K getKey() {
            return key;
        }

        public final V getValue() {
            return value;
        }

        public final V setValue(V newValue) {
            // 这里仅仅 提供了对值覆盖方法
	    V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            // 这里对类型 key value 地址值 和 值得判断
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry)o;
            Object k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }

        public final int hashCode() {
            // 这里是对key 和 value 两个的地址值 进行 异或运算,得出的hashcode
            return (key==null   ? 0 : key.hashCode()) ^
                   (value==null ? 0 : value.hashCode());
        }
       // 这两个方法 真没看懂,有什么作用。。。
       // 知道了人,麻烦帮帮忙解释下
       // 当然因为这方法在put remove 等地方都会自动调用估计在扩展的里面的东西留
       // 下扩充空间吧
        void recordAccess(HashMap<K,V> m) {
        }

        void recordRemoval(HashMap<K,V> m) {
        }
    }

    我们可以看到,hashMap 也是一个内部类的数组,内部类 有ket value 属性而已,下面看看主要方法实现

   

// 可以看到 这里是有返回值的 ,而且是返回的value    
public V put(K key, V value) {
        if (key == null)
           // 可以看出 key 是可以为null的。并且对值 也进行了操作,请看下面
           return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
             // 这里做了便利,对key 为null 的值
            if (e.key == null) {
                V oldValue = e.value;
                // 这里进行了赋值操作,并且返回的是 旧的一个值
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        // 数据自增一
        modCount++;
        // 这里指定了 key 为null 的元素存放的位置,从下面的方法 以及构造,我们看出
        // 所有key 为null 的存放位置,默认是放在0的位置,也就是数组第一个
        addEntry(0, null, value, 0);
        return null;
    }

//addEntry(0, null, value, 0);
void addEntry(int hash, K key, V value, int bucketIndex) {
	Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        if (size++ >= threshold)
            resize(2 * table.length);
    }
// 当然这里还进行可 扩容的操作,当当前元素size >=  增长位置threshold 的时候
  void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        // 这里我很意外,这里设置了一个最大初始化的位置,是Integer 最大值的一半
        // 当元素刚好有MAXIMUM_CAPACITY这么多时,就一个赋值 return,后面就无法完成扩容了
        // 而我们看到扩容是 都是2*table.length,而这个数是一个单数- -,那么这里理论上是无法
        // 得到执行的。也就是没用的,谁知道 这里的设计是为了什么呢?

        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        // 这里就是新的扩容了,
        Entry[] newTable = new Entry[newCapacity];
        // 这里就是数组的复制,并且位置是从新计算了的。感觉上是比较耗的,因为会从新计算存放        // 的位置,不是单纯的复制,关于位置的计算,下面继续。
        transfer(newTable);
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }
   
这是 put 方法 里面的两个东西

int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);

 // hash值算法,为了减小重复而这样方式的
 // 这里先不谈,专门找章节介绍,可以参考:http://marystone.iteye.com/blog/709945
 static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
    //参考:http://blog.csdn.net/heyutao007/article/details/6206153
    static int indexFor(int h, int length) {
        return h & (length-1);
    }

// 通过相同的hash 和 indexFors 算法,快速的取值
public V get(Object key) {
        if (key == null)
            return getForNullKey();
        int hash = hash(key.hashCode());
        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;
    }
 

   综上可以看看出:put 方法 是通过hash算法,以及indexFor 算出值应该存放在数组的哪个位置,并不是顺序存放的,这样的好处是 在取值的 也可以通过算法 就行读取。但是存放过程中进过了大量的计算,以及数组的操作,因此不难理解存放是比较耗时的,而取值是很快的。

  下面我们来看看几个常用的方法:

   remove():
   

 public V remove(Object key) {
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }
 // final 提高查找速度
 final Entry<K,V> removeEntryForKey(Object key) {
        // 通过hash 和 i, 找出table 中元素的位置
        int hash = (key == null) ? 0 : hash(key.hashCode());
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;

        while (e != null) {
            Entry<K,V> next = e.next;
            Object k;
            // 通过该元素的hash 和 key 值,进行对比 确认元素
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                modCount++;
                size--;
                // 通过数组用后一个元素 覆盖当前元素,完成移除操作
                // 这里size --,当时数组空间其实并没减少,只是最后一个元素你无法访问了
                // 相当于 1,2,3,4,5 移除3, 就变成了1,2,4,5,5,但是size 5变成了4!
                // 这里估计是想操作数组又需要时间 和空间吧!
                if (prev == e)
                    table[i] = next;
                else
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }

        return e;
    }
    

   下面来看我们常用的几个方法:

    public Set<KkeySet();

    public Set<Map.Entry<K,V>> entrySet();

    public Collection<V> values();

     

// 获得所有value 的集合 
public Collection<V> values() {
        Collection<V> vs = values;
        return (vs != null ? vs : (values = new Values()));
    }

    private final class Values extends AbstractCollection<V> {
        public Iterator<V> iterator() {
            return newValueIterator();
        }
     // ..略
}
 // 获得所有Entry的集合 
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();
        }
       // ... 略

}

// 获得所有key的集合 
public Set<K> keySet() {
        Set<K> ks = keySet;
        return (ks != null ? ks : (keySet = new KeySet()));
    }

    private final class KeySet extends AbstractSet<K> {
        public Iterator<K> iterator() {
            return newKeyIterator();
        }
        // ... 略
}
   如果看过hashTable 的源码就会发现,它是通过 3个标志 判断取的是什么,调用的一个方法,而hashMap
   这里主要其实也差不多, newKeyIterator 等3个方法 ,继承的 HashIterator
   
 private final class ValueIterator extends HashIterator<V> {
        public V next() {
            return nextEntry().value;
        }
    }

    private final class KeyIterator extends HashIterator<K> {
        public K next() {
            return nextEntry().getKey();
        }
    }

    private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
        public Map.Entry<K,V> next() {
            return nextEntry();
        }
    }
       就能获得你需要的元素了,下面主要看 HashIterator:
    
 private abstract class HashIterator<E> implements Iterator<E> {
        // 这里感觉和Entry 差不多,其实是的,这里内部类主要是为了维护table[Entry]
        Entry<K,V> next;	// next entry to return
        // 这里控制多线程修改,用来判断同时操作 快速的抛出异常
        int expectedModCount;	// For fast-fail
        int index;		// current slot
        Entry<K,V> current;	// current entry
        //  
        HashIterator() {
            expectedModCount = modCount;
            if (size > 0) { // advance to first entry
                Entry[] t = table;
                // 这里其实是通过循环 对next 赋值,每次都取得第一个值,
                // 这写法好真简洁!
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
        }

        public final boolean hasNext() {
            return next != null;
        }
        // 这里就能获得entry,然后通过entry 就能获得需要的值了
        final Entry<K,V> nextEntry() {
            // 这里就是刚才线程控制啦,虽然是线程不安全的,但是迭代器中不允许几种同时操作
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Entry<K,V> e = next;
            if (e == null)
                throw new NoSuchElementException();
             
            if ((next = e.next) == null) {
                Entry[] t = table;                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
	    current = e;
            return e;
        }
        
        public void remove() {
            if (current == null)
                throw new IllegalStateException();
            // 移除操作,对迭代过程的控制,迭代不能删除的
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Object k = current.key;
            current = null;
            HashMap.this.removeEntryForKey(k);
            expectedModCount = modCount;
        }

    }
 
小结 :这里就介绍了 hashMap 的常用方法,总的来说和hashTable 实现的功能上差不多,但是细节上上的区别,比如:1.hashMap 没table 那线程安全锁,因此线程是不安全的,速度更快。2:hash 值上,hashMap 独立了hash算法,并且算法是通过key value 多次算出来的,减少了重复性。3:hashMap 可以放key 为null 的值,并且默认是数组第一个。4:设计上都是可以获得Set 集合,从而遍历集合,hashMap 是独立了内部类,分别获得key  value entry,而hashTale 仅仅是3个变量去获得,hashMap 扩展性更好。当然还有其他的实现方式的区别,就不解释了,需要再慢慢研究。
     hashMap 这里就结束了,希望大家多批评指定,小弟一定学习改正!

一、HashMap介绍:

    上面一篇介绍了hashTable,这里HashMap的作用就不多啰嗦了。HashMap 实现的功能和hashTable 差不多,具体实现和功能我们从源码进行分析。

二、源码分析:

2.1 类实现:

      public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>,        Cloneable, Serializable{...}

      这里我们可以看到继承了 AbstractMap<K,V>  还实现了 Map<K,V>,其他的参考hashTable,其实 AbstractMap<K,V>  去看源码知道已经实现了Map<K,V> 接口,为什么这里还需要去实现呢?很多人这里有有疑问,觉得很啰嗦,下面我谈一下介绍,和自己的看法.

      Map 接口提供了键值对 这类型数据的通用的方法,而AbstractMap 这是对Map实现的抽象类,我们知道一般抽象类都是用来定义一个概念,一种通用的,不指向具体事物。 

      这里举个例子:假设我们知道动物都有  eat(food)  的行为,相当于Map. 然后我们设计一个抽象类去实现 void eat(food) {System.out.println(food);} 相当于AbstractMap。然后这个时候我们去实现各个动物比如:人,熊猫等等,都去继承这个抽象类,相当于hashMap ,  TreeMap 一样,这样我们就可以省略eat 这个通用的实现了。当然实际上这些通用实现可能不满足我们的需求,那么我就要从写。

      事实上这里还是没明白为什么 HashMap 还要实现Map接口,因为抽象类已经实现了啊。为啥还要实现呢?其实你进入AbstractMap ,仔细观察会发现:

       public abstract Set<Entry<K,V>> entrySet(); 

       这里其实没有对 entrySet 进行实现的,相当于复制了Map的接口方法。我们从JDK 解释中明白:

       1.AbstractMap 提供 Map 接口的骨干实现,以最大限度地减少实现此接口所需的工作。

       2.要实现不可修改的映射,编程人员只需扩展此类并提供 entrySet 方法的实现即可。

       第一句话很好理解,提供方便,第二句话强调了 实现不可修改的映射,也就是说我们继承了AbstractMap ,实现entrySet()方法,那么当前类的集合就是不可修改的了。具体点说,就是你调用 put(),putAll(),remove(),clear() 方法,数据都会存在,并且put()还会报错,具体请看源码和API。

        好了,说了这么多,其实是简单解释了AbstractMap 的作用。而实现Map 接口,从功能上来说,没啥作用,因为去掉也没影响。但是从接口上来说,我们可以从JAVA 集合的关系图中接口图就能看出,Map Collection Itertor 是最顶层的东西,我们习惯面向接口编程,这样我们的hashMap 也能能直接指向Map 接口,整个结构更加的完整,这是我的想法,请补充指正!下面继续进行 代码的详细研究吧!

 public HashMap() {
        
        this.loadFactor = DEFAULT_LOAD_FACTOR;// 0.75
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY];// 16
        init();
    }
// 这里我们new HashMap 无论带参数 或者不带参数,主要都是设置增长因子0.75
// 当前增长的位置
// 以及Entry 的数组,这里可以参看hashTable ,下面继续看 Entry 类
static class Entry<K,V> implements Map.Entry<K,V> {
        // 这是定义的内部类,Key value 内部属性,
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

        public final K getKey() {
            return key;
        }

        public final V getValue() {
            return value;
        }

        public final V setValue(V newValue) {
            // 这里仅仅 提供了对值覆盖方法
	    V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            // 这里对类型 key value 地址值 和 值得判断
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry)o;
            Object k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }

        public final int hashCode() {
            // 这里是对key 和 value 两个的地址值 进行 异或运算,得出的hashcode
            return (key==null   ? 0 : key.hashCode()) ^
                   (value==null ? 0 : value.hashCode());
        }
       // 这两个方法 真没看懂,有什么作用。。。
       // 知道了人,麻烦帮帮忙解释下
       // 当然因为这方法在put remove 等地方都会自动调用估计在扩展的里面的东西留
       // 下扩充空间吧
        void recordAccess(HashMap<K,V> m) {
        }

        void recordRemoval(HashMap<K,V> m) {
        }
    }

    我们可以看到,hashMap 也是一个内部类的数组,内部类 有ket value 属性而已,下面看看主要方法实现

   

// 可以看到 这里是有返回值的 ,而且是返回的value    
public V put(K key, V value) {
        if (key == null)
           // 可以看出 key 是可以为null的。并且对值 也进行了操作,请看下面
           return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
             // 这里做了便利,对key 为null 的值
            if (e.key == null) {
                V oldValue = e.value;
                // 这里进行了赋值操作,并且返回的是 旧的一个值
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        // 数据自增一
        modCount++;
        // 这里指定了 key 为null 的元素存放的位置,从下面的方法 以及构造,我们看出
        // 所有key 为null 的存放位置,默认是放在0的位置,也就是数组第一个
        addEntry(0, null, value, 0);
        return null;
    }

//addEntry(0, null, value, 0);
void addEntry(int hash, K key, V value, int bucketIndex) {
	Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        if (size++ >= threshold)
            resize(2 * table.length);
    }
// 当然这里还进行可 扩容的操作,当当前元素size >=  增长位置threshold 的时候
  void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        // 这里我很意外,这里设置了一个最大初始化的位置,是Integer 最大值的一半
        // 当元素刚好有MAXIMUM_CAPACITY这么多时,就一个赋值 return,后面就无法完成扩容了
        // 而我们看到扩容是 都是2*table.length,而这个数是一个单数- -,那么这里理论上是无法
        // 得到执行的。也就是没用的,谁知道 这里的设计是为了什么呢?

        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        // 这里就是新的扩容了,
        Entry[] newTable = new Entry[newCapacity];
        // 这里就是数组的复制,并且位置是从新计算了的。感觉上是比较耗的,因为会从新计算存放        // 的位置,不是单纯的复制,关于位置的计算,下面继续。
        transfer(newTable);
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }
   
这是 put 方法 里面的两个东西

int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);

 // hash值算法,为了减小重复而这样方式的
 // 这里先不谈,专门找章节介绍,可以参考:http://marystone.iteye.com/blog/709945
 static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
    //参考:http://blog.csdn.net/heyutao007/article/details/6206153
    static int indexFor(int h, int length) {
        return h & (length-1);
    }

// 通过相同的hash 和 indexFors 算法,快速的取值
public V get(Object key) {
        if (key == null)
            return getForNullKey();
        int hash = hash(key.hashCode());
        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;
    }
 

   综上可以看看出:put 方法 是通过hash算法,以及indexFor 算出值应该存放在数组的哪个位置,并不是顺序存放的,这样的好处是 在取值的 也可以通过算法 就行读取。但是存放过程中进过了大量的计算,以及数组的操作,因此不难理解存放是比较耗时的,而取值是很快的。

  下面我们来看看几个常用的方法:

   remove():
   

 public V remove(Object key) {
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }
 // final 提高查找速度
 final Entry<K,V> removeEntryForKey(Object key) {
        // 通过hash 和 i, 找出table 中元素的位置
        int hash = (key == null) ? 0 : hash(key.hashCode());
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;

        while (e != null) {
            Entry<K,V> next = e.next;
            Object k;
            // 通过该元素的hash 和 key 值,进行对比 确认元素
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                modCount++;
                size--;
                // 通过数组用后一个元素 覆盖当前元素,完成移除操作
                // 这里size --,当时数组空间其实并没减少,只是最后一个元素你无法访问了
                // 相当于 1,2,3,4,5 移除3, 就变成了1,2,4,5,5,但是size 5变成了4!
                // 这里估计是想操作数组又需要时间 和空间吧!
                if (prev == e)
                    table[i] = next;
                else
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }

        return e;
    }
    

   下面来看我们常用的几个方法:

    public Set<KkeySet();

    public Set<Map.Entry<K,V>> entrySet();

    public Collection<V> values();

     

// 获得所有value 的集合 
public Collection<V> values() {
        Collection<V> vs = values;
        return (vs != null ? vs : (values = new Values()));
    }

    private final class Values extends AbstractCollection<V> {
        public Iterator<V> iterator() {
            return newValueIterator();
        }
     // ..略
}
 // 获得所有Entry的集合 
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();
        }
       // ... 略

}

// 获得所有key的集合 
public Set<K> keySet() {
        Set<K> ks = keySet;
        return (ks != null ? ks : (keySet = new KeySet()));
    }

    private final class KeySet extends AbstractSet<K> {
        public Iterator<K> iterator() {
            return newKeyIterator();
        }
        // ... 略
}
   如果看过hashTable 的源码就会发现,它是通过 3个标志 判断取的是什么,调用的一个方法,而hashMap    这里主要其实也差不多, newKeyIterator 等3个方法 ,继承的 HashIterator    
 private final class ValueIterator extends HashIterator<V> {
        public V next() {
            return nextEntry().value;
        }
    }

    private final class KeyIterator extends HashIterator<K> {
        public K next() {
            return nextEntry().getKey();
        }
    }

    private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
        public Map.Entry<K,V> next() {
            return nextEntry();
        }
    }
       就能获得你需要的元素了,下面主要看 HashIterator:     
 private abstract class HashIterator<E> implements Iterator<E> {
        // 这里感觉和Entry 差不多,其实是的,这里内部类主要是为了维护table[Entry]
        Entry<K,V> next;	// next entry to return
        // 这里控制多线程修改,用来判断同时操作 快速的抛出异常
        int expectedModCount;	// For fast-fail
        int index;		// current slot
        Entry<K,V> current;	// current entry
        //  
        HashIterator() {
            expectedModCount = modCount;
            if (size > 0) { // advance to first entry
                Entry[] t = table;
                // 这里其实是通过循环 对next 赋值,每次都取得第一个值,
                // 这写法好真简洁!
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
        }

        public final boolean hasNext() {
            return next != null;
        }
        // 这里就能获得entry,然后通过entry 就能获得需要的值了
        final Entry<K,V> nextEntry() {
            // 这里就是刚才线程控制啦,虽然是线程不安全的,但是迭代器中不允许几种同时操作
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Entry<K,V> e = next;
            if (e == null)
                throw new NoSuchElementException();
             
            if ((next = e.next) == null) {
                Entry[] t = table;                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
	    current = e;
            return e;
        }
        
        public void remove() {
            if (current == null)
                throw new IllegalStateException();
            // 移除操作,对迭代过程的控制,迭代不能删除的
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Object k = current.key;
            current = null;
            HashMap.this.removeEntryForKey(k);
            expectedModCount = modCount;
        }

    }
  小结 :这里就介绍了 hashMap 的常用方法,总的来说和hashTable 实现的功能上差不多,但是细节上上的区别,比如:1.hashMap 没table 那线程安全锁,因此线程是不安全的,速度更快。2:hash 值上,hashMap 独立了hash算法,并且算法是通过key value 多次算出来的,减少了重复性。3:hashMap 可以放key 为null 的值,并且默认是数组第一个。4:设计上都是可以获得Set 集合,从而遍历集合,hashMap 是独立了内部类,分别获得key  value entry,而hashTale 仅仅是3个变量去获得,hashMap 扩展性更好。当然还有其他的实现方式的区别,就不解释了,需要再慢慢研究。      hashMap 这里就结束了,希望大家多批评指定,小弟一定学习改正!

猜你喜欢

转载自greemranqq.iteye.com/blog/1923187