深入学习java集合:HashMap<K,V>实现

来源:互联网 发布:unity3d 动态加载 编辑:程序博客网 时间:2024/05/21 22:43
1、HashMap类图

        Map 用于保存具有映射关系的数据,因此 Map 集合里保存着两组值,一组值用于保存 Map 里的 Key,另外一组用于保存 Map 里的 Value,Map 中的 key 和  value 都可以是任何引用类型的数据。Map 中的 Key 不允许重复,即同一个 Map 对象的任何两个 Key 通过 equals 方法比较中返回 false,Key 和 Value 之间存在单向一对一关系,即通过指定的 Key 总能找到唯一的,确定的 Value
        HashMap按 Hash 算法来存储集合中的元素,因此具有很好的存取和查找性能。HashMap具有以下特点:
                   –不能保证元素的排列顺序
                    –HashMap不是线程安全的
                    –集合元素可以使null值和null
        当向 HashMap集合中存入一个元素时,HashMap会调用该对象的 hashCode() 方法来得到该对象的 hashCode值,然后根据 hashCode值决定该对象在 HashMap中的存储位置。HashMap使用拉链法解决元素冲突。
        在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
  
                 
 
      从上图中可以看出,HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。

HashMap构造实现以及重要方法
 
      1)底层使用数组实现:
   /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
)构造器
          HashMap 提供了4个不同的构造器
// 构造一个初始大小为initialCapacity,装载因子为loadFactor的空的HashMap
   public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
    }
// 构造一个初始大小为initialCapacity,装载因子为默认的0.75的空的HashMap
 public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
//默认的无参数构造器,默认的初始大小为16,装载因子为0.75
 public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }
 public HashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        inflateTable(threshold);
        putAllForCreate(m);
    }
3)put(key: K, value: V): V  向Map中添加key-value对方法
    // HashMap允许存放null键和null值。  
    // 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。  
    // 根据key的keyCode重新计算hash值,然后搜索指定hash值在对应table中的索引也就是对应的Hash桶。  
  // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。  通过equals方法判断相等性,若发现已存在,则替换,否则新添加。
  public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        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;
    } 
// 真正向Map中添加Entry方法
 void addEntry(int hash, K key, V value, int bucketIndex) {
        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);
    }
//计算hash值的算法,此算法加入了高位计算,防止低位不变,高位变化时,造成的hash冲突。
 final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }
   
        // 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).
     
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
//通过此方法,将计算出来的Hash值对应到底层数组下标,计算出Hash桶。
static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
        return h & (length-1);
    }


  4 ) get(key: Object): V  获取指定Key对应的Value方法
//HashMapget元素时,首先计算keyhashCode,找到数组中对应位置的某一元素,然后通过keyequals方法在对应位置的链表中找到需要的元素。
 public V get(Object key) {
        if (key == null)   //若key为null,则从table的第一个位置获取
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);
        return null == entry ? null : entry.getValue();
    }
  final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }
        int hash = (key == null) ? 0 : hash(key);
        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 != null && key.equals(k))))
                return e;
        }
        return null;
    }
5)   resize( newCapacity :int):void   HashMap的扩容方法    

     HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中,这是一个常用的操作,而在HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize

       那么HashMap什么时候进行扩容呢?当HashMap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

   void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }
  /**
     * Transfers all entries from current table to newTable.
     */
    void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
            while(null != e) {
                Entry<K,V> next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }

6) remove(key: Object): V  从Map中移除对应的Key方法
 public V remove(Object key) {
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }
 public V remove(Object key) {
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }
    final Entry<K,V> removeEntryForKey(Object key) {
        if (size == 0) {
            return null;
        }
        int hash = (key == null) ? 0 : hash(key);
        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;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                modCount++;
                size--;
                if (prev == e)
                    table[i] = next;
                else
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }
        return e;
    }

7) keySet(): Set<K> 返回该HashMap的所以Key的集合视图方法
  ,并返回,在遍历这个Set视图时,通过iterator()方法,获取newKeyIterator();
     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();
        }
        public int size() {
            return size;
        }
        public boolean contains(Object o) {
            return containsKey(o);
        }
        public boolean remove(Object o) {
            return HashMap.this.removeEntryForKey(o) != null;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }

8) entrySet(): Set<Map.Entry<K,V>> 获取hashMap中包含的元素视图集合方法
 
    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();
        }
        public boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<K,V> e = (Map.Entry<K,V>) o;
            Entry<K,V> candidate = getEntry(e.getKey());
            return candidate != null && candidate.equals(e);
        }
        public boolean remove(Object o) {
            return removeMapping(o) != null;
        }
        public int size() {
            return size;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }
9) Fail-Fast机制:  

         我们知道java.util.HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。

          这一策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount

     HashIterator() {
            expectedModCount = modCount;
            if (size > 0) { // advance to first entry
                Entry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
        }

       在迭代过程中,判断modCountexpectedModCount是否相等,如果不相等就表示已经有其他线程修改了Map。注意到modCount声明为volatile,保证线程之间修改的可见性。

      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;
        }




0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 被怀疑假结婚该怎么办 中考作弊被捉了怎么办 露娜注册错了怎么办 去泰国开民宿怎么办工作签证 绩点2.7想出国怎么办 香港过境想去澳门怎么办 加拿大博士资格考试没通过怎么办 外国人没有学历怎么办工作签证 澳洲语言班挂了怎么办 英国学位翻译成文学硕士怎么办 没考上好的高中怎么办 毕业证12月发放申请英国怎么办 高二迷茫成绩差怎么办 高二期末考的差怎么办 法国留学签证办不下来怎么办 澳洲留学挂科签证续签怎么办 澳洲旅游签证被拒了怎么办 学校六级不让刷分怎么办 英国留学生怎么办申根签证 挂科太多拿不到学位证怎么办 ucl语言班没过怎么办 西澳大学工程挂科怎么办 澳洲语言班没过怎么办2018 杨浦区对口公立小学太差怎么办 公立小学太差了怎么办 澳大利亚出国留学怎么办-语言课程 学校不给释放信怎么办 重修费交不起钱怎么办 不想用家里的钱怎么办 三本学费太贵怎么办 从日本往中国汇款怎么办 学费钱大一没交怎么办 初中孩子和同学相处不好怎么办 使用假护照出国被发现怎么办 酒店忘记退房了怎么办 语言课申请不上怎么办 学会计的应届生想转行怎么办 上班熬不下去了怎么办 墨大选修挂科怎么办 日本签证年收入没有10w怎么办 做生意的办房贷没有薪资流水怎么办