一文搞定HashMap的实现原理和面试

HashMap底层原理(详细介绍) 数组:其实所谓的数组指的就是一组相关类型的变量集合,并且这些变量彼此之间没有任何的关联。存储区间连续,占用内存严重,数组有下标,查询数据快,但是增删比较慢; 链表:一种常见的基础数据结构,是一种线性表,但是不会按照线性的顺序存储数据,而是每一个节点里存到下一个节点的指针。存储区间离散,占用内存比较宽松,使用链表查询比较慢,但是增删比较快; 哈希表:Hash table 既满足了数据的快速查询(... 阅读详情

前言

HashMap在日常开发中基本是天天见的,而且都知道什么时候需要用HashMap,根据Key存取Value,但是存和取的时候那些操作却是很少去研究。同时在面试中也是面试官们必问的。以下是基于JDK1.8

正文

先看看HashMap的结构图:

1. 先来认识一下HashMap中定义的一些需要了解的成员变量
// hashMap数组的初始容量 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 负载因子 0.75f;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 树形化阈值 8
static final int TREEIFY_THRESHOLD = 8;
// 解除树形化阈值 6
static final int UNTREEIFY_THRESHOLD = 6;
// 树形化的另一条件 Map数组的长度阈值 64
static final int MIN_TREEIFY_CAPACITY = 64
// 这个就是hashMap的内部数组了,而Node则是链表节点对象。
transient Node<K,V>[] table;
// 数组扩容阈值。
int threshold;

initialCapacity 数组的初始容量为16。可以在构造方法中指定。必须是2的幂次方。(16 → 32 → 64 …)

loadFactor 加载因子 0.75f。 所谓的加载因子就是HashMap的容量达到0.75时的时候会试试扩容resize(), (例:假设有一个 HashMap 的初始容量为 16 ,那么扩容的阀值就是 0.75 * 16 = 12 。也就是说,在你打算存入第 13 个值的时候,HashMap 会先执行扩容)。加载因子也能通过构造方法中指定,如果指定大于1,则数组不会扩容,牺牲了性能不过提升了内存。

TREEIFY_THRESHOLD 树形化阈值。当链表的节点个数大于等于这个值时,会将链表转化为红黑树。

UNTREEIFY_THRESHOLD  解除树形化阈值。当链表的节点个数小于等于这个值时,会将红黑树转换成普通的链表。

MIN_TREEIFY_CAPACITY树形化阈值的第二条件。当数组的长度小于这个值时,就算树形化阈达标,链表也不会转化为红黑树,而是优先扩容数组resize()。

threshold 数组扩容阈值。即:HashMap数组总容量 * 加载因子。当前容量大于或等于该值时会执行扩容**resize()**。扩容的容量为当前 HashMap 总容量的两倍。比如,当前 HashMap 的总容量为 16 ,那么扩容之后为 32 。

2. 继承关系
// table 内部数组是节点类型
static class Node<K,V> implements Map.Entry<K,V> {
     final int hash; 
     final K key;
     V value;
     Node<K,V> next; //下一个节点
    //省略...
}

拉链法的散列表是通过链表解决碰撞问题的,所以HashMap的内部数组是节点类型。 hash值是经过hash()方法处理过的hashCode,也就是数组的索引 bucket,为了使hashCode分布更加随机。

java.util.HashMap<K, V>.Node<K, V>
    java.util.LinkedMap<K, V>.Entry<K, V>
        java.util.HashMap<K, V>.TreeNOde<K, V>

TreeNode是Node是子类,继承关系如下:Node是单向链表节点,Entry是双向链表节点,TreeNode是红黑树节点。TreeNode的代码400多行都是写的红黑树。这个有点难度…可以自行去了解。

3. 先对HashMap的简单总结

HashMap是基于拉链法实现的一个散列表,内部由数组和链表和红黑树实现。

  1. 数组的初始容量为16,而容量是以2的次方扩充的,一是为了提高性能使用足够大的数组,二是为了能使用位运算代替取模预算(据说提升了5~8倍)。

  2. 数组是否需要扩充是通过负载因子判断的,如果当前元素个数为数组容量的0.75时,就会扩充数组。这个0.75就是默认的负载因子,可由构造传入。我们也可以设置大于1的负载因子,这样数组就不会扩充,牺牲性能,节省内存。

  3. 为了解决碰撞,数组中的元素是单向链表类型。当链表长度到达一个阈值时(7或8),会将链表转换成红黑树提高性能。而当链表长度缩小到另一个阈值时(6),又会将红黑树转换回单向链表提高性能,这里是一个平衡点。

  4. 对于第三点补充说明,检查链表长度转换成红黑树之前,还会先检测当前数组数组是否到达一个阈值(64),如果没有到达这个容量,会放弃转换,先去扩充数组。所以上面也说了链表长度的阈值是7或8,因为会有一次放弃转换的操作。

4. 深入了解源码
4.1 构造方法
// 默认数组初始容量为16,负载因子为0.75f
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
// 指定数组的初始容量
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
// 指定数组的初始容量 和 负载因子
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;

    // NaN:Not a Number。例如给-1开方就会得到NaN。
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                loadFactor);
    this.loadFactor = loadFactor;

    // 这个方法可以将任意一个整数转换成2的次方。
    // 例如输入10,则会返回16。
    // 另外,有人可能疑惑,不是说threshold是 数组容量 * loadFactor得到的吗?
    // 是的,在第一次put操作,扩充数组时,会将这个threshold作为数组容量,然后再重新计算这个值。
    this.threshold = tableSizeFor(initialCapacity);
}

在使用指定数组的初始容量时上面说过,数组容量必须是2的次方。所以就需要通过算法将我们给定的数值转换成2的次方。

// 这个方法可以将任意一个整数转换成2的次方。
static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

相关的位运算这里不做讲解。想了解的可以自己去查阅资料。

4.2 数组的索引 bucket

HashMap采用hash算法来决定集合中元素的存储位置,每当系统初始化HashMap时,会创建一个为**capacity的数组,这个数组里面可以存储元素的位置被成为桶(bucket), 每个bucket**都有其指定索引。可以根据该索引快速访问存储的元素。

public V put(K key, V value) {
    // 传入的key经过了 hash(key) 方法
    return putVal(hash(key), key, value, false, true);
}
// 特殊处理的hashCode
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

在Java中每个对象都会拥有一个**hashCode()**方法,这个就是散列函数,通过这个方法会返回一个32位的整数,使用这么大的值作为哈希值其实是为了尽量避免发生碰撞(相同),例如两个不同对象的hashCode一样的话那就是发生了碰撞。但是如果用这么长的数字来当做索引肯定是不行的,那需要数组有多大才行?所以我们需要把这个hashCode缩小到规定数组的长度范围内。

上面的代码只是用hashCode的高16位与低16位进行异或运算。hash()方法就是将hashCode进一步的混淆,增加其“随机度”,试图减少插入HashMap时的hash冲突。

在putVal方法中,有一行这样的代码

if ((p = tab[i = (n - 1) & hash]) == null)

i = (n - 1) & hash,n是数组长度,hash就是通过hash()方法进行高低位异或运算得出来的hash值。
这个表达式就是hash值的取模运算,上面已经说过当除数为2的次方时,可以用与运算提高性能。

4.3 HashMap.put(k,v)
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

// onlyIfAbsent:当存入键值对时,如果该key已存在,是否覆盖它的value。false为覆盖,true为不覆盖 参考putIfAbsent()方法。
// evict:用于子类LinkedHashMap。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    HashMap.Node<K,V>[] tab; // tab:内部数组
    HashMap.Node<K,V> p;   // p:hash对应的索引位中的首节点
    int n, i;  // n:内部数组的长度    i:hash对应的索引位
    
    // 首次put时,内部数组为空,扩充数组。
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 计算数组索引,获取该索引位置的首节点,如果为null,添加一个新的节点
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {   
        HashMap.Node<K,V> e; K k;
        // 如果首节点的key和要存入的key相同,那么直接覆盖value的值。
        if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        // 如果首节点是红黑树的,将键值对插添加到红黑树
        else if (p instanceof HashMap.TreeNode)
            e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        // 此时首节点为链表,如果链表中存在该键值对,直接覆盖value。
        // 如果不存在,则在末端插入键值对。然后判断链表是否大于等于7,尝试转换成红黑树。
        // 注意此处使用“尝试”,因为在treeifyBin方法中还会判断当前数组容量是否到达64,
        // 否则会放弃次此转换,优先扩充数组容量。
        else {
            // 走到这里,hash碰撞了。检查链表中是否包含key,或将键值对添加到链表末尾
            for (int binCount = 0; ; ++binCount) {
                // p.next == null,到达链表末尾,添加新节点,如果长度足够,转换成树结构。
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                // 检查链表中是否已经包含key
                if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }

        // 覆盖value的方法。
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount; // fail-fast机制
    
    // 如果元素个数大于阈值,扩充数组。
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

细心看注释部分,总结来说就是以下几个步骤:

1.检查数组是否为空,执行resize()扩充;
2.通过hash值计算数组索引,获取该索引位的首节点。
3.如果首节点为null**(没发生碰撞),直接添加节点到该索引位(bucket)。
4.如果首节点不为null
(发生碰撞),那么有3种情况
① key和首节点的key相同,覆盖old value
(保证key的唯一性)**;否则执行②或③
② 如果首节点是红黑树节点(TreeNode),将键值对添加到红黑树。
③ 如果首节点是链表,将键值对添加到链表。添加之后会判断链表长度是否到达TREEIFY_THRESHOLD - 1这个阈值,“尝试”将链表转换成红黑树。
5.最后判断当前元素个数是否大于threshold,扩充数组。

// 把链表转换为红黑色
final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    // 如果当前数组容量太小(小于64),放弃转换,扩充数组。
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize(); 
    } else if ((e = tab[index = (n - 1) & hash]) != null) {
        // 将链表转成红黑树...
    }
}

HashMap在jdk1.8之后引入了红黑树的概念,表示若桶中链表元素超过8时,会自动转化成红黑树;若桶中元素小于等于6时,树结构还原成链表形式。

红黑树的平均查找长度是log(n),长度为8,查找长度为log(8)=3,链表的平均查找长度为n/2,当长度为8时,平均查找长度为8/2=4,这才有转换成树的必要;链表长度如果是小于等于6,6/2=3,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短。

以6和8来作为平衡点是因为,中间有个差值7可以防止链表和树之间频繁的转换。假设,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。

概括起来就是:链表:如果元素小于8个,查询成本高,新增成本低,红黑树:如果元素大于8个,查询成本低,新增成本高。

4.4 resize() 数组扩容
final HashMap.Node<K,V>[] resize() {
    HashMap.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;
        }
        // 否则数组容量扩充一倍。(2的N次方)
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    // 如果数组还没创建,但是已经指定了threshold(这种情况是带参构造创建的对象),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);
    }
    // 可能是上面newThr = oldThr << 1时,最高位被移除了,变为0。
    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"})
    HashMap.Node<K,V>[] newTab = (HashMap.Node<K,V>[])new HashMap.Node[newCap];
    table = newTab;
    
    // 下面代码是将原来数组的元素转移到新数组中。问题在于,数组长度发生变化。 
    // 那么通过hash%数组长度计算的索引也将和原来的不同。
    // jdk 1.7中是通过重新计算每个元素的索引,重新存入新的数组,称为rehash操作。
    // 这也是hashMap无序性的原因之一。而现在jdk 1.8对此做了优化,非常的巧妙。
    if (oldTab != null) {
        
        // 遍历原数组
        for (int j = 0; j < oldCap; ++j) {
            // 取出首节点
            HashMap.Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                // 如果链表只有一个节点,那么直接重新计算索引存入新数组。
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                // 如果该节点是红黑树,执行split方法,和链表类似的处理。
                else if (e instanceof HashMap.TreeNode)
                    ((HashMap.TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                
                // 此时节点是链表
                else { // preserve order
                    // loHead,loTail为原链表的节点,索引不变。
                    HashMap.Node<K,V> loHead = null, loTail = null;
                    // hiHeadm, hiTail为新链表节点,原索引 + 原数组长度。
                    HashMap.Node<K,V> hiHead = null, hiTail = null;
                    HashMap.Node<K,V> next;
                    
                   // 遍历链表
                    do {
                        next = e.next;
                        // 新增bit为0的节点,存入原链表。
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        // 新增bit为1的节点,存入新链表。
                        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;
}

扩充数组不单单只是让数组长度翻倍,将原数组中的元素直接存入新数组中这么简单。

因为元素的索引是通过hash&(n - 1)得到的,那么数组的长度由n变为2n,重新计算的索引就可能和原来的不一样了。

在jdk1.7中,是通过遍历每一个元素,每一个节点,重新计算他们的索引值,存入新的数组中,称为rehash操作。

而java1.8对此进行了一些优化,没有了rehash操作。因为当数组长度是通过2的次方扩充的,那么会发现以下规律:

元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。因此,在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”。

先计算新数组的长度和新的阈值(threshold),然后将旧数组的内容迁移到新数组中,和1.7相比不需要执行rehash操作。因为以2次幂扩展的数组可以简单通过新增的bit判断索引位。

4.5 HashMap.get(k)
public V get(Object key) {
    Node<K,V> e;
    // 也会获取节点时也调用了hash()方法
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

final Node<K,V> getNode(int hash, Object key) {
    // tab:内部数组  first: 索引位首节点 n: 数组长度 k: 索引位首节点的key
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    // 数组不为null 数组长度大于0 索引位首节点不为null
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        // 如果索引位首节点的hash==key的hash 或者 key和索引位首节点的k相同
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            // 返回索引位首节点(值对象)
            return first;
        if ((e = first.next) != null) {
            // 如果是红黑色则到红黑树中查找
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                // 发送碰撞 key.equals(k)
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

总结起来就是以下步骤
1.检查数组是否为null 和 索引位首节点**(bucket的第一个节点)是否为null
2.如果索引节点的hash==key的hash 或者 key和索引节点的k相同则直接返回
(bucket的第一个节点)**
3.如果是红黑色则到红黑树查找
4.如果有冲突,则通过key.equals(k)查找
5.都没找到就返回null

5. 总结 (面试相关)
1、你用过HashMap吗?” “什么是HashMap?你为什么用到它?

呃…

2、我们能否让HashMap同步?

Map m = Collections.synchronizeMap(hashMap);

3、你知道HashMap的工作原理吗?

查看第序号3的总结。

4、你知道HashMap的put()方法和get()方法的工作原理吗??

put() 查看序号4.3的总结。
get() 查看序号4.5的总结。

5、当两个对象的hashcode相同会发生什么?

两个对象的hashCode相同所以它们的bucket位置相同,会发生hash碰撞。HashMap使用链表存储对象,这个Entry会存储在链表中,存储时会检查链表中是否包含key (key != null && key.equals(k),或将键值对添加到链表尾部。如果链表长度大于或等于8,链表转换红黑树 …

6、如果两个键的hashcode相同,你如何获取值对象?

两个对象的hashCode相同所以它们的bucket位置相同,找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点 (key != null && key.equals(k)。

7、怎么减少碰撞?

使用final修饰的对象、或不可变的对象作为键,使用(Integer、String)(是不可变、final的,而且已经重写了equals和hashCode方法)这样的wrapper类作为键是非常好的,(我们可以使用自定义的对象作为键吗?答:当然可以,只要它遵守了equals和hashCode方法定义规则,并且当对象插入到Map中之后将不会再改变。)

8、如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?

会调用**resize()**进行数组扩容。

9、你了解重新调整HashMap大小存在什么问题吗?

当多线程的情况下,可能产生条件竞争。

因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。这个时候,你可以质问面试官,为什么这么奇怪,要在多线程的环境下使用HashMap呢?:)

10、HashMap是非线程安全的,那么原因是什么呢?(HashMap的死锁)

由于HashMap的容量是有限的,如果HashMap中的数组的容量很小,假如只有2个,那么如果要放进10个keys的话,碰撞就会非常频繁,此时一个O(1)的查找算法,就变成了链表遍历,性能变成了O(n),这是Hash表的缺陷。

为了解决这个问题,HashMap设计了一个阈值,其值为容量的0.75,当HashMap所用容量超过了阈值后,就会自动扩充其容量。

在多线程的情况下,当重新调整HashMap大小的时候,就会存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历。如果条件竞争发生了,那么就会产生死循环了。  (又绕回了上一个问题 :)

11、影响HashMap性能的因素?
  • 负载因子。

  • 哈希值;理想情况是均匀的散列到各个桶。 一般HashMap使用String类型作为key,而String类重写了hashCode函数。

12、HashMap的key需要满足什么条件?

必须重写hashCode和equals方法

13、HashMap允许key/value为null, 但最多只有一个, 为什么?

如果key为null会放在第一个bucket(即下标0)位置, 而且是在链表最前面(即第一个位置)

【硬核】HashMap最全面试题(附答案) 文章目录hashmap基础hashmap的nodehashmap的容量hashmap的负载因子hashmap的hash()算法HashMap里面的hash()返回值hashmap的数组+链表/树问题hashmap为什么引入链表为什么jdk1.8会引入红黑树呢hashmap为什么一开始不就使用红黑树?HashMap的底层数组取值的时候,为什么不用`取模`,而是&数组的长度为什么是2的次幂如果指定数组的长度不为 2次幂,就破坏了数组的长度是2次幂的这个规则吗?hashmap里面的源码HashMap的pu 阅读详情

相关推荐

HashMap 底层原理面试题总结

Q0:HashMap是如何定位下标的?A:1、获取key的hashCode值 h,获取h的无符号位移16的结果2、1中的结果做异或运算3、2中重新计算hash值与数组长度n-1做与运算// 计算key的hash值 static final int hash(Object key) {int h;} // 计算数组下标 数组长度为2的倍数(n - 1) & hashQ1:HashMap由什么组成?

SS1773818087的博客 426

HashMap底层实现原理

通过对cap进行位移或操作计算,获取扩容阈值2的幂次方。此时这里的阈值threshold不是初始容量*负载因子,不必在意,这只是临时的,真正设置threshold在后面put方法中。HashMap 最早出现在 JDK 1.2中,底层基于散列算法实现,它是一个key-value结构的容器。JDK1.8 版本的HashMap,底层数据使用数组 + 链表/红黑树实现。Node的数据结构是一个链表结构,红黑树也是基于Node的数据结构构建得到。计算key.hashCode()并将哈希的高位数扩展到低位数。

天之道居同文。 1万+

银河麒麟高级服务器操作系统V10SP2(X86)PXE无人值守批量部署实战指南

本文详细介绍了银河麒麟高级服务器操作系统V10SP2(X86)PXE无人值守批量部署的实战指南。通过PXE网络引导技术,实现全自动化、高效率的系统部署,特别适用于企业级数据中心和国产化替代项目。文章涵盖基础环境准备、关键服务配置、Kickstart高级定制及部署测试等核心内容,帮助用户快速掌握批量部署技巧。

weixin_42525637的博客 384

HashMap的底层实现原理

HashMap底层实现原理

qq_45621240的博客 7989

将3DMax编辑的动画,导入给已经绑定蒙皮好的模型给Unity使用

现在程序猿想做独立游戏,即容易,也难。 说容易,引擎越来越傻瓜化。 说难,到哪去找靠谱的美术? 好在伟大的AssetStore(还有淘宝上)有大批不赖的资源可以用。 现成的模型,是好,但是,没有配套的动作,却是一件让人头疼的事情,即使用IK动画,也不能完美的弥补这一缺陷。 虽然Unity具备 retargeting 功能,但是,也不是万能的,很多情况下,非标准的骨骼会导致

正经的时候都在打游戏 1万+

【架构设计】DDD 到底解决了什么问题

DDD作为架构设计思想帮助微服务控制规模复杂度,那它是怎么做到的呢?第一章提出了架构的根本目是解决系统复杂度,并简要说明系统复杂度的六个来源及通用解法,为我们设计架构提供了清晰可执行的操作思路。

DreamSun的博客 1162

hashmap实现原理_一文搞定HashMap的实现原理和面试

原文 https://juejin.im/post/5d09f2d56fb9a07ec7551fb0HashMap在日常开发中基本是天天见的,而且都知道什么时候需要用HashMap,根据Key存取Value,但是存和取的时候那些操作却是很少去研究。同时在面试中也是面试官们必问的。 以下是基于JDK1.8正文先看看HashMap的结构图:1. 先来认识一下HashMap中定义的一些需要了解的成员变量...

weixin_39954569的博客 217

数据中心——Vxlan基本概念1

东西流量指的就是横向流量-------例如:服务器与服务器之间交换流量南北流量指的就是纵向流量-------例如:客户端和服务器之间的流量以下主要是讲解业务平面的网络架构(一般业务平面就需要用到专用的数据中心交换机)

m0_49864110的博客 1万+

floatmap 二维数组_一文搞定HashMap的实现原理和面试

原标题:一文搞定HashMap的实现原理和面试 原文 https://juejin.im/post/5d09f2d56fb9a07ec7551fb0HashMap在日常开发中基本是天天见的,而且都知道什么时候需要用HashMap,根据Key存取Value,但是存和取的时候那些操作却是很少去研究。同时在面试中也是面试官们必问的。 以下是基于JDK1.8正文先看看HashMap的结构图: 1. 先来认...

weixin_42261068的博客 191

在linux上本地大模型部署与推理

您的Python脚本(客户端)可能在您的个人电脑上,而模型服务(服务器)在远程Linux服务器上。如果您的服务器有强大的NVIDIA GPU,并且您对性能有更高的要求(例如需要高吞吐量),vLLM是更好的选择。这种方式无需向公网暴露服务器端口,而是通过加密的SSH通道将服务器的端口“映射”到您的本地电脑上。我们将启动一个API服务器,它会加载您指定的模型,并等待您的代码前来调用。如果您直接在SSH窗口运行上面的命令,一旦您关闭窗口,服务就会停止。这是根据您的原始代码修改的版本,适配本地vLLM服务。

frostmelody 全网同名,大家多多关注呀~ 持续分享优质内容! 2058

机器学习kaggle报告-Facial Keypoints Detection【完整解题过程+代码】

在处理面部关键点检测任务时,首先需要将存储在 CSV 文件中的图像数据转换为可以显示和分析的图像。数据集中每个图像是以一个一维数组的形式存储,其中包含了图像的所有像素值。为了将这些数据转化为实际的图像,我们需要按照一定的规则将这些像素值重新排列为一个二维矩阵(即图像的行列)。下面是一个案例,展示了如何将数据集中的图像数据转化为图片。转化过程读取CSV文件:首先,我们需要读取包含图像数据的 CSV 文件,通常该文件包含多张图像的像素值。每一行表示一张图像,并且包含图像的像素值列表。提取图像数据。

一键难忘的博客 3356

实现树形每次展开动态加载_一文搞定HashMap的实现原理和面试

原文 https://juejin.im/post/5d09f2d56fb9a07ec7551fb0HashMap在日常开发中基本是天天见的,而且都知道什么时候需要用HashMap,根据Key存取Value,但是存和取的时候那些操作却是很少去研究。同时在面试中也是面试官们必问的。 以下是基于JDK1.8正文先看看HashMap的结构图:1. 先来认识一下HashMap中定义的一些需要了解的成员变量...

weixin_26775679的博客 125

AutoCAD中圆弧折线化方法与实现

在AutoCAD中,圆弧折线化是将圆弧曲线转换为由一系列线段组成的折线曲线的过程。本文将介绍如何使用LISP编写代码来实现圆弧折线化,并提供相应的源代码。上述示例代码将生成一个以坐标原点为圆心、起点为(5.0, 0.0, 0.0)、终点为(0.0, 5.0, 0.0)、半径为5.0、角度为90度的圆弧的折线表示。通过使用上述的LISP代码,你可以在AutoCAD中实现将圆弧转换为折线的功能。你可以根据需要修改代码中的参数来适应不同的圆弧转换需求。函数,并传入圆弧的起点、终点、圆心、半径和角度参数。

VcuOcaml的博客 1419

算法和数据结构:面试高频:一文搞定HashMap的实现原理

1. 为什么需要理解HashMap? 2. 什么是哈希表? 2.1 哈希表性能 2.2 哈希表的数据结构 2.3 哈希冲突 3. hashmap的实现原理 3.1 hashmap数据结构 3.2 hashmap特点 3.3 Hashmap扩容 4. 什么是红黑树? 5. HashMap&Hashtable的区别? 1. 为什么需要理解HashMap的实现原理? HashMap是Java语言中使用频率很高的一种数据结构。 HashMap是一种经典&集大成的数据结构,同

caobaokang的专栏 678

【1】Python交叉编译到OpenHarmony标准系统运行(arm32位)

本文介绍如何Python语言如何在OpenHarmony标准系统运行,包括5.0r和4.1r以及4.0r,和未来版本的OpenHarmony版本上。 Python语言在OpenHarmony上使用,需要将Python解释器CPython移植到OpenHarmony标准系统。通过交叉编译的方式。

weixin_58069108的博客 930

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

害怕面试被问HashMap?这一篇就搞定了!

声明:本文以jdk1.8为主! 搞定HashMap 作为一个Java从业者,面试的时候肯定会被问到过HashMap,因为对于HashMap来说,可以说是Java集合中的精髓了,如果你觉得自己对它掌握的还不够好,我想今天这篇文章会非常适合你,至少,看了今天这篇文章,以后不怕面试被问HashMap了 其实在我学习HashMap的过程中,我个人觉得HashMap还是挺复杂的,如果真的想把它搞得明明白...

庆哥Java的优快云技术博客 6万+

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

面试被问到 HashMap 有这一文就够了!

HashMap 是难点也是重点,更是面试中的常客,充分了解 HashMap 绝对有助于提升编程的内功心法。本文重点是对 JDK1.7 和 JDK1.8 中其实现方式的变化进行分析学习。 引 不同集合底层数据结构? 集合实现 Object数组 List ArrayList Object数组 Vector Object数组 LinkedList 双向链表(JDK1.6及之前为循环链表) Set HashSet(无序、唯一) 基于 HashMap 实现 LinkedHash

qq_42758551的博客 6236

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

《面试必问-HashMap》通俗易懂搞定HashMap底层原理

HashMap的底层原理是面试必考题,为什么面试官如此青睐这道题?

m0_55611144的博客 2335

为什么面试老问HashMap,9道HashMap面试题帮你搞定90%

前言: 为什么面试问hashmap? HashMap的工作原理是目前java面试问的较为常见的问题之一,这里面主要会包含是否用过Hashmap,hashMap的hash碰撞的机制是什么,hashMap是如何扩容的,hashMap的底层数据结构是什么,jdk1.8中对hash算法和寻址算法是如何优化的等问题 今天给大家整理了九道面试题,我认为这些问题面试被问到的几率已经超过我今年能找到女朋友了! 1. HashMap的原理,内部数据结构? 底层使用哈希表(数组 + 链表),当链表过长会将链表转成 红黑

豆汁 4078
上一篇: 一篇文章帮你彻底搞清楚“I/O多路复用”和“异步I/O”的前世今生
下一篇: 如何获取 Kafka 的消费者详情? —— 从 Scala 到 Java 的切换
Java技术那些事儿
博客等级 码龄5年 2306粉丝 2584原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值