JDK1.8 Map之HashMap

Q1 HashMap是通过什么方式实现的?-->数组Entry的链表

Q2 HashMap为什么不安全?怎样实现安全?

多线程情况下如果两个线程同时put的hash(key)相同,则会发生碰撞,出现覆盖的情况。

避免需要这样实例:Map m = Collections.synchronizeMap(hashMap);

Q3 两个键的hashcode相同,你如何获取值对象?如何put对象?

3.1 当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,找到bucket位置之后,会调用keys.equals()方法去找到LinkList链表中正确的节点,最终找到要找的值对象。

3.2 put时候先查找key的hash值,如果hash相同,那么则找到该对象的链表,放在链头的位置。

Q4调整HashMap大小存在什么问题吗?
当多线程的情况下,可能产生条件竞争。

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

Q5为什么String, Interger这样的wrapper类适合作为键?自定义对象可以作为键吗?

String是不可变的,也是final的,而且已经重写了equals()和hashCode()方法。

自定义对象可以作为键,只要是插入之后是不可变的,并重写了equals和hashCode()方法。

Q6 HashMap复杂度-->理想O(N/Buckets),N就是以数组中没有发生碰撞的元素

要保证o(N),需要尽量避免出现重复的hashCode(key),因为出现重复的回在get/put的时候需要进行二次equals比较计算出在链表的位置;以及链表的长度都会影响时间复杂度

如果某个桶中的链表记录过大的话(当前是TREEIFY_THRESHOLD = 8),就会把这个链动态变成红黑二叉树(TreeMap),使查询最差复杂度由O(N)变成了O(logN)。

Q7 重新调整HashMap大小存在什么问题吗?

可能产生条件竞争,多线程的情况下会有多个线程同时修改map的大小,导致出现死循环。

Q8 HashMap & TreeMap & LinkedHashMap 使用场景? 

一般情况下,使用最多的是 HashMap。 
HashMap:在 Map 中插入、删除和定位元素时; 
TreeMap:在需要按自然顺序或自定义顺序遍历键的情况下; 

LinkedHashMap:在需要输出的顺序和输入的顺序相同的情况下。

Q9 HashMap 和 HashTable 有什么区别? 
①、HashMap 是线程不安全的,HashTable 是线程安全的; 
②、由于线程安全,所以 HashTable 的效率比不上 HashMap; 
③、HashMap最多只允许一条记录的键为null,允许多条记录的值为null,而 HashTable 不允许; 
④、HashMap 默认初始化数组的大小为16,HashTable 为 11,前者扩容时,扩大两倍,后者扩大两倍+1; 
⑤、HashMap 需要重新计算 hash 值,而 HashTable 直接使用对象的 hashCode

1 Map是一个接口。

//map是一个接口
public interface Map<K,V> {}
//定义抽象的类实现Map接口
public abstract class AbstractMap<K,V> implements Map<K,V>{}
//AbstractHashedMap继承抽象map
public class AbstractHashedMap extends AbstractMap implements IterableMap{}

2 可以通过entrySet获取当前数组的所有entry。

public abstract Set<Entry<K,V>> entrySet();

3 HashMap定义

3.1 初始容量:默认map容量,默认为16

3.2 加载因子:为设定临界值的维度,默认为0.75。

3.3 容量临界值:在put后会如果当前的大小大于旧的数组大小,需要重新计划临界值。

3.4 最大容量:1.8jdk规定最大为2的30次方。

    protected static final int DEFAULT_CAPACITY = 16;//初始容量
    protected static final int DEFAULT_THRESHOLD = 12;//容量临界值=初始容量*默认加载因子
    protected static final float DEFAULT_LOAD_FACTOR = 0.75F;//默认加载因子0.75
    protected static final int MAXIMUM_CAPACITY = 1073741824;//最大容量为2的30次方
    protected static final Object NULL = new Object();
    protected transient float loadFactor;
    protected transient int size;//已经存在的个数
    protected transient AbstractHashedMap.HashEntry[] data;//数组
    protected transient int threshold;
    protected transient int modCount;//已从结构上修改 此列表的次数

4 HashMap的构造函数

JDK源码中有3个构造函数,最后都调用AbstractHashedMap(int initialCapacity, float loadFactor)方法。

/**
* initialCapacity : 初始化容量,默认16
* loadFactor : 初始化加载因子,默认0.75
**/
protected AbstractHashedMap(int initialCapacity, float loadFactor) {
        if(initialCapacity < 1) {
            throw new IllegalArgumentException("Initial capacity must be greater than 0");
        } else if(loadFactor > 0.0F && !Float.isNaN(loadFactor)) {
            this.loadFactor = loadFactor;
            // 判断初始化容量是否大于hashMap定义大小(2的30次方),如果大于设置初始大小为2的30次方,如果小于,设置大小为2(1<<=1)           
            initialCapacity = this.calculateNewCapacity(initialCapacity);
            //初始化临界值:(int)((float)initialCapacity* loadFactor)
            this.threshold = this.calculateThreshold(initialCapacity, loadFactor);
            //初始化数组大小,通过initialCapacity
            this.data = new AbstractHashedMap.HashEntry[initialCapacity];
            this.init();
        } else {
            throw new IllegalArgumentException("Load factor must be greater than 0");
        }
    }

面试问题:hashMap默认大小是多大?

hashMap默认大小为16

/**
* 如果传递参数的map的大小大于16,那么会创建size*2的容量。
**/
protected AbstractHashedMap(Map map) {
      this(Math.max(2 * map.size(), 16), 0.75F);
      this.putAll(map);
}

面试问题:为什么每次创建都是为2的幂次?

判断初始化容量是否大于hashMap定义大小(2的30次方),如果大于设置初始大小为2的30次方,如果小于,设置大小为2(1<<=1) ;通过左移位赋值运算符进行计算<<1

protected int calculateNewCapacity(int proposedCapacity) {
        int newCapacity = 1;
        if(proposedCapacity > 1073741824) {
            newCapacity = 1073741824;
        } else {
            while(true) {
                if(newCapacity >= proposedCapacity) {
                    if(newCapacity > 1073741824) {
                        newCapacity = 1073741824;
                    }
                    break;
                }
                newCapacity <<= 1;//左移位赋值运算符
            }
        }
        return newCapacity;
}
5 put(Object key, Object value)方法
/**
* 逻辑步骤
* 1 判断key是否为空,支持NULL关键字
* 2 取key的hashCode值
* 3 通过key的hashCode值与当前map的长度进行&与运算,得到数组下标
(&运算:将两端转化为二进制,进行对应位比较,如果相等返回1,不想等返回0)
* 4 循环当前map是否存在key,如果存在比较==/equals是否相等,
* 4.1 如果相等更新key的值,返回旧的值
* 5 不存在则创建HashEntry对象,
* 6 将HashEntry存放到数组中指定下标位置
**/
public Object put(Object key, Object value) { 
    // 判断key是否为空,如果为空,返回NULL 
    key = this.convertKey(key);      
    //得到key的hash()值 
    int hashCode = this.hash(key); 
    //得到数组下标:hashCode & this.data.length- 1; 
    int index = this.hashIndex(hashCode, this.data.length); 
    // 循环当前数组,判断是否选在当前key 
    for(AbstractHashedMap.HashEntry entry = this.data[index]; entry != null; entry = entry.next) { 
        // 如果存在key进行比较,如果完全对象相等,值相等则替换key值,返回旧的值             
        if(entry.hashCode == hashCode && this.isEqualKey(key, entry.key)) {                 
            Object oldValue = entry.getValue();                 
            this.updateEntry(entry, value);                 
            return oldValue;             
        }         
    }         
    this.addMapping(index, hashCode, key, value);        
    return null; 
}
protected void addMapping(int hashIndex, int hashCode, Object key, Object value) {
   ++this.modCount;
   //创建HashEntry对象,
   AbstractHashedMap.HashEntry entry = this.createEntry(this.data[hashIndex], hashCode, key, value);
   this.addEntry(entry, hashIndex);
   //将enrty存放到指定数组下标位置
   -->this.data[hashIndex] = entry;
   //设置房前的map大小+1
   ++this.size;
   //检查map容量
   this.checkCapacity();
}

   
/**
* 检查容量
* 逻辑步骤:
* 1 判断当前map大小大于等于容量界定值(默认12) && map大小*2后小于最大容量(2的32次方)
* 2 重新进行容量的初始化
* 3 实例化新的数组,并将old数组集合循环赋值给新的数组集合
* 4 根据新的容量*加载因子计算临界值
**/
protected void checkCapacity() {
    if(this.size >= this.threshold) {
        int newCapacity = this.data.length * 2;
        if(newCapacity <= 1073741824) {
            this.ensureCapacity(newCapacity);
        }
    }
}

protected void ensureCapacity(int newCapacity) {
    int oldCapacity = this.data.length;
    if(newCapacity > oldCapacity) {
        if(this.size == 0) {
            this.threshold = this.calculateThreshold(newCapacity, this.loadFactor);
            this.data = new AbstractHashedMap.HashEntry[newCapacity];
        } else {
            AbstractHashedMap.HashEntry[] oldEntries = this.data;
            AbstractHashedMap.HashEntry[] newEntries = new AbstractHashedMap.HashEntry[newCapacity];
            ++this.modCount;

            for(int i = oldCapacity - 1; i >= 0; --i) {
                AbstractHashedMap.HashEntry entry = oldEntries[i];
                if(entry != null) {
                    oldEntries[i] = null;
                    AbstractHashedMap.HashEntry next;
                    do {
                        next = entry.next;
                        int index = this.hashIndex(entry.hashCode, newCapacity);
                        entry.next = newEntries[index];
                        newEntries[index] = entry;
                        entry = next;
                    } while(next != null);
                }
            }
            //计算新的加载因子并复制
            this.threshold = this.calculateThreshold(newCapacity, this.loadFactor);
            //-->(int)((float)newCapacity * loadFactor);
            this.data = newEntries;
        }

    }
}

6 get(String key)

/*** 1 检查key是否为空
* 2 获取key的HashCode值
* 3 循环遍历得到HashEntry,返回entry的值
**/
public Object get(Object key) {
    key = this.convertKey(key);    
    int hashCode = this.hash(key);     
    for(AbstractHashedMap.HashEntry entry = this.data[this.hashIndex(hashCode, this.data.length)]; entry != null; entry = entry.next) { 
        if(entry.hashCode == hashCode && this.isEqualKey(key, entry.key)) {              
            return entry.getValue();         
        }     
    }     
    return null; 
}


            
阅读更多
个人分类: JDK1.8
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

不良信息举报

JDK1.8 Map之HashMap

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭