• HashMap内部实现机制及优化第一篇



    HashMap是链表(请参考LinkedList算法详解)和数组(ArrayList解读)组合的一个综合性的算法,理解本文前最好阅读本文的两篇

    他的结构类似于:

    数组保存时链表的头部,下面来一步一的详细解释HashMap主要源码:

    对象:

       // 默认容量(数组容量)
        static final int DEFAULT_INITIAL_CAPACITY = 16;
        //最大容量(数组容量)
        static final int MAXIMUM_CAPACITY = 1 << 30;
        //加载因子(当size>=容量*加载因子大于或等于table.length,容量扩大一倍)
        static final float DEFAULT_LOAD_FACTOR = 0.75f;
        //链表数组
        transient Entry[] table;
        //元素个数
        transient int size;
        //此值=容量*加载因子
        int threshold;
    //加载因子
    final float loadFactor;

    上面介绍的HashMpa的属性暂时可能有点难理解。下面介绍他们的用处:

    对象初始化:

        public HashMap(int initialCapacity, float loadFactor) {
    //判断传入的初始化容量是否为小于0,如果是抛出异常
    if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); //判断传入的初始化容量是否为大于MAX,如果是就等于MAX
    if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY;  //判断传入的初始化加载因子是否符合要求。
    if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); // 初始化容量
    int capacity = 1;
         //如果传入的容量小于初始化容量向左移动一位(也就是乘二)
    while (capacity < initialCapacity) capacity <<= 1;
    //加载因子
    this.loadFactor = loadFactor; //加载因子(当size>=threshold,容量扩大一倍)
    threshold
    = (int)(capacity * loadFactor); //初始化table 
    table
    = new Entry[capacity]; init(); }

    上面介绍的对象,大家现在可以先别管(记住有这东西),对于现在有点难理解。大家可以集合源码整体的看看效果更好。

    增加:

        public V put(K key, V value) {
            //判断是否为空,如果为空就空处理。
            if (key == null)
                return putForNullKey(value);
            //取处理后的hash(下面有介绍)
            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;
      

    想要理解上面的代码就必须理解hash的存储机制,下面来介绍下:


      1.根据key的hash值(hashCode()),到HashMap提供的hash方法里面加工一下(这里暂时这么理解)

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

          这是一段神奇的代码~表示看不懂,你们可以深入研究。参数h为对象hashcode()方法返回值。

      2.取到加工后的hash码后然后根据indexFor()方法取到index(这里可以说是hash算法的核心,所有操作的基于它的。)

        static int indexFor(int h, int length) {
            return h & (length-1);
        }

          h是加工后的hash码,length是当前数组的长度,这个方法取到的长度不会超过length-1,所以index不会超过数组的长度.

    前面介绍的两个方法是hash算法的核心,每个不同对象都有不同的hash码(在hashmap里面相同的对象hash码一定是相等的,而且==或则equals也返回true),而当对象通过hash算法取到的index也是一个随机的,所以存储的位置是根据hash值来决定的,在这里我们来个假设:如果没有hashmap通过hash值来取索引,你会怎样去根据k来取到value?可想而知,hash码取索引节约了很多时间。

    3.取到索引后判断指定索引里面的Entry(单向链表)是否存在添加的k,也就是(e.hash == hash && ((k = e.key) == key || key.equals(k))),如果返回true就直接覆盖,返回false添加新元素,覆盖不多讲,这里讲添加。首先介绍一下hash的Entry数据结构:

        static class Entry<K,V> implements Map.Entry<K,V> {
            final K key;
            V value;
            Entry<K,V> next;
            final int hash;
    
            /**
             * Creates new entry.
             */
            Entry(int h, K k, V v, Entry<K,V> n) {
                value = v;
                next = n;
                key = k;
                hash = h;
            }
    .........省略
    }

    看了前面的LinedList详解,就能理解这种数据结构的基本操作(这里是个单向链表,也就是没有previous)相对而言操作简单些,前面介绍的hashmap对象里面就有一个table,他是Entry数组,也就是table里面保存了多条链表。整个数据结构类似于2纬表(所以也经常叫hash表)。

     添加:

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


    index和hash,k,v已经从put方法传过来了,已经知道要放在哪一条链表中了(知道table的index),每次添加就是把table[index]后移动,然后把添加的元素添加到头部,这里不具体介绍,添加后然后判断当前size是否等于或大于threshold(table.length*loadfactor),加载因子的作用在这里体现。间接性的控制链表的长度,或者是控制数组的长度。如果条件满足,数组扩大一倍,也就是调用resize(length)方法,接下来介绍resize():

        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);
            table = newTable;
            threshold = (int)(newCapacity * loadFactor);
        }


    这里的方法很简单,这里调用了transfer(newTable):

        void transfer(Entry[] newTable) {
            Entry[] src = table;
            int newCapacity = newTable.length;
            for (int j = 0; j < src.length; j++) {
                Entry<K,V> e = src[j];
                if (e != null) {
                    src[j] = null;
                    do {
                        Entry<K,V> next = e.next;
                        int i = indexFor(e.hash, newCapacity);
                        e.next = newTable[i];
                        newTable[i] = e;
                        e = next;
                    } while (e != null);
                }
            }
        }

    看resize是不是还没有添加元素到新的table中来?所以这个方法是加载table。还记得indexFor(h,length)这个方法吗?这个方法是根据数组的来确定index的。现在新数组的长度扩大了两倍所以元素的index改变了。这个方法就是重新加载新table。
    基本的put操作已经介绍完。

    get操作:

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

    这里有个null处理因为没有key.hashcode()会报NullPointException异常,所以要来个空处理,这里根据hash码就能取到index(因为他是根据hash值来存储的)。然后遍历指定的index处的Entry.next.next......的k是存在,如果存在返回value,不存在返回null.

    基本操作也介绍完(remove....等一些方法自己可以打开源码看)。

     下面给出我实现的MyHashMap:

    package servlet;
    
    import java.util.AbstractMap;
    import java.util.Iterator;
    import java.util.Map;
    import java.util.Set;
    
    @SuppressWarnings("unchecked")
    public class MyHashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>,Iterable<K> {
    
        static final int MAX_CAPCITY = 1 << 30; // max size
    
        static final int DEFAULT_INITAL_CAPCITY = 16; // default size;
    
        static final float DEFAULT_LOAD_FACTOR = 0.75f; // default load factor
    
        transient int capcity;//容量
    
        transient float factor;//加载因子
    
        transient int threshold;//桶值
    
        transient int size;//大小
    
        transient Entry[] table;//hash表
    
        public MyHashMap(int capcity, float factor) {
            if (capcity < 0 || capcity > MAX_CAPCITY || Float.isNaN(factor) || factor <= 0)
                throw new IllegalArgumentException();
            this.capcity = 1;
            while(this.capcity < capcity){
                this.capcity<<=1;
            }
            this.factor = factor;
            table = new Entry[this.capcity];
            threshold = (int) (capcity * factor);
        }
    
        public MyHashMap() {
            this(DEFAULT_INITAL_CAPCITY, DEFAULT_LOAD_FACTOR);
        }
    
        /*
         * return size
         * 
         * @see java.util.AbstractMap#size()
         */
        public int size() {
            return size;
        }
    
        public V get(Object k) {
            int hash = hash(k.hashCode());
            for (Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
                if (e.h == hash && e.k == k || e.k.equals(k)) {
                    return e.v;
                }
            }
            return null;
        }
    
        public V put(K key, V value) {
            int hash = hash(key.hashCode());// 计算键hash值
            int index = indexFor(hash, table.length);// 计算出index
            // 遍历当前列是否有存在所添加的k
            for (Entry<K, V> column = table[index]; column != null; column = column.next) {
                // 如果存在覆盖
                if (column.h == hash && key == column.k || key.equals(column.k)) {
                    V oldv = column.v;
                    column.v = value;
                    return oldv;
                }
            }
            addEntry(hash, key, value, index);// 往列中添加新数据
            return value;
        }
    
        /**
         * 
         * 添加新对象
         */
        public void addEntry(int hash, K k, V v, int index) {
            table[index] = new Entry<K, V>(hash, k, v, table[index]);
            if (size++ >= threshold)
                resize(table.length * 2);// 新数组容量
        }
        
        /**
         * 调整数组大小
         * @Author : JimmyYong
         */
        void resize(int newLength) {
            if (table.length == MAX_CAPCITY) {// 当达到最大容量
                threshold = Integer.MAX_VALUE;// 直接int最大值
                return;// 数组不变
            }
            Entry[] entry = new Entry[newLength];
            transfer(entry);
            table=entry;
            threshold = (int) (newLength * factor);
        }
        
        /**
         * 从组hash表
         * @Author : JimmyYong
         */
        void transfer(Entry[] entry) {
            // 从组hash表
            for (int i = 0; i < table.length; i++) {
                for (Entry<K, V> e = table[i]; e != null; e = e.next) {
                    // 取到索引
                    int index = indexFor(e.h, entry.length);
                    e.next = entry[index];
                    entry[index] = e;
                    table[i]=null;
                }
            }
        }
    
        @SuppressWarnings("hiding")
        private class Entry<K, V> {
            K k;
            V v;
            Entry<K, V> next;
            int h;
    
            public Entry(int h, K k, V v, Entry<K, V> next) {
                this.h = h;
                this.k = k;
                this.v = v;
                this.next = next;
            }
            public K getKey(){
                return k;
            }
            public V getValue(){
                return v;
            }
        }
    
        public Set<java.util.Map.Entry<K, V>> entrySet() {
    
            return null;
        }
    
        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);
        }
    
        /**
         * Returns index for hash code h.散列
         */
        static int indexFor(int h, int length) {
            return h & (length - 1);
        }
    
        public void putAll(Map map) {
            int targetCapcity = map.size();
            if (targetCapcity >= MAX_CAPCITY) {
                targetCapcity = MAX_CAPCITY;
            }
            int newLength = table.length;
            while (newLength < targetCapcity) {
                newLength <<= 1;
            }
            resize(newLength);
            for (int index = 0; index < table.length; index++) {
                for (Entry<K, V> e = table[index]; e != null; e = e.next) {
                    
                }
            }
        }
        
        public V remove(Object k){
            int hash=hash(k.hashCode());
            int index=indexFor(hash,table.length);
            Entry<K,V> previous = table[index];//后一个对象
            for(Entry<K,V> e = previous; e != null; e = e.next){
                if(e.h == hash && e.k == k || e.k.equals(k)){
                    size--;
                    //判断是否为链表的头
                    if(e == table[index]){
                        table[index] = null;
                        return e.v;
                    }
                    previous.next=e.next;//移除
                    return e.v;
                }
                previous=e;
            }
            return null;
        }
        
        
        private abstract class MyHashIterator<E> implements Iterator<E> {
    
            int index;// 保存table数组的index
            Entry<K, V> current;// 当前对象
            Entry<K, V> next = table[index];// 下一个对象
            int length = table.length;
            public MyHashIterator(){
                for(;next==null && index<table.length-1;next=table[++index]);
            }
            public boolean hasNext() {
                return next!=null;
            }
    
            public Entry<K, V> getEntry() {
                current = next;
                if (current != null) {
                    next = current.next;
                }
                for (; next == null && index < length-1; next = table[++index]);
                return current;
            }
    
            public void remove() {
                
            }
        }
        
        private class MyKeyIterator extends MyHashIterator<K>{
            public K next() {
                return  getEntry().getKey();
            }
        }
        
        private class MyValueIterator extends MyHashIterator<V>{
            public V next() {
                return  getEntry().getValue();
            }
        }
        
        private class MyEntryIterator extends MyHashIterator<Entry<K,V>>{
            public Entry<K,V> next() {
                return  getEntry();
            }
        }
        
        public Iterator<K> getMyKeyIterator(){
            return new MyKeyIterator();
        }
        
        public Iterator<V> getMyValueIterator(){
            return new MyValueIterator();
        }
        
        public Iterator<Entry<K,V>> getMyEntryIterator(){
            return new MyEntryIterator();
        }
    
        public Iterator<K> iterator() {
            return getMyKeyIterator();
        }
    }

    此类我优化了很久,还是不能超过hashmap的速度。极度郁闷!看来还要努力学习!
    源码下载

  • 相关阅读:
    cmd启动数据库时,出现 (无法启动此程序,因为计算机中丢失VCRUNTIME140_1.dll 尝试重新安装此程序以解决此问题 )解决方法。
    浅谈Promise语法API+封装
    浅谈JS回调地狱
    MySQL数据库安装步骤
    将MongoDB安装为Windows服务---安装MongoDB服务
    后缀.msc文件是什么?
    Mongoose类库使用教程---实现增删改查
    MongoDB可视化工具--Robo 3T 安装使用教程
    久违的锻炼
    出差(2~十四)
  • 原文地址:https://www.cnblogs.com/JimmyXie/p/2889419.html
Copyright © 2020-2023  润新知