1、通过查看源码,可以看到HashSet的无参构造函数:
public HashSet() {
map = new HashMap<E, Object>();
}
其中map: private transient HashMap<E, Object> map;
由此我们可以推出:HashSet底层使用HashMap来实现的。
但是我们知道Map是由key和value组成的键值对,而Set只有元素,他们是怎么联系起来的呢?
2、接下来我们看HashSet是怎么添加元素的
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
其中PRESENT:private static final Object PRESENT = new Object();
由于HashSet我们只需要key,不需要value,但是它又依靠Map来实现,我们就需要建立一个假的对象PRESENT(所有的value都是该对象)。
3、通过查看源码发现HashSet的add、remove、iterator、clear、isEmpty等方法都是调用map的方法来实现的。接下来我们就重点分析HashMap的底层实现
4、首先查看HashMap的构造函数:
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;//负载因子,float类型,底层所维护的哈希表的负载因子;默认的负载因子0.75f赋值给它
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];//table是一个长度为16的Entry类型的数组,由此可见HasnMap底层使用数组实现,但此时的数组不是简单的数组,是一个Entry类型的数组
init();//包级别的,外部无法访问
}
5、Entry是什么呢?
Entry是一个内部类,其实现了Map.Entry
static class Entry<K,V> implements Map.Entry<K,V>
{
final K key;
V value;
Entry<K,V> next;
int hash;
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
}
6、HashMap的put方法
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);//其中hash是一个散列函数,他根据key的hashCode的来计算
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
总结:
1.HashMap底层维护一个数组,我们向HashMap中所放置的对象实际上是存储在该数组中
2.当向HashMap中put一对键值时,他会根据key的hashCode值计算出一个位置,该位置就是此对象准备往数组中存放的位置
3.如果该位置没有对象存在,就将此对象直接放入数组当中;如果该位置已经有对象存在了,则顺着此存在着的对象的链开始寻找(Entry类有一个Entry类型的next成员变量,指向了该对象的下一个对象),如果此链上有对象的话,再去使用equals方法进行比较,如果对此链上的某个对象的equals方法比较为false,则将该对象存放到数组当中,然后将数组中该位置上以前存在的那个对象链接到此对象的后面
HashMap的内存布局示意图: