精华内容
下载资源
问答
  • HashMap

    2020-06-18 16:39:30
    HashMap基于Map接口实现,是一个用于存储Key-Value键值对的集合(允许null键和null值,但key不允许重复,故只能有一个键为null),每一个键值对也叫做Entry。HashMap不能保证放入元素的顺序,是无序的。 1.2 继承关系...

    注:主要记录自己学习过程中的心得,以Java8(jdk1.8)为主,另外在比较过程中会标明jdk1.7

    一、定义

    1.1 综述

    HashMap基于Map接口实现,是一个用于存储Key-Value键值对的集合(允许null键和null值,但key不允许重复,故只能有一个键为null),每一个键值对也叫做Entry。HashMap不能保证放入元素的顺序,是无序的。

    1.2 继承关系

    public class HashMap<k,v>extends AbstractMap<k,v> implements Map<k,v>,Cloneable,Serializable

    1.2基本属性

    static final int DEFAULT_INITIAL_CAPACITY =1<<4;//默认初始化大小16

    static final float DEFAULT_LOAD_FACTOR = 0.75f; //负载因子0.75

    static final Entry<?,?>[] EMPTY_TABLE={}; //初始化默认数组

    transient int size; //HashMap中元素的数量           

    1.3底层实现

      Java8之后:HashMap 由数组+链表+红黑树的形式构成   

       put方法:大致思路为:

                1.对key的hashCode()做hash,再计算index;

                2.如果没碰撞直接放到bucket里;如果碰撞了,以链表的形式存在buckets后 

                3,.如果碰撞导致链表过长(>=TREEIFY_THRESHOLD),就把链表转换成红黑树

                4.如果节点已经存在就替换old value 

                5.数据存放后,判断当前存入的对象个数,如果大于阈值(负载因子*HashMap的容量),则进行扩容。

    *hash碰撞:hashCode相同,key不同         

    二、hashCode算法与HashMap的hash算法                             

     2.1.hashCode:产生的依据:哈希码并不是完全唯一的,它是一种算法,让同一个类的对象按照自己不同的特征尽量的有不同的哈希码,                                                                                                        

    2.2.HashMap中的hash算法(java8):

    static final int hash(Object key) {
            int h;
            return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        }

    *为什么hash算法要逻辑右移16位,为什么用位异或

    原因就在于HashMap是通过tab[(n - 1) & hash]),即数组长度减一 位与 hash值的方式来计算数组下标的,而n的大小一般不会超过2^16(甚至更小) 此时 只有hash值的低16位甚至更低的位置参与位与运算,造成的结果就是冲突加剧,很明显这样不是一个好的散列算法。

    但是如果将hashCode的值右移16位,即取int类型的一半,并且使用异或运算,那么hashCode的更多位值参与到运算里,降低了冲突的产生,使结果更加均匀。

    至于为什么用位异或而不是位与或者位或,原因是&会使结果偏向0,|使结果偏向1。

    hashMap容量为什么建议是2的幂次方

    hash算法的目的就是让hash值尽量均匀的分布,再来看hashMap计算index的方法tab[(n - 1) & hash]),当容量为2的幂次方的时候,n-1转换为二进制所有位置都是1,这样进行位与运算时,是0或者是1完全取决于hash值对应位置的数值(位数取决于n-1)。

    eg: 当容量为9时,有两个key其hash值分别为

    10110010 11110010 11001110 00101011 , 11010101 01000100 00011111 01011101 

    且(9-1)转换为二进制为1000

    tab[(n - 1) & hash])运算的结果都为1000,因为位与的原因 hash值的低三位完全没起到作用,这背离了设计初衷。

    2.3..HashMap中的hash算法(java7):

    final int hash(Object k) {
            int h = hashSeed;
            if (0 != h && k instanceof String) {
                return sun.misc.Hashing.stringHash32((String) k);
            }
            h ^= k.hashCode();
            // 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);
        }

    可以看到Java8与7中的hash算法都用到了hashCode值,但对其处理过程却完全不同,Java8的hash算法已经说的很清楚了,在这来说说Java7的hash算法,可以看到在下图中做了好几次逻辑右移和异或,目的是明确的:就是尽量让每一位都参与运算,让相近的数最后通过hash能分散开并减少碰撞(但是为什么要这么做,为什么选择20,12,7,4,查阅了很多资料,最终还是没得到一个特别有说服力的答案,感兴趣的可以看看https://stackoverflow.com/questions/9335169/understanding-strange-java-hash-function)

    在自定义容量的时候最好是多少呢?

    HashMap h=new HashMap(n);

    由源码知,hashMap的容量大于阈值(负载因子*HashMap的容量)时,会扩容,而扩容会重新计算数据的位置,性能损失严重,故n必须大于预计数据量的1.33333(4/3)倍(默认负载因子0.75) ,另外hashmap并不是用户输入多少,初始化的时候容量就是多少,而是会对用户输入的n值进行判断,取第一个比(n-1)大的2的幂。

    三、Java7与Java8扩容

    3.1 java7:

          扩容需要满足两个条件:1.存放新值之前 当前已有元素的个数必须大于等于阈值

                                                  2.发生hash冲突

    public V put(K key, V value) {
         //省略部分代码。。。。
    
        //计算当前key的哈希值    
        int hash = hash(key);
        //通过哈希值和当前数据长度,算出当前key值对应在数组中的存放位置
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
          Object k;
          //如果计算的位置有值,且key值一样,则覆盖原值value,并返回原值value
          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;
      }

    put方法里面调用了addEntry(),很关键的一行代码if ((size >= threshold) && (null != table[bucketIndex]))

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

    通过代码可知,Java7中 HashMap当同时满足两个条件时才会扩容,同时从代码中也能看出:先扩容再执行插入操作

    仔细看这个if条件,会出现一个比较有意思的事,因为扩容必须两个条件都满足,我们假设前11个元素hashcode相同,那么他们会被放入同一个桶里,当put第12个元素的时候,如果hashcode还相同,此时if条件(11>=12)不满足,不会触发扩容。即如果接下来的15个元素每一个占一个桶,那么就会出现容量为16的hashmap存储了27个元素还没有进行扩容(默认负载因子为0.75,容量为16),当然这种极端情况现实中应该不会出现。。。

    满足扩容条件之后会调用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()将原数组里面的值放到新数组里
            transfer(newTable, initHashSeedAsNeeded(newCapacity));
            table = newTable;
            //设置新的阈值
            threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
        }

    关键在于resize方法中的transfer()方法。

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

    if(rehash)这行代码,true则重新进行计算key的hash值,false则不用计算,而rehash的来源在resize方法中可以看到rehash=initHashSeedAsNeeded(newCapacity)。

    /**
         * Initialize the hashing mask value. We defer initialization until we
         * really need it.
         */
        final boolean initHashSeedAsNeeded(int capacity) {
            //如果hashSeed!=0,表示当前正在使用备用哈希
            boolean currentAltHashing = hashSeed != 0;
            //如果VM启动了且map的容量大于阈值,则使用备用哈希
            boolean useAltHashing = sun.misc.VM.isBooted() &&
                    (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
            //异或操作
            boolean switching = currentAltHashing ^ useAltHashing;
            if (switching) {
                //把hashSeed设置为随机值
                hashSeed = useAltHashing
                    ? sun.misc.Hashing.randomHashSeed(this)
                    : 0;
            }
            return switching;
        }

    仔细看这个方法: boolean currentAltHashing,我们可以记住一点 ,刚开始hashSeed初始化的时候是赋值0的,也只有下面的if(switching)里面才会更改hashSeed的值,所以currentAltHashing的值为false。再看useAltHashing的值,它是由两个值的逻辑与运算决定的:sun.misc.VM.isBooted一般VM启动的时候他是为true的,而ALTERNATIVE_HASHING_THRESHOLD的值为integer的最大值,所以使用过程中capacity一般是小于后者的,为false,结果就是useAltHashing为false,最终导致的就是switching的值一般是false。

    (感兴趣的可以看看这个,从14:25开始看起,讲的很清楚https://www.bilibili.com/video/BV1vE411v7cR?p=4)

    回到上面的transfer方法,if(rehash),很明显rehash为false,所以得出结论:

    一般情况下,触发扩容的时候,不会去重新计算key的hash值,只会重新计算在数组中的位置

                    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;

        有意思的点在于扩容的时候需要将旧的元素移到扩容后的newTable里:

               同一个链表里最先put的元素会放到链表头,链表翻转。(比如:以前是A->B->C->D,扩容后变为D->C->B->A).扩容一次,翻转一次。

               扩容结束之后或者不满足扩容条件之后就会执行插入操作 (区别在于扩容后时会重新计算元素在表中的位置index)。

    而当不满足扩容条件时,会去执行插入操作createEntry(int  hash,K key,V value,int bucketIndex);

    void createEntry(int hash, K key, V value, int bucketIndex) {
            Entry<K,V> e = table[bucketIndex];
            table[bucketIndex] = new Entry<>(hash, key, value, e);
            size++;
        }

    3.2 Java8

     java 8扩容条件与7不同,在两种情况下都会扩容,先说结论(先插入再判断是否扩容):

        (1)存放新值之后,目前元素(包括新加入的这个值)的个数大于阈值,

        (2)发生hash冲突,存入链表长度(不包括新加入的key,虽然此时链尾的next指针已指向新key)>=8并且hash表的数组长度<64

     

    为了说明这两种扩容情况,我们从put方法说起吧:

    public V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
        }
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
            Node<K,V>[] tab; Node<K,V> p; int n, i;
            /**
             * 关键从这块开始,
             * Java8并不是在new的时候对对象初始化,(jdk7是在new的时候初始化)
             * 而是在这里当对象为空的时候才会调用resize进行初始化。
             */
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
            //插入的新元素没有冲突,则在对象存放位置创建新node,该新节点将作为这个位置链表的头结点
            if ((p = tab[i = (n - 1) & hash]) == null)
                tab[i] = newNode(hash, key, value, null);
            else {
                //如果将要插入的位置已有元素
                Node<K,V> e; K k;
                //并且该元素与将要插入的元素相同(==,equals),则直接覆盖
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                //如果不相等,则分两种情况:判断所要存储的位置是否为红黑树结构,是则调用putTreeVal
                else if (p instanceof TreeNode)
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                //不是红黑树,则为链表,进行循环遍历该链表
                else {
                    for (int binCount = 0; ; ++binCount) {
                        /**
                         * 如果存入位置所在链表下一个位置为空(从头结点下一个位置开始)
                         * 则将新节点直接存入(即头结点的next指针指向newNode)
                         * 接着binCount>=7(即判断这个链表的长度是否>=8,此时链表的长度计算不包括newNode)
                         * 满足条件,则调用treeifyBin方法(该方法会判断map长度是否<64,小于则触发扩容)
                         * 存入位置链表下一位置不为空,则判断是否相同,是则覆盖,不是则继续循环
                         */
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                            break;
                        }
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        p = e;
                    }
                }
                //在判断处理流程中,如果key相同(==,equals),则直接覆盖,将旧值返回
                if (e != null) { // existing mapping for key
                    V oldValue = e.value;
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;
            //size此时是不包括新元素的,所以++size包括新加入的元素,即目前键值对数>阈值,触发扩容
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);
            return null;
        }

    有耐心的可以看上面代码说明,附上如果binCount>7之后调用的treeifyBin方法:

    /**
         * Replaces all linked nodes in bin at index for given hash unless
         * table is too small, in which case resizes instead.
         */
        final void treeifyBin(Node<K,V>[] tab, int hash) {
            int n, index; Node<K,V> e;
            /**
             * 这里MIN_TREEIFY_CAPACITY=64,
             * tab.length等于hashmap的容量,是一个2的幂(而不是有的博主说的map的size)
             * 即当满足binCount>7且length<64时,触发扩容,不满足时转红黑树
             */
            if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
                resize();
            //既然都已经进入treeifyBin方法了,这边为什么还要进行一次判断没搞懂,没搞懂
            else if ((e = tab[index = (n - 1) & hash]) != null) {
                TreeNode<K,V> hd = null, tl = null;
                do {
                    TreeNode<K,V> p = replacementTreeNode(e, null);
                    if (tl == null)
                        hd = p;
                    else {
                        p.prev = tl;
                        tl.next = p;
                    }
                    tl = p;
                } while ((e = e.next) != null);
                if ((tab[index] = hd) != null)
                    hd.treeify(tab);
            }
        }

    :java7插入元素的方式是头插法,即往HashMap里面put元素时,此时新增的元素在链表上的头部

    四、get方法的流程

     

    未完。。。

    展开全文
  • 浅谈HashMap原理,并手写HashMap并实现部分区块链特征 写在前面 最近有很多的粉丝私信我,说自己在面试的时候,老是被人问HashMap的原理,但是在实际的工作中,也只是使用HashMap,从来就没有关注过它的原来,今天博...

    浅谈HashMap原理,并手写HashMap并实现部分区块链特征

    写在前面

    最近有很多的粉丝私信我,说自己在面试的时候,老是被人问HashMap的原理,但是在实际的工作中,也只是使用HashMap,从来就没有关注过它的原来,今天博主本人,根据自己的实际经验,浅谈一下HashMap的实现原理。并附上自己根据原理,手写的MHashMap,并且还带上了部分区块链的特征。

    JDK7和JDK8中的HashMap

    JDK7:数组和链式结构
    JDK8:数组和链式结构,红黑树
    两者之间的区别就是,JDK8中加上了红黑树。
    因为今天是一个HashMap的原理浅析,这里就不附带讲解红黑树,后面的博文,我会专门来讲解红黑树。

    正文

    现在我开始正式进入我对HashMap讲解中来。
    HashMap的特征

    存储结构:key,value键值对的形式

    查询数据:
    1,通过key进行mod(取模),获取到当前数组对应的node,
    2,根据node存放的hash和key对应的hash进行比较,是否一致,如果不一致,则同当前node的下一个node进行比对。
    3,还是不一致,就继续从当前比较的Node取下一个节点进行比较。

    存储数据:
    1,对key进行mod计算
    2,将数据存放到data[mod]中去
    3,如果当前的data[mod]已经有值存在了,则将当前已经存在的对象oldNode,存放到新的Node的next中去

    我们定义一个数组,并给给它一个默认的长度

      private final static int defaultDataLength = 16;
    

    再创建一个指定长度的数组

     private Node[] data = new Node[defaultDataLength];
    

    这里的Node为我们自己定义的一个内部类对象

        @Getter
        @Setter
        static class Node {
            private int hashCode;
            private Object key;
            private Object value;
            private Node next;
    
            public static Node instance(int hashCode, Object key, Object value, Node next) {
                Node node = new Node();
                node.setHashCode(hashCode);
                node.setKey(key);
                node.setValue(value);
                node.setNext(next);
                return node;
            }
        }
    

    它由四个元素组成,当前传入key的hash值,当前的key,当前的value及下一个对象。

    完整代码

    package com.moonl.jvm.utils;
    
    import lombok.Getter;
    import lombok.Setter;
    
    import java.io.Serializable;
    import java.util.AbstractMap;
    import java.util.Map;
    import java.util.Set;
    
    public class MHashMap<K, V> {
    
        private final static int defaultDataLength = 16;
    
        private Node[] data = new Node[defaultDataLength];
    
        private Integer previousHashCode = 0;
    
    
        public V put(K key, V value) {
            int hash = hash(key);
            int mod = hashMod(key);
            if (null == data[mod]) {
                data[mod] = Node.instance(hash, key, value, null);
                return value;
            }
            //旧的node缓存
            Node oldNode = data[mod];
            //新的key,存放到指定位置的数组中去,并将原有节点存放到next中
            data[mod] = Node.instance(hash, key, value, oldNode);
            return value;
        }
    
        public V get(K key) {
            Node node = data[hashMod(key)];
            //root node hashCode
            this.previousHashCode = hash((K) ("root_" + hashMod(key)));
            return findKey(node, key);
        }
    
    
        public V findKey(Node node, K key) {
    
            if (null == key) {
                return null;
            }
            if (node == null) {
                return null;
            }
            String chain = "The Previous HashCode:" + previousHashCode + " Now HashCode :" + node.getHashCode();
            //传入的key对应的hash
            int hash = hash(key);
            //当前节点存储的hashCode
            int hashCode = node.getHashCode();
            if (hash == hashCode) {
                System.out.println("input key is :" + key + " and hash:" + hashCode);
                System.out.println("query key is :" + node.getKey() + " and hash:" + node.getHashCode());
                return (V) node.getValue();
            }
            //当前找到的节点的下一个节点
            Node nextNode = node.getNext();
            //如果要从下一个节点开始查询,则当前节点变更为上一个节点
            previousHashCode = node.getHashCode();
            chain = chain + " Next Node HashCode :" + nextNode.getHashCode();
            System.out.println(chain);
            return findKey(nextNode, key);
        }
    
        public int hash(K key) {
            int hashCode = 0;
            return key == null ? 0 : key.hashCode();
        }
    
        public int hashMod(K key) {
            return Math.abs(key.hashCode() % 16);
        }
    
        @Getter
        @Setter
        static class Node {
            private int hashCode;
            private Object key;
            private Object value;
            private Node next;
    
            public static Node instance(int hashCode, Object key, Object value, Node next) {
                Node node = new Node();
                node.setHashCode(hashCode);
                node.setKey(key);
                node.setValue(value);
                node.setNext(next);
                return node;
            }
        }
    
    
    }
    

    在代码中,我对根节点,也就是数组节点的记录方式是"root_"+mod值共同生成的hash
    //
    现在我们开始使用封装数据(put),并逐步(get)数据

        @Test
        public void testHashMap() {
    //        Map<String, Object> maps = new HashMap<String, Object>();
            MHashMap<String, Object> zhaoMaps = new MHashMap<String, Object>();
            for (int i = 0; i < 1000; i++) {
                zhaoMaps.put("赵"+i+"姐","赵颖"+i+"姐 is 250");
            }
            MHashMap<String, Object> yingMaps = new MHashMap<String, Object>();
            for (int i = 0; i < 1000; i++) {
                yingMaps.put("大颖"+i+"姐","大颖"+i+"姐 is 250");
            }
            System.out.println(zhaoMaps.get("赵162姐"));
            System.out.println(yingMaps.get("大颖456姐"));
        }
    

    输出的结果是

    The Previous HashCode:-925311973 Now HashCode :-914498616 Next Node HashCode :-914499608
    The Previous HashCode:-914498616 Now HashCode :-914499608 Next Node HashCode :-914500600
    The Previous HashCode:-914499608 Now HashCode :-914500600 Next Node HashCode :-914501592
    The Previous HashCode:-914500600 Now HashCode :-914501592 Next Node HashCode :-914502584
    The Previous HashCode:-914501592 Now HashCode :-914502584 Next Node HashCode :-914503576
    The Previous HashCode:-914502584 Now HashCode :-914503576 Next Node HashCode :-914529368
    The Previous HashCode:-914503576 Now HashCode :-914529368 Next Node HashCode :-914530360
    The Previous HashCode:-914529368 Now HashCode :-914530360 Next Node HashCode :-914531352
    The Previous HashCode:-914530360 Now HashCode :-914531352 Next Node HashCode :-914532344
    The Previous HashCode:-914531352 Now HashCode :-914532344 Next Node HashCode :-914533336
    The Previous HashCode:-914532344 Now HashCode :-914533336 Next Node HashCode :-914560120
    The Previous HashCode:-914533336 Now HashCode :-914560120 Next Node HashCode :-914561112
    The Previous HashCode:-914560120 Now HashCode :-914561112 Next Node HashCode :-914562104
    The Previous HashCode:-914561112 Now HashCode :-914562104 Next Node HashCode :-914563096
    The Previous HashCode:-914562104 Now HashCode :-914563096 Next Node HashCode :-914584424
    The Previous HashCode:-914563096 Now HashCode :-914584424 Next Node HashCode :-914590872
    The Previous HashCode:-914584424 Now HashCode :-914590872 Next Node HashCode :-914591864
    The Previous HashCode:-914590872 Now HashCode :-914591864 Next Node HashCode :-914592856
    The Previous HashCode:-914591864 Now HashCode :-914592856 Next Node HashCode :-914614184
    The Previous HashCode:-914592856 Now HashCode :-914614184 Next Node HashCode :-914615176
    The Previous HashCode:-914614184 Now HashCode :-914615176 Next Node HashCode :-914621624
    The Previous HashCode:-914615176 Now HashCode :-914621624 Next Node HashCode :-914622616
    The Previous HashCode:-914621624 Now HashCode :-914622616 Next Node HashCode :-914643944
    The Previous HashCode:-914622616 Now HashCode :-914643944 Next Node HashCode :-914644936
    The Previous HashCode:-914643944 Now HashCode :-914644936 Next Node HashCode :-914645928
    The Previous HashCode:-914644936 Now HashCode :-914645928 Next Node HashCode :-914652376
    The Previous HashCode:-914645928 Now HashCode :-914652376 Next Node HashCode :-914673704
    The Previous HashCode:-914652376 Now HashCode :-914673704 Next Node HashCode :-914674696
    The Previous HashCode:-914673704 Now HashCode :-914674696 Next Node HashCode :-914675688
    The Previous HashCode:-914674696 Now HashCode :-914675688 Next Node HashCode :-914676680
    The Previous HashCode:-914675688 Now HashCode :-914676680 Next Node HashCode :-914703464
    The Previous HashCode:-914676680 Now HashCode :-914703464 Next Node HashCode :-914704456
    The Previous HashCode:-914703464 Now HashCode :-914704456 Next Node HashCode :-914705448
    The Previous HashCode:-914704456 Now HashCode :-914705448 Next Node HashCode :-914706440
    The Previous HashCode:-914705448 Now HashCode :-914706440 Next Node HashCode :-914707432
    The Previous HashCode:-914706440 Now HashCode :-914707432 Next Node HashCode :-914733224
    The Previous HashCode:-914707432 Now HashCode :-914733224 Next Node HashCode :-914734216
    The Previous HashCode:-914733224 Now HashCode :-914734216 Next Node HashCode :-914735208
    The Previous HashCode:-914734216 Now HashCode :-914735208 Next Node HashCode :-914736200
    input key is :162姐 and hash:-914736200
    query key is :162姐 and hash:-914736200
    赵颖162姐 is 250
    The Previous HashCode:-925311975 Now HashCode :-2010266582 Next Node HashCode :-2010267574
    The Previous HashCode:-2010266582 Now HashCode :-2010267574 Next Node HashCode :-2010268566
    The Previous HashCode:-2010267574 Now HashCode :-2010268566 Next Node HashCode :-2010269558
    The Previous HashCode:-2010268566 Now HashCode :-2010269558 Next Node HashCode :-2010270550
    The Previous HashCode:-2010269558 Now HashCode :-2010270550 Next Node HashCode :-2010271542
    The Previous HashCode:-2010270550 Now HashCode :-2010271542 Next Node HashCode :-2010296342
    The Previous HashCode:-2010271542 Now HashCode :-2010296342 Next Node HashCode :-2010297334
    The Previous HashCode:-2010296342 Now HashCode :-2010297334 Next Node HashCode :-2010298326
    The Previous HashCode:-2010297334 Now HashCode :-2010298326 Next Node HashCode :-2010299318
    The Previous HashCode:-2010298326 Now HashCode :-2010299318 Next Node HashCode :-2010300310
    The Previous HashCode:-2010299318 Now HashCode :-2010300310 Next Node HashCode :-2010301302
    The Previous HashCode:-2010300310 Now HashCode :-2010301302 Next Node HashCode :-2010302294
    The Previous HashCode:-2010301302 Now HashCode :-2010302294 Next Node HashCode :-2010326102
    The Previous HashCode:-2010302294 Now HashCode :-2010326102 Next Node HashCode :-2010327094
    The Previous HashCode:-2010326102 Now HashCode :-2010327094 Next Node HashCode :-2010328086
    The Previous HashCode:-2010327094 Now HashCode :-2010328086 Next Node HashCode :-2010329078
    The Previous HashCode:-2010328086 Now HashCode :-2010329078 Next Node HashCode :-2010330070
    The Previous HashCode:-2010329078 Now HashCode :-2010330070 Next Node HashCode :-2010331062
    The Previous HashCode:-2010330070 Now HashCode :-2010331062 Next Node HashCode :-2010332054
    The Previous HashCode:-2010331062 Now HashCode :-2010332054 Next Node HashCode :-2010333046
    The Previous HashCode:-2010332054 Now HashCode :-2010333046 Next Node HashCode :-2010355862
    The Previous HashCode:-2010333046 Now HashCode :-2010355862 Next Node HashCode :-2010356854
    The Previous HashCode:-2010355862 Now HashCode :-2010356854 Next Node HashCode :-2010357846
    The Previous HashCode:-2010356854 Now HashCode :-2010357846 Next Node HashCode :-2010358838
    The Previous HashCode:-2010357846 Now HashCode :-2010358838 Next Node HashCode :-2010359830
    The Previous HashCode:-2010358838 Now HashCode :-2010359830 Next Node HashCode :-2010360822
    The Previous HashCode:-2010359830 Now HashCode :-2010360822 Next Node HashCode :-2010361814
    The Previous HashCode:-2010360822 Now HashCode :-2010361814 Next Node HashCode :-2010362806
    The Previous HashCode:-2010361814 Now HashCode :-2010362806 Next Node HashCode :-2010363798
    The Previous HashCode:-2010362806 Now HashCode :-2010363798 Next Node HashCode :-2010385622
    The Previous HashCode:-2010363798 Now HashCode :-2010385622 Next Node HashCode :-2010386614
    The Previous HashCode:-2010385622 Now HashCode :-2010386614 Next Node HashCode :-2010387606
    The Previous HashCode:-2010386614 Now HashCode :-2010387606 Next Node HashCode :-2010388598
    The Previous HashCode:-2010387606 Now HashCode :-2010388598 Next Node HashCode :-2010389590
    The Previous HashCode:-2010388598 Now HashCode :-2010389590 Next Node HashCode :-2010390582
    The Previous HashCode:-2010389590 Now HashCode :-2010390582 Next Node HashCode :-2010391574
    The Previous HashCode:-2010390582 Now HashCode :-2010391574 Next Node HashCode :-2010392566
    The Previous HashCode:-2010391574 Now HashCode :-2010392566 Next Node HashCode :-2010393558
    The Previous HashCode:-2010392566 Now HashCode :-2010393558 Next Node HashCode :-2010394550
    The Previous HashCode:-2010393558 Now HashCode :-2010394550 Next Node HashCode :-2010416374
    The Previous HashCode:-2010394550 Now HashCode :-2010416374 Next Node HashCode :-2010417366
    The Previous HashCode:-2010416374 Now HashCode :-2010417366 Next Node HashCode :-2010418358
    The Previous HashCode:-2010417366 Now HashCode :-2010418358 Next Node HashCode :-2010419350
    input key is :大颖456姐 and hash:-2010419350
    query key is :大颖456姐 and hash:-2010419350
    大颖456姐 is 250
    
    

    通过输出的日志,我们可以看见,node节点的存储结构是按照区块链的特性,记录上个节点的信息和下一个节点的信息关系来存储数据。

    当然,这里我们对Node对象再写深入一些,加上一些共识的算法,加密解密验签的算法,并做节点的共享,那么一个简单的区块链就完成了。

    本文,主要是为了解答粉丝在面试过程中,面试官不停问HashMap原理而编写的,区块链就不做更多的阐述了。

    展开全文
  • Hashmap

    2020-07-28 11:05:59
    hashmap计算hashcode时 ,为什么会右移16位 减少碰撞 让值分布的更加均匀 计算位置时,hssh值会和length-1进行&...所以取高16位与低16位异或,取出高16位与低16位不同的地方,即各自的特征,进行位置的运算 ...

    hashmap计算hashcode时 ,为什么会右移16位

    减少碰撞 让值分布的更加均匀
    计算位置时,hssh值会和length-1进行&操作,在其长度范围内取hash值
    因为hashmap的长度一般不会超过16位,所以hash值的前16位可能不参与位置的计算,
    所以取高16位与低16位异或,取出高16位与低16位不同的地方,即各自的特征,进行位置的运算
    
    展开全文
  • HashMap基于hashing原理,用put和get存储和获取对象。当我们将键值传递给put方法时,put方法调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法...

    ①工作原理
    HashMap基于hashing原理,用put和get存储和获取对象。当我们将键值传递给put方法时,put方法调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。

    ②什么是HashMap
    HashMap实现了Map接口,Map接口对键值对进行映射。Map中不允许重复的键。Map接口有两个基本的实现,HashMap和TreeMap。TreeMap保存了对象的排列次序,而HashMap则不能。HashMap允许键和值为null。HashMap是非synchronized的,但collection框架提供方法能保证HashMap synchronized,这样多个线程同时访问HashMap时,能保证只有一个线程更改Map。

    ③HashSet和HashMap的区别(摘自他处)
    摘自一篇觉得不错的文章

    展开全文
  • Java源码之HashMap

    2020-10-19 15:54:12
    Java源码之HashMap特征存储实现源码分析构造方法put方法get方法 特征 HashMap是一个存储K-V的数据结构,key可以为null 时间复杂度为O(1),空间复杂度为O(n) 存储实现 HashMap底层存储实现是通过数组链表实现的 ![在...
  • hashmap

    2016-12-14 10:02:41
    而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留下来。 最后我们来看一下Peter Lawley的一篇专栏文章《An introduction to optimising a hashingstrategy》里的的一个实验:他随机选取了352个...
  • 关于HashMap

    2021-08-18 21:50:37
    HashMap类主要用来处理具有键值对特征的数据,随着JDK版本的更新,JDK1.8对HashMap底层也进行了优化 1、 HashMap是基于哈希表对Map接口的实现,HashMap具有较快的访问速度,但遍历顺序却是不确定的。 2、...
  • HashMap详解

    2019-01-09 09:40:55
    什么是HashMap? HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 HashMap的数据结构 在Java编程语言...
  • 最近在研究关于多线程环境下如何提升性能,在程序中执行最多的是“查询”,但同时也要维护数据的“添加”和“删除” 目前在 Hashtable 和 HashMap 中选择。 看了jdk文档,我们知道 Hashtable是同步...
  • HashMap 详解

    2020-05-17 17:25:22
    HashMap 详解1. 底层结构2. 加载因子3. 源码分析3.1 基本属性2.2 哈希函数 hash2.3 查询方法 get2.4 新增方法 put2.5 扩容方法3. 死循环分析4 线程不安全(rehash 和 resize)4.1 哈希碰撞 rehash4.2 扩容 resize6. ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 34,241
精华内容 13,696
关键字:

hashmap的特征