JDK1.8 Map之HashMap

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

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

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

避免需要这样实例:建议使用ConcurrentHashMap

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

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

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

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

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

Q5为什么字符串,Interger这样的包装类适合作为键?自定义对象可以作为键吗?

字符串是不可变的,也是最后的,而且已经重写了equals()方法和hashCode()方法方法。

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

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

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

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

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

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

Q8 HashMap&TreeMap&LinkedHashMap使用场景? 

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

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

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

1地图是一个接口。

//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获取当前数组的所有条目。

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

 


 
阅读更多
换一批

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