HashMap理解

JDK1.7
//HashMap get方法解析
public V get(Object key) {
        if (key == null)//如果传入key 为null 直接调用getForNullKey()方法
            return getForNullKey();
            //否则调用内部链表Entry<K,V> 的getEntry()方法
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }

//内部类 Entry<K,V>的 getEntry方法
    final Entry<K,V> getEntry(Object key) {
        //判断链表是否为kong 
        if (size == 0) {//size==0 代表链表没有数据 返回null
            return null;
        }
//hash算法 计算 key的hash值
        int hash = (key == null) ? 0 : hash(key);
        //调用indexFor() 进行位与运算 目的: 计算key命中的table数组的索引值
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            //遍历该table数组的链表 当前e==链表的头部. 循环下一次为e.next(链表指向下一个值)
            Object k;

            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
            //这是源码的if语句  为了理解我拆分了下. start
            if (e.hash==hash) {//如果链表的头部的e.hash值和传入key的hash值一样的话.
                k=e.key//给 对象k赋值==链表e.key
                if (k==key||(key!=null&&key.equals(k))) {
                //如果 链表的key 和 传入key相同 或者 链表key不为null,并且链表的 key与传入key相同;   
                    //返回当前链表的的value值;
                }
            }
            //end
        }//否则返回null;
        return null;
    }

    //indexFor
    static int indexFor(int h, int length) {
        //位与运算符(&)
        //运算规则:两个数都转为二进制,然后从高位开始比较,如果两个数都为1则为1,否则为0。
        return h & (length-1);
    }



//内部 getForNullKey
    private V getForNullKey() {
        //如果hashmap的size==0 既没有元素.返回null
        if (size == 0) {
            return null;
        }
        //调用put方法的时候,如果key==null 会默认存入 table[]数组的 table[0]上
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            //解释: for循环 Entry<K,V> e= table[0] 的链表的当前值,下一次循环指向链表的next.
            if (e.key == null)//当e.key==null 既 链表的e.key值==null 返回value;
                return e.value;
        }//否则返回null;既没有值
        return null;
    }

//HashMap put方法解析
public V put(K key, V value) {
    //如果当前table数组 为空数组的话
        if (table == EMPTY_TABLE) {
            //这个方法初始化table数组.
            inflateTable(threshold);
        }

        if (key == null)//如果key为null
            return putForNullKey(value);//调用putForNullKey table[0]=new Entry();K=null,V=value hash=0
        int hash = hash(key);//否则 计算传入对象key 的hash值;
        int i = indexFor(hash, table.length);
        /**计算table数组的索引值/计算桶的位置, 
        *indexFor(hash, table.length);//会产生hash碰撞问题.
        *既根据桶的长度和hash值计算有可能会命中一个已存在的桶.而不是空桶
        */
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            //循环table[i]位置的链表 循环第一个是链表头部; 下一个是链表的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;
            }
            // create Mrs.zhao start
            if (e.hash==hash) {//链表e.hash值和传入对象Key的hash值相等
                Object k;//声明对象k
                k=e.key;
                if (k==key||key.equals(k)) {//链表头部 e.key==传入key或者传入对象key和k相同
                    //对象V OldValue=当前链表e.value;
                    V oldValue = e.value;
                    //当前链表头部e.value=传入value 链表e.value 替换;
                    e.value = value;
                    e.recordAccess(this);//不知道干嘛的
                    //返回旧value
                     return oldValue;
                }
            }
            //end 注意: 如果
        }
        //当前hashMap 修改次数增加.
        modCount++;
        //往 table[i]的数组新增链表头部;

        addEntry(hash, key, value, i);//hash碰撞以后.这里的方法会调用构造函数new Entry(),来构建一个新Entry,把原来的Entry,作为新Entry的next节点.
        return null;
    }
//内部Entry 方法 addEntry(int hash{hash值}, K key{key}, V value{value}, int bucketIndex{table数组/table桶的位置})
    void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            //如果当前hashMap的 table[]数组的size的大小>=threshold 临界值 并且 table[bucketIndex] 不为null 即新增对象不为null
            resize(2 * table.length);//执行扩容操作 扩容为原来table.size的2倍.
            //
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }

// 扩方方法
    void resize(int newCapacity) {
        Entry[] oldTable = table;//oldTable赋值table
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {//判断当前table大小是否达到最大值.
            threshold = Integer.MAX_VALUE;//达到,不进行扩容.
            return;
        }

        Entry[] newTable = new Entry[newCapacity];//没有达到最大值,new一个新entry[]数组为size为原来的2倍
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;//tbale替换成新table
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);//重新计算临界值 threshold.
    }
// 旧table 转新table.
    void transfer(Entry[] newTable, boolean rehash) {

        int newCapacity = newTable.length;//取table数组大小
        for (Entry<K,V> e : table) {//循环旧table数组
            while(null != e) {//当链表不为null
                Entry<K,V> next = e.next;
                if (rehash) {//判断是否需要重新计算e.key的hash值
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);//计算链表e再新table中的位置.
                e.next = newTable[i];//如果i位置原来没有值,则直接插入;有值,采用链头插入法
                newTable[i] = e;//赋值;
                e = next;//e赋值e.next 进行下一次while循环知道e指向null
            }
        }
    }
// 创建链表方法 createEntry(hash, key, value, bucketIndex);
    void createEntry(int hash, K key, V value, int bucketIndex) {
        //去除table[i]位置的链表 entry 命名为 链表e -->old entry
        Entry<K,V> e = table[bucketIndex];
        //创建一个新链表, 把e old entry 作为链表的next节点.存放. 并且给table[i] 赋值为新的链表.
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

hashMap 的底层实现就是:数组➕链表  Node<K,V>  [] table;

hash碰撞模拟:

public static void main(String[] args) {
        HashMap<User, String> map = new HashMap<>();
        for (int i = 0; i < 65536; i++) {
            User user = new User();
            user.setId("100");
            map.put(user, user.toString());
        }
    }
    static class User {
        private String id;
        private String name;
        public User() {
        }
        @Override
        public String toString() {
            return "User{" +
                    "id='" + id + '\'' +
                    ", name='" + name + '\'' +
                    '}';
        }
        public String getId() {
            return id;
        }
        public void setId(String id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }

hashmap应对hash碰撞的解决途径:构建新链表,传入的value 作为新链表的头部.old value 作为新链表的next节点;

/**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
//h=计算的hahs值,k=put方法传入key,v=put方法传入的value,n=原链表对象
            value = v;
            next = n;
            key = k;
            hash = h;
        }

如何获取hashmap table数组里面的对象/既链表.

先给原文链接. https://blog.csdn.net/u013849486/article/details/52629008

方法是这样的.

String string = "nihao";
       // string += "<p>" + "hao ni";
        System.out.println(string);

        HashMap<String,Object> hashMap = new HashMap();
        for (int i = 0; i < 10; i++) {
            hashMap.put(i + string, i);
            hashMap.put(0 + string, UUID.randomUUID().toString());
        }
        // 利用反射,获取内部字段 "table"
        Class clsHashMap = null;
        Class clsHashMap$Node = null;
        Field[] f = null;
        Field t = null, fNode = null;
        try {
            clsHashMap = Class.forName("java.util.HashMap");
            clsHashMap$Node = Class.forName("java.util.HashMap$Entry");
            f = clsHashMap.getDeclaredFields();
            AccessibleObject.setAccessible(f, true);
            for (Field field : f) {
                // System.out.println(field.getName());
                if (field.getName() == "table")
                    t = field;

            }
            Object[] O = ((Object[]) t.get(hashMap));
            for (Object o : O) {
                if (o != null) {
                    System.out.println(o);
                    fNode = clsHashMap$Node.getDeclaredField("next");
                    fNode.setAccessible(true);
                    while ((o = fNode.get(o)) != null) {
                        System.out.println(o);
                    }
                }

            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

备注:

hashMap hashtable 区别

HashMap 的put`get 方法都没有 使用  synchronized关键字,所以在并发编程的时候不太安全.但是速度快啊! 单线程/某个方法域里的new hashMap 不会影响安全问题, 所以我们最常用的还是 hashMap

HashTable的put`get方法都有使用 synchronized关键字 并发编程同一时刻只有一个线程可以获取该HashTable对象. 保证写入,读取时安全的. 但是效率不高. 同理 单线程/某个方法域里的new hashmap 不会影响安全问题, 用HashTable/HashMap 没区别.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值