HashSet、HashMap,散列表数据结构(哈希表) (一)

2014-11-24 10:46:26 · 作者: · 浏览: 0

HashSet、HashMap,散列表数据结构(哈希表)


前几天介绍了ArrayList的底层实现以及对性能的简单分析。今天再来看看HashSet,HashMap。

HashSet:

很多开发者,初学者都知道HashSet无序,不可重复,线程非同步。底层是哈希表结构。
但它是怎么做到的?什么是散列表数据结构(哈希表)?有什么特性?都清楚吗?不清楚继续往下看。

它是这样做到的:

先来看HashSet的源码,首先看默认构造器:
[java]
public HashSet() {
map = new HashMap();
}
// ok,我们看到构造器中new了一个HashMap。key使用了泛型,value使用Object。

public HashSet() {
map = new HashMap();
}
// ok,我们看到构造器中new了一个HashMap。key使用了泛型,value使用Object。
再来看add方法源码:
[java]
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
// PRESENT是一个Object类型的常量,用来当做map的value. 也就是说,你以后在HashSet中存储的元素都是HashMap中key,value全部使用Object。

private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
// PRESENT是一个Object类型的常量,用来当做map的value. 也就是说,你以后在HashSet中存储的元素都是HashMap中key,value全部使用Object。
HashMap的key是不可以重复的,保证元素唯一的依据是对象的hashCode跟equals方法。
而HashSet不就是用HashMap的key来存储元素嘛,也就保证了元素的唯一性。包括迭代器也是HashMap中keySet方法取得的iterator。
[java]
public Iterator iterator() {
return map.keySet().iterator();
}

public Iterator iterator() {
return map.keySet().iterator();
}
通过上面的介绍,已经对HashSet比较了解了,我们知道HashSet底层是用了HashMap。
要想知道怎么做到存取速度快的,我们直接看HashMap就好了。

散列表数据结构(哈希表)

散列表(Hash table,也叫哈希表),是根据关键字(Key value)而直接进行访问的数据结构。
也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

HashMap底层就是散列表数据结构,即数组和链表的结合体,
底层是一个数组结构,数组中的每一项又是一个链表。这样做有什么好处呢?
数组能够提供对元素的快速访问但不易于扩展(如果不知道元素脚标,还得进行遍历查找),链表易于扩展但不能对其元素进行快速访问。
怎样做到两全其美,就是散列表数据结构。

我们来看看HashMap中元素存跟取的实现方式:

首先明白,HashMap根据key的hashCode计算出元素在Entry数组中的位置,然后再Entry内部链表中存放key,value。

先看构造方法源码:
[java]
static final int DEFAULT_INITIAL_CAPACITY = 16; // 默认初始化容量
static final float DEFAULT_LOAD_FACTOR = 0.75f;
final float loadFactor; // 用于计算扩容阀值
/* The next size value at which to resize (capacity * load factor) */
int threshold; // Entry扩容阀值
// The table, resized as necessary. Length MUST Always be a power of two.
transient Entry[] table;// 存放键值对的Entry数组
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); // 计算扩容阀值
table = new Entry[DEFAULT_INITIAL_CAPACITY]; // 初始化Entry数组
init();
}
/* 在默认构造方法中,初始化了一个容量为16的HashMap(Entry数组),当元素超过75%(16*0.75f=12个)的时候开始自动扩容*/

static final int DEFAULT_INITIAL_CAPACITY = 16; // 默认初始化容量
static final float DEFAULT_LOAD_FACTOR = 0.75f;
final float loadFactor; // 用于计算扩容阀值
/* The next size value at which to resize (capacity * load factor) */
int threshold; // Entry扩容阀值
// The table, resized as necessary. Length MUST Always be a power of two.
transient Entry[] table;// 存放键值对的Entry数组
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); // 计算扩容阀值
table = new Entry[DEFAULT_INITIAL_CAPACITY]; // 初始化Entry数组
init();
}
/* 在默认构造方法中,初始化了一个容量为16的HashMap(Entry数组),当元素超过75%(16*0.75f=12个)的时候开始自动扩容*/
put方法源码: eg: map.put("a","abc");
[java]
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode()); // 获取key的hash值
int i = indexFor(hash, table.length); // h & (length-1); 通过hashcode取模数组长度, 定位hash值在table数组中的索引
// 如果table数组中i索引所在位置有元素,循环遍历该链表中的下一个元素
for (Entry e = table[i]; e != nul