您的位置:首页 > 理论基础 > 数据结构算法

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

2016-06-22 20:49 381 查看
很多开发者,初学者都知道HashSet无序,不可重复,线程非同步。底层是哈希表结构。

但它是怎么做到的?什么是散列表数据结构(哈希表)?有什么特性?都清楚吗?不清楚继续往下看。

它是这样做到的:

先来看HashSet的源码,首先看默认构造器:

[java] 

public HashSet() { 

    map = new HashMap<E,Object>(); 



// ok,我们看到构造器中new了一个HashMap。key使用了泛型,value使用Object。 

public HashSet() {

 map = new HashMap<E,Object>();

}

// 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<E> iterator() { 

    return map.keySet().iterator(); 



public Iterator<E> iterator() {

 return map.keySet().iterator();

}

通过上面的介绍,已经对HashSet比较了解了,我们知道HashSet底层是用了HashMap。

要想知道怎么做到存取速度快的,我们直接看HashMap就好了。

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

散列表(Hash table,也叫哈希表),是根据关键字(Key value)而直接进行访问的数据结构。

也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

HashMap底层就是散列表数据结构,即数组和链表的结合体,

底层是一个数组结构,数组中的每一项又是一个链表。这样做有什么好处呢?

数组能够提供对元素的快速访问但不易于扩展(如果不知道元素脚标,还得进行遍历查找),链表易于扩展但不能对其元素进行快速访问。

怎样做到两全其美,就是散列表数据结构。

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

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

 

我们来对HashMap存取元素的过程来做一个小的总结:

元素(key,value)在HashMap中被封装进Entry数组。

put元素的时候,根据key的hash值定位元素在Entry数组中的索引,如果当前索引有元素,就通过equals方法进行比较,将元素存进当前Entry的链表中适当的位置。

get元素的时候,根据key的hash值定位元素在Entry数组中的索引,然后通过equals方法定位元素在链表中的位置,取出该元素。

性能分析:

因为元素的存取是通过hash算法进行的,所以速度都没的说。

在查找操作中,唯一影响性能的是在链表中,但实际只要优化好了key对象hashCode跟equals方法,就会避免链表中的数据过多而导致查找性能变慢。

再一个非常影响性能的是数组扩容操作,当使用默认的DEFAULT_INITIAL_CAPACITY对HashMap进行初始化的时候,

如果元素个数非常多,会导致扩容次数增加,每次扩容都会进行元素位置的重新分配,这是相当耗费性能的。

如果能预算好元素个数,就应该避免使用默认的DEFAULT_INITIAL_CAPACITY,可在HashMap的构造函数中为其指定一个初始值。

问题解决:

hashCode必须和equals保持兼容(equals方法的判断依据和计算hashCode的依据相同),这样做是为了避免链表中的数据过多。

 

参考链接:http://www.2cto.com/kf/201306/218390.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐