面试题:你真的了解HashMap吗?

HashMap可以说是Java中最常用的集合类框架之一,是Java语言中非常典型的数据结构,因此在面试中经常会被问到hashmap的问题。

1)hashmap的底层原理?

hashmap是通过数组+链表实现的。

调用put方法存储数据的时候,通过hashCode方法处理key,计算出Entry在数组中存放的位置(bucket,桶)。

index = (length - 1) &  HashCode(Key) (取模效率比位运算低,所以并没有使用取模这种方式计算)

HashMap数组的每一个元素不止是一个Entry对象,也是一个链表的节点,每个Entry对象通过Next指针指向它的下一个Entry节点。当多个Entry被定位到一个数组的时候(碰撞),只需要插入到对应的链表即可。

调用get方法获取数据的时候,同样是通过hashCode方法处理key,计算出Entry在数组中存放的位置。然后通过equals()方法来寻找键值对。

2)hashmap的默认长度?为什么这么设置?

hashmap的默认长度是16。

之所为设置成16,是为了降低hash碰撞(两个元素虽然不相同,但是hash函数的值相同)的几率。

index = (length - 1) &  HashCode(Key

如果长度是16或者其他2的幂,length - 1的值是所有二进制位全为1(1111),index的结果等同于hashcode后几位的值只要输入的hashcode本身分布均匀,hash算法的结果就是均匀的。如果是非2的幂,可能会导致分配不均匀,甚至有的bucket永远分配不到。

扫描二维码关注公众号,回复: 10573444 查看本文章

所以HashMap给初始值、扩容的时候,容器大小都是2的幂次方。

3)高并发下,为什么hashmap会出现死锁?如何避免这种问题?

如果两个线程同时put,发现HashMap需要重新调整大小,这时候会产生条件竞争。(java8版本以下才有该问题,头部插入导致

调整大小的条件:HashMap.Size   >=  index * LoadFactor(负载因子,默认值为0.75f,空间利用率和减少查询成本的折中,0.75的话碰撞最小

需要遍历原Entry数组,然后把所有的Entry重新Hash到新数组(长度为原来的两倍)。因为length变化了,根据index = (length - 1) &  HashCode(Key),index必然也会发生改变。

在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部。转移前链表顺序是1->2,那么转移后就会变成2->1。

如果多个线程同时操作的时候,链表容易形成环形链表1->2、2->1。这种时候如果get方法获取链表的话,就会陷入死循环。

高并发下可以使用CocurrentHashMap替代hashmap,CocurrentHashMap线程安全同时效率高。

4)java8中对hashmap做了什么优化?

1)HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树。提高了查询效率,红黑树查询时间是O(logn),链表是O(n)。

2)发生hash碰撞时,java 1.7 会在链表的头部插入,而java 1.8会在链表的尾部插入。头部插入效率高,不需要遍历尾部,但是容易产生环形链表,引入红黑树后没法头部插入,但是红黑树减少了插入的成本。

5)手写个hashmap

public class Node<K, V> {
    private K key;
    private V value;
    private Node<K, V> next;

    public Node(K key, V value, Node<K, V> next) {
        this.key = key;
        this.value = value;
        this.next = next;
    }

    public K getKey() {
        return key;
    }

    public void setKey(K key) {
        this.key = key;
    }

    public V getValue() {
        return value;
    }

    public void setValue(V value) {
        this.value = value;
    }

    public Node<K, V> getNext() {
        return next;
    }

    public void setNext(Node<K, V> next) {
        this.next = next;
    }
}
public class MyHashMap<K, V> {

    Node<K, V>[] table = null;

    // 默认初始大小
    static final int DEFAULT_INITIAL_CAPACITY = 16;

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

    // 实际大小
    static int size;

    public void put(K k, V v) {
        if (table == null) {
            table = new Node[DEFAULT_INITIAL_CAPACITY];
        }

        int index = k.hashCode() & (DEFAULT_INITIAL_CAPACITY - 1);

        Node<K, V> node = table[index];

        if (node == null) {
            table[index] = new Node<>(k, v, null);
            size++;
        } else {
            Node<K, V> newNode = node;

            while (newNode != null) {
                if (k.equals(newNode.getKey()) || k == newNode.getKey()) {
                    newNode.setValue(v);
                    return;
                }

                newNode = node.getNext();
            }
            table[index] = new Node<K, V>(k, v, table[index]);

            size++;
        }

    }

    public V get(K k) {
        int index = k.hashCode() & (DEFAULT_INITIAL_CAPACITY - 1);

        Node<K, V> node = table[index];

        if (k.equals(node.getKey()) || k == node.getKey()) {
            return node.getValue();
        } else {
            Node<K, V> nextNode = node.getNext();

            while (nextNode != null) {
                if (k.equals(nextNode.getKey()) || k == nextNode.getKey()) {
                    return nextNode.getValue();
                }
            }

        }

        return null;
    }

}

发布了43 篇原创文章 · 获赞 0 · 访问量 3905

猜你喜欢

转载自blog.csdn.net/zhangdx001/article/details/105092130