Java刷题知识点之HashMap的实现原理、HashMap的存储结构、HashMap在JDK1.6、JDK1.7、JDK1.8之间的差异以及带来的性能影响

简介:

HashMap的实现原理

  HashMap是基于java.util.map接口的实现,该实现提供了所有的对Map的可选操作,同时也允许null类型的key以及value (HashTable与此大致相同,只是HashTable是同步的,不过HashTable一般被认为是已经过时的,很少有人再去用了)

  HashMap不保证Map中的顺序,特别是不能保证数据在一段时间内的顺序性。

  如果散列函数(Hash函数)在正确的在桶中分散元素,HashMap的实现提供了对基本的操作(put与get)时间性能为常数。

  集合视图的迭代需要的时间与HashMap实例的容量capacity值(桶数)和大小(键值对个数)成正比,所以如果对迭代器的性能比较关注, 那么不要把初始化的容量capacity值设的太高或不要将装填因子load factor设的太小就非常重要。

  HashMap的实例总有两个非常重要的影响性能的参数:初始容量大小(initial capacity)与装填因子(load factor)。

       在HashMap中,capacity就是buckets的数量,初始容量大小就是在HashMap创建的时候指定的capacity。

  装填因子指的是HashMap在多满的时候就需要提前自动扩容了(如初始化容量16,装填因子0.75,一旦元素个数 超过16*0.75=12个的时候,就需要对容量进行扩充,翻倍) 扩容需要rehash,也就是说HashMap内部结构会被重新构建。

  通常来讲,默认的装填因子0.75提供了一个基于时间与空间代价比较好的平衡。桶中装的更满意味着空间开销更低, 但是查找开销变大(反映到HashMap中就是包含get与put在内各种操作开销变大,时间变长)。

  当设置初始容量大小时,应该考虑HashMap中预期的数量以及装填因子,这样才能让rehash执行的次数最少, 如果初始化大小的值比实际数据的条数乘以装填因子的积还要大,那么就不会发生rehash。

  如果有非常多的记录存在HashMap中,那么在初始化这个HashMap的时候将容量capacity设置的足够大,比自动rehash 扩容效率更高。

  注意如果很多key有了相同的hashCode值,那么会对性能造成很大的影响(产生聚集问题,rehash次数变多,性能下降),为了改善这种情况 当key可以进行比较(继承了Comparable接口之类的),HashMap会采用对key进行比较排序。

  注意:HashMap不是线程同步的。

  如果多线程并发访问一个HashMap,同时至少有一个线程修改了HashMap的结构,那么该HashMap必须要在外部进行同步 (结构的改变指的是添加或删除一个映射关系-键值对,只是根据key修改了value的值不会对HashMap造成结构上的影响) 这种同步操作通常在封装有HashMap的Object中实现;如果没有这样的对象实现,那么HashMap需要采用Collections.synchronizedMap 来保证多线程并发环境下的数据同步问题,这个操作最好在创建HashMap的时候就去做。 Map m = Collections.synchronizedMap(new HashMap(…));

  如果HashMap产生的迭代器创建后,HashMap数据结构又发生了变化,则除了remove方法外,迭代器iterator会抛出一个 ConcurrentModificationException异常,这就是迭代器的快速失败(fail-fast)。 因此面对并发修改,迭代器采用快速而干净的失败来取代在未来的某一个不确定的时间产生不确定的后果。

  注意:迭代器的快速失败行为是无法保证的,通常来讲,在非同步并发修改的情况下这种硬性保证都没法给出,快速失败也只能尽量抛出 ConcurrentModificationException异常,所以不能写一段代码去基于该异常做出义务逻辑处理,最好快速失败进行BUG探测。

HashMap的存储结构

  HashMap的存储,本质上是构造了一个table,根据计算的hashCode将对应的KV存储到该table中,一旦发生hashCode冲突,那么就会将该KV放到对应的已有元素的后面, 此时,形成了一个链表式的存储结构,即:HashMap 就是一个table数组  +   链表实现的存储结构(在JDK1.8之前的存储结构),如下图所示:

  

   从上图中,我们可以发现哈希表是由数组  +   链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。它的内部其实是用一个Entity数组来实现的,属性有key、value、next。

牛客网Java刷题知识点之数组、链表、哈希表、 红黑二叉树

 

 

 

 

 

  当构造出一个HashMap后,每次put一个元素时,会执行addEntry操作,addEntry中会执行创建一个Entry实体存储当前的KV,如果有Hash冲突, 那么就会将当前的entry指向最后一个冲突的entry,同时将当前的entry放到链表中,执行完当前操作后,判断是否需要resize,如果需要,则构造出一个新的entry数组(比之前大一倍), 并将原有的数组中的entry链表(或entry)拷贝到新的数组中。

   在JDK1.8中有了一些变化,当链表的存储的数据个数大于等于8的时候,不再采用链表存储,而采用了红黑树存储结构。如下图所示:

 

   这么做主要是查询的时间复杂度上,链表为O(n),而红黑树一直是O(logn),一般来讲,冲突(即为相同的hash值存储的元素个数) 超过8个,红黑树的性能高于链表的查找性能。

 

 

 

 

 

 

 

HashMap在JDK1.6中的实现方式

  put方法:

  put方法完成数据的基本写入操作,同时会验证数据的有效性,如key是否为空的处理,计算hash值,hash值 的查找,并根据hash值找出所有的数据,判断是否重复,如果重复,则替换,并返回旧值,如果不重复,则写入数据(addEntry)。

复制代码
/**
    * Associates the specified value with the specified key in this map.
    * If the map previously contained a mapping for the key, the old
    * value is replaced.
    *
    * @param key key with which the specified value is to be associated
    * @param value value to be associated with the specified key
    * @return the previous value associated with <tt>key</tt>, or
    *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
    *         (A <tt>null</tt> return can also indicate that the map
    *         previously associated <tt>null</tt> with <tt>key</tt>.)
    */
   public V put(K key, V value) {
       if (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;
   }
复制代码

 

 

  对应的addEntry方法: addEntry方法实现了数据的写入以及判断是否需要对HashMap进行扩容。

复制代码
/**
 * Adds a new entry with the specified key, value and hash code to
 * the specified bucket.  It is the responsibility of this
 * method to resize the table if appropriate.
 *
 * Subclass overrides this to alter the behavior of put method.
 */
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);
}
复制代码

 

 

 

 

  resize方法: resize方法主要是对Map进行扩容的实际操作:构造存储结构,并调用数据转移方法transfer进行数据转移

复制代码
/**
     * Rehashes the contents of this map into a new array with a
     * larger capacity.  This method is called automatically when the
     * number of keys in this map reaches its threshold.
     *
     * If current capacity is MAXIMUM_CAPACITY, this method does not
     * resize the map, but sets threshold to Integer.MAX_VALUE.
     * This has the effect of preventing future calls.
     *
     * @param newCapacity the new capacity, MUST be a power of two;
     *        must be greater than current capacity unless current
     *        capacity is MAXIMUM_CAPACITY (in which case value
     *        is irrelevant).
     */
    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 方法: 完成数据转移,注意,转移的过程就是两个数组进行数据拷贝的过程,数据产生了倒置,新元素其实是插入到了第一个位置。

复制代码
 /**
     * Transfers all entries from current table to 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);
            }
        }
    }
复制代码

 

 

 

 

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

  ···
}
复制代码

 

 

 

 

 

 

 

 

 

HashMap在JDK1.7中的实现

  JDK1.7中的实现与JDK1.6中的实现总体来说,基本没有改进,区别不大。

  put 方法:

复制代码
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;
    }
复制代码

 

 

 

  addEntry方法:

复制代码
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);
    }
复制代码

 

 

  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, initHashSeedAsNeeded(newCapacity));
    table = newTable;
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
复制代码

 

 

  transfer方法:

复制代码
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;
            }
        }
复制代码

 

 

 

  createEntry方法:

复制代码
/**
    * Like addEntry except that this version is used when creating entries
    * as part of Map construction or "pseudo-construction" (cloning,
    * deserialization).  This version needn't worry about resizing the table.
    *
    * Subclass overrides this to alter the behavior of HashMap(Map),
    * clone, and readObject.
    */
   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++;
   }
复制代码

 

 

 

 

 

 

HashMap在JDK1.8中的实现

  put方法: put方法中与JDK1.6/1.7相比,新增了判断是否是TreeNode的逻辑,TreeNode即为红黑树, 同时添加了冲突的元素个数是否大于等于7的判断(因为第一个是-1,所以还是8个元素),如果冲突的元素个数超过8个,则构造红黑树(treeifyBin方法),否则还是按照链表方式存储

复制代码
/**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)//取模运算,寻址无冲突,写入数据
            tab[i] = newNode(hash, key, value, null);
        else {//存在冲突
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)//集合中已经是红黑树了
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {//冲突时的数据写入逻辑,判断是否需要构造红黑树
                    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;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
复制代码

 

 

 

  treeifyBin方法: 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;
       if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
           resize();
       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);
       }
   }
复制代码

 

 

 

  resize方法: 注意:JDK1.8中resize方法扩容时对链表保持了原有的顺序。

复制代码
/**
    * Initializes or doubles table size.  If null, allocates in
    * accord with initial capacity target held in field threshold.
    * Otherwise, because we are using power-of-two expansion, the
    * elements from each bin must either stay at same index, or move
    * with a power of two offset in the new table.
    *
    * @return the table
    */
   final Node<K,V>[] resize() {
       Node<K,V>[] oldTab = table;
       int oldCap = (oldTab == null) ? 0 : oldTab.length;
       int oldThr = threshold;
       int newCap, newThr = 0;
       if (oldCap > 0) {
           if (oldCap >= MAXIMUM_CAPACITY) {
               threshold = Integer.MAX_VALUE;
               return oldTab;
           }
           else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                    oldCap >= DEFAULT_INITIAL_CAPACITY)
               newThr = oldThr << 1; // double threshold
       }
       else if (oldThr > 0) // initial capacity was placed in threshold
           newCap = oldThr;
       else {               // zero initial threshold signifies using defaults
           newCap = DEFAULT_INITIAL_CAPACITY;
           newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
       }
       if (newThr == 0) {
           float ft = (float)newCap * loadFactor;
           newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                     (int)ft : Integer.MAX_VALUE);
       }
       threshold = newThr;
       @SuppressWarnings({"rawtypes","unchecked"})
           Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
       table = newTab;
       if (oldTab != null) {
           for (int j = 0; j < oldCap; ++j) {
               Node<K,V> e;
               if ((e = oldTab[j]) != null) {
                   oldTab[j] = null;
                   if (e.next == null)
                       newTab[e.hash & (newCap - 1)] = e;
                   else if (e instanceof TreeNode)
                       ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                   else { // preserve order
                       Node<K,V> loHead = null, loTail = null;
                       Node<K,V> hiHead = null, hiTail = null;
                       Node<K,V> next;
                       do {
                           next = e.next;
                           if ((e.hash & oldCap) == 0) {
                               if (loTail == null)
                                   loHead = e;
                               else
                                   loTail.next = e;
                               loTail = e;
                           }
                           else {
                               if (hiTail == null)
                                   hiHead = e;
                               else
                                   hiTail.next = e;
                               hiTail = e;
                           }
                       } while ((e = next) != null);
                       if (loTail != null) {
                           loTail.next = null;
                           newTab[j] = loHead;
                       }
                       if (hiTail != null) {
                           hiTail.next = null;
                           newTab[j + oldCap] = hiHead;
                       }
                   }
               }
           }
       }
       return newTab;
   }
复制代码

 

 

 

 

 

 

 

HashMap的不足以及产生原因

  多线程并发问题

  在多线程并发环境下会有如下一种情况:

  两个线程同时操作时同时遇到HashMap需要扩容,且映射到相同的地址(key计算得到的HashCode相同),此时在扩容时可能发生一种情况, 两个线程同时对HashMap进行扩容,扩容时做第一次循环时一个线程阻塞,另一个完成扩容,前一个继续,那么就可能发生链表数据的相互指向问题, 造成get数据时遍历的死循环

 

  测试代码如下:

复制代码
final Map<Integer, String> map = new HashMap<>(2);
        map.put(3, "3");
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                map.put(5, "5");
            }
        }, "thread");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                map.put(7, "7");
            }
        }, "thread2");

        t.start();
        t2.start();
        t.join();
        t2.join();
复制代码

 

 

  注意:我参考了大量的网上的一些博客,他们基本都是基于JDK1.6进行的分析,即:先写入数据,再扩容:

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);//再扩容
    }

 

 

  由此,很容易产生上述问题,而在JDK1.7中发生了变化,先扩容,再进行数据写入:

复制代码
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);//再写入数据
    }
复制代码

  此时,先扩容,再写入数据,就不会产生这种问题,但是会产生另一中问题,即: HashMap中有A元素,thread1 写入B元素(碰撞),thread2写入C元素(碰撞) 线程thread1先扩容,在写入数据,同时线程thread2也进行该操作那么,最终互不影响, thread1得到:B->A链表,thread2得到:C->A链表,那么,在thread1中取数据,能否取到C元素? 多线程并发问题依旧存在。

 

 

  在JDK1.8中,HashMap的这块儿做了大量改进,死循环问题已经解决了:

复制代码
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                  boolean evict) {
       //......
               for (int binCount = 0; ; ++binCount) {
                   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;
               }
           //......
   }
复制代码

 

 

  但是JDK1.8 中通过测试发现依然存在JDK1.7中的数据丢失情况。

  同时在并发情况下会出现如下新的问题: Hash碰撞情况下:前一个线程判断是链表存储,准备写入数据,但是写入时阻塞,其他线程也准备写入,发现数据链表为8,此时构造红黑树,然后完成数据转移 前一个线程此后写入数据时,就会出现类型错误:

复制代码
Exception in thread "24590 _subThread" java.lang.ClassCastException: java.util.HashMap$Node cannot be cast to java.util.HashMap$TreeNode
    at java.util.HashMap$TreeNode.moveRootToFront(HashMap.java:1827)
    at java.util.HashMap$TreeNode.treeify(HashMap.java:1944)
    at java.util.HashMap$TreeNode.split(HashMap.java:2170)
    at java.util.HashMap.resize(HashMap.java:713)
    at java.util.HashMap.putVal(HashMap.java:662)
    at java.util.HashMap.put(HashMap.java:611)
    at com.lin.map.HashMapDeadLoopTest$1$1.run(HashMapDeadLoopTest.java:37)
    at java.lang.Thread.run(Thread.java:745)
复制代码

 

 

相关测试代码(可能需要多执行几次):

复制代码
public class HashMapDeadLoopTest {

    @Test
    public void test() throws InterruptedException {
//        final Map<String, String> map = Collections.synchronizedMap(new HashMap<>(2048));
        final Map<String, String> map = new HashMap<>(2048);
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100000; i++) {
                    final int x = i;
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            put(map, x);
                        }
                    },i+" _subThread").start();
                    if(i!=0 && i%10000==0){
                        System.out.println("1:"+map.size());
                    }

                }
            }
        },"thread");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100000; i++) {
                    final int x = i;
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            put(map, x);
                        }
                    },i+" _subThread2").start();
                    if(i!=0 && i%10000==0){
                        System.out.println("2:"+map.size());
                    }

                }
            }
        },"thread2");
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100000; i++) {
                    final int x = i;
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            put(map, x);
                        }
                    },i+" _subThread3").start();
                    if(i!=0 && i%10000==0){
                        System.out.println("3:"+map.size());
                    }

                }
            }
        },"thread3");

        t.start();
        t2.start();
        t3.start();
        t.join();
        t2.join();
        t3.join();
    }

    String getKey(){
        return RandomStringUtils.randomAlphanumeric(32);
    }
    void put(Map<String,String> map,int index){
        String key = getKey();
        map.put(key, key);
        if(map.get(key)==null){
            System.out.println(key+",元素缺失,index:"+index);
        }
    }
}
复制代码

 

 

 

 

总结

  • HashMap的JDK1.6、JDK1.7、JDK1.8中的实现各不相同,尤其以JDK1.8变化最大,注意区分
本文转自大数据躺过的坑博客园博客,原文链接:http://www.cnblogs.com/zlslch/p/7622756.html,如需转载请自行联系原作者
相关文章
|
27天前
|
存储 算法 安全
HashMap的实现原理,看这篇就够了
关注【mikechen的互联网架构】,10年+BAT架构经验分享。深入解析HashMap,涵盖数据结构、核心成员、哈希函数、冲突处理及性能优化等9大要点。欢迎交流探讨。
HashMap的实现原理,看这篇就够了
|
6天前
|
IDE Java 编译器
开发 Java 程序一定要安装 JDK 吗
开发Java程序通常需要安装JDK(Java Development Kit),因为它包含了编译、运行和调试Java程序所需的各种工具和环境。不过,某些集成开发环境(IDE)可能内置了JDK,或可使用在线Java编辑器,无需单独安装。
|
26天前
|
设计模式 Java API
[Java]静态代理与动态代理(基于JDK1.8)
本文介绍了代理模式及其分类,包括静态代理和动态代理。静态代理分为面向接口和面向继承两种形式,分别通过手动创建代理类实现;动态代理则利用反射技术,在运行时动态创建代理对象,分为JDK动态代理和Cglib动态代理。文中通过具体代码示例详细讲解了各种代理模式的实现方式和应用场景。
23 0
[Java]静态代理与动态代理(基于JDK1.8)
|
1月前
|
Java
Java基础之 JDK8 HashMap 源码分析(中间写出与JDK7的区别)
这篇文章详细分析了Java中HashMap的源码,包括JDK8与JDK7的区别、构造函数、put和get方法的实现,以及位运算法的应用,并讨论了JDK8中的优化,如链表转红黑树的阈值和扩容机制。
26 1
|
1月前
|
存储 算法 索引
HashMap底层数据结构及其增put删remove查get方法的代码实现原理
HashMap 是基于数组 + 链表 + 红黑树实现的高效键值对存储结构。默认初始容量为16,负载因子为0.75。当存储元素超过容量 * 负载因子时,会进行扩容。HashMap 使用哈希算法计算键的索引位置,通过链表或红黑树解决哈希冲突,确保高效存取。插入、获取和删除操作的时间复杂度接近 O(1)。
29 0
|
1月前
|
存储 算法 安全
HashMap常见面试题(超全面):实现原理、扩容机制、链表何时升级为红黑树、死循环
HashMap常见面试题:红黑树、散列表,HashMap实现原理、扩容机制,HashMap的jd1.7与jdk1.8有什么区别,寻址算法、链表何时升级为红黑树、死循环
|
1月前
|
安全 Java 编译器
Java基础-知识点(二)
Java基础-知识点(二)
13 0
|
1月前
|
存储 缓存 安全
Java基础-知识点(一)
Java基础-知识点(一)
17 0
|
存储 SQL 关系型数据库
最新精心整理Java面试题,实现原理分析
最新精心整理Java面试题,实现原理分析
|
5天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
下一篇
无影云桌面