之前去大华面试,问道了hashMap底层的一些东西,对于我这种毕业一年不到的人来说,就输的很惨,所以一些底层的,基础的还是要加强呀!
说一下这个HashMap,有些人可能只会知道Map接口的实现类,比如hashMap,hashTable,treeMap的差别。这里简单的讲一下,hashTable、hashMap的实现原理都是一样的,都是基于哈希表实现的,区别就是hashTable是线程同步,hashMap不是。而treeMap是基于红黑树的数据结构实现的,实现key排序。
然后回到主题,hashMap是数组,链表来实现对数据的存储,但这两者基本上是两个极端,就是这两个都是各自为了实现某种特定的功能实现的。
下面简单的讲一下这两种数据结构的特点与缺点
数组
数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难;
链表
链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:寻址困难,插入和删除容易。
而我们的哈希表就是综合两种数据结构的
hasMap的构造函数
public HashMap(int initialCapacity, float loadFactor) {
//初始容量不能<0
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: "
+ initialCapacity);
//初始容量不能 > 最大容量值,HashMap的最大容量值为2^30
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//负载因子不能 < 0
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: "
+ loadFactor);
// 计算出大于 initialCapacity 的最小的 2 的 n 次方值。
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
//设置HashMap的容量极限,当HashMap的容量达到该极限时就会进行扩容操作
threshold = (int) (capacity * loadFactor);
//初始化table数组
table = new Entry[capacity];
init();
}
从源码中可以看出,每次新建一个HashMap时,都会初始化一个table数组。table数组的元素为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里面用到链式数据结构的一个概念。总体就是即想在寻找时快,又想在插入,删除的时候容易,怎么实现呢?上面我们提到过Entry类里面有一个next属性,作用是指向下一个Entry。打个比方, 第一个
键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?
HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样我们发现index=0的地方其实存取了A,B,C三
个键值对,他们通过next这个属性链接在一起。所以疑问不用担心。也就是说数组中存储的是最后插入的元素。到这里为止,HashMap的大致实现,
我们应该已经清楚了。相当于用key的hash值使用数组的形式排列,方便寻找,数组的每个元素又是一个链表,方便进行插入,删除。
接下来附上一个网址,详细解释hashMap在初始化的时候,各个参数的含义
http://blog.youkuaiyun.com/fan2012huan/article/details/51087722