热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java集合框架源码解析HashSet及LinkedHashSet

Java容器类的用途是“保存对象”,分为两类:Map——存储“键值对”组成的对象;Collection——存储独立元素。Collectio

 Java容器类的用途是“保存对象”,分为两类:Map——存储“键值对”组成的对象;Collection——存储独立元素。Collection又可以分为List和Set两大块。List保持元素的顺序,而Set不能有重复的元素。

     本文分析Set中最常用的HashSet类,并简单介绍和对比LinkedHashSet。

     首先对Set接口进行简要的说明。 

     存入Set的每个元素必须是惟一的,因为Set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set不保证维护元素的次序。Set与Collection有完全一样的接口。

     在没有其他限制的情况下需要Set时应尽量使用HashSet,因为它对速度进行了优化。

     下面是HashSet的定义: 

1 public class HashSet
2 extends AbstractSet
3 implements Set, Cloneable, java.io.Serializable

     HashSet继承了AbstractSet,实现了Set接口。其实AbstractSet已经实现Set接口了。AbstractSet继承自AbstractCollection,而AbstractCollection实现了Collection接口的部分方法,而Set接口和Collection接口完全一致,所以AbstractSet只是实现了AbstractCollection没有实现的Set接口的方法和重写了部分AbstractCollection已经实现的方法。

     下面是HashSet定义的属性:

1 private transient HashMap map;
2 private static final Object PRESENT = new Object();

     为什么会有一个HashMap定义的属性?

     想一下HashMap有什么特点:基于哈希表,存储键值对,Key不能相同等等。Key不能相同!这个特点是不是和Set的元素不能相同和类似?如果将Set的元素当成Map的Key,是否就保证了元素的不重复?!答案是肯定的。但是Map存储键值对,Key有了,那么Value呢?这正是第二个属性PERSENT的意义。看到PERSENT属性时一个Object对象,且是static和final的,它的用途就是当做Value存进map中。

     总结一下,HashSet的实现方式大致如下,通过一个HashMap存储元素,元素是存放在HashMap的Key中,而Value统一使用一个Object对象。

     这样看来HashSet应该很简单,应该只是使用了HashMap的部分内容来实现。

     下面看具体的其它代码来验证上面的猜想。

     构造方法:

1 // 构造方法一:调用默认的HashMap构造方法初始化map
2 public HashSet() {
3 map = new HashMap();
4 }
5 // 构造方法二:根据给定的Collection参数调用HashMap(int initialCapacity)的构造方法创建一个HashMap(这个构造方法的HashMap的源码分析里已经描述过了)
6 // 调用addAll方法将c中的元素添加到HashSet对象中
7 public HashSet(Collectionextends E> c) {
8 map = new HashMap(Math.max((int) (c.size()/.75f) + 1, 16));
9 addAll(c);
10 }
11 // 构造方法三:构造一个指定初始化容量和负载因子的HashMap
12 public HashSet(int initialCapacity, float loadFactor) {
13 map = new HashMap(initialCapacity, loadFactor);
14 }
15 // 构造方法四:构造一个指定初始化容量的HashMap
16 public HashSet(int initialCapacity) {
17 map = new HashMap(initialCapacity);
18 }
19 // 构造方法五:构造一个指定初始化容量和负载因子的LinkedHashMap
20 // dummy参数被忽略,只是用于区分其他的,包含一个int、float参数的构造方法
21 HashSet(int initialCapacity, float loadFactor, boolean dummy) {
22 map = new LinkedHashMap(initialCapacity, loadFactor);
23 }


     上面的构造方法都很简单,只有构造方法二中调用了addAll(Collection c)方法。该方法在AbstractCollection中定义,HashSet通过继承拥有该方法。

1 public boolean addAll(Collectionextends E> c) {
2 boolean modified = false;
3 Iteratorextends E> e = c.iterator();
4 while (e.hasNext()) {
5 if (add(e.next()))
6 modified = true;
7 }
8 return modified;
9 }


     这个方法通过遍历c中的元素,然后调用add(E e)方法添加元素。

1 public boolean add(E e) {
2 return map.put(e, PRESENT)==null;
3 }

     看add(E e)方法只是调用了HashMap(构造方法中提供了创建LinkedHashMap的方式,但是LinkedHashMap是继承HashMap的,put方法也是调用HashMap的put方法)的put方法将e当做Key,PERSENT当做Value加入到map中并根据返回值判断是否添加成功。

     因为HashMap的put方法在Key已经存在的情况下返回的是对应的Value值,若Key不存在则返回的是null,所以根据返回的是null可以确定新元素被添加到HashSet中了,如果返回的是其他值则说明Key已经存在,即元素已经在HashSet中已经存在,add(E e)返回的结果为false。虽然add(E e)返回false说明了HashSet添加元素失败,但实际上其中的map中的内容已经被替换,原先的值被PERSENT代替。

     如果原先的值就是null呢?其实不用考虑这个问题,因为通过HashSet添加的元素,Value的内容都是PERSENT,不会出现null的情况。

     iterator()

1 public Iterator iterator() {
2 return map.keySet().iterator();
3 }

     很清楚了,返回的是HashMap中KeySet的迭代器。

     size()

1 public int size() {
2 return map.size();
3 }

     size()方法同样返回的是map的大小,所以HashSet根本就没定义size属性。

1 public boolean isEmpty() {
2 return map.isEmpty();
3 }

     既然size()用的是map的大小,那么isEmpty()自然也是判断map。

1 public boolean contains(Object o) {
2 return map.containsKey(o);
3 }
4 public void clear() {
5 map.clear();
6 }
7 public Object clone() {
8 try {
9 HashSet newSet = (HashSet) super.clone();
10 newSet.map = (HashMap) map.clone();
11 return newSet;
12 } catch (CloneNotSupportedException e) {
13 throw new InternalError();
14 }
15 }


     这几个方法就不解释了。

1 public boolean remove(Object o) {
2 return map.remove(o)==PRESENT;
3 }

     remove(Object o)为什么还要判断结果呢?因为通过HashSet存入的元素,所对应的Value值都是PERSENT,如果传入的o不存在,map的remove方法返回为null,则对应的结果是HashSet的remove操作应该放回false,所以这里根据返回的结果判断是否移除成功。

     只能感叹HashMap太强大了,HashSet是完全使用HashMap来实现的。

---------------------------------------------------------------------------------------------------------------------------------

     LinkedHashSet源码分析

     LinkedHashSet具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按照元素的插入次序显示。

     看LinkedHashSet的内容。

1 public class LinkedHashSet
2 extends HashSet
3 implements Set, Cloneable, java.io.Serializable {
4
5 public LinkedHashSet(int initialCapacity, float loadFactor) {
6 super(initialCapacity, loadFactor, true);
7 }
8
9 public LinkedHashSet(int initialCapacity) {
10 super(initialCapacity, .75f, true);
11 }
12
13 public LinkedHashSet() {
14 super(16, .75f, true);
15 }
16
17 public LinkedHashSet(Collectionextends E> c) {
18 super(Math.max(2*c.size(), 11), .75f, true);
19 addAll(c);
20 }
21 }


     LinkedHashSet继承自HashSet,HashSet基于HashMap实现,看LinkedHashSet类只是定义了四个构造方法,也没看到和链表相关的内容,为什么说LinkedHashSet内部使用链表维护元素的插入顺序(插入的顺序)呢?

    注意这里的构造方法,都调用了父类HashSet的第五个构造方法:HashSet(int initialCapacity, float loadFactor, boolean dummy)。如果还记得上面的内容应该明白为什么是基于链表,下面再给出这个构造方法的内容。

1 HashSet(int initialCapacity, float loadFactor, boolean dummy) {
2 map = new LinkedHashMap(initialCapacity, loadFactor);
3 }

     区别于其他的HashSet的构造方法,这个方法创建的是一个LinkedHashMap。LinkedHashMap继承自HashMap,同时自身有一个链表结构用于维护元素顺序,默认情况使用的是插入元素(见《LinkedHashMap源码分析》),所以LinkedHashSet既有HashSet的访问速度(因为访问的时候都是通过HashSet的方法访问的),同时可以维护顺序。

    下面是一个HashSet和LinkedHashSet维护元素顺序的例子。

1 Set linkedSet = new LinkedHashSet();
2 linkedSet.add("First");
3 linkedSet.add("Second");
4 linkedSet.add("Thrid");
5 linkedSet.add("Fourth");
6 System.out.println("LinkedHashSet:"+linkedSet);
7 Set hashSet = new HashSet();
8 hashSet.add("First");
9 hashSet.add("Second");
10 hashSet.add("Thrid");
11 hashSet.add("Fourth");
12 System.out.println("HashSet:"+hashSet);
13 // LinkedHashSet:[First, Second, Thrid, Fourth]
14 // HashSet:[Fourth, Second, Thrid, First]


推荐阅读
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文介绍了Java中Hashtable的clear()方法,该方法用于清除和移除指定Hashtable中的所有键。通过示例程序演示了clear()方法的使用。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • Whatsthedifferencebetweento_aandto_ary?to_a和to_ary有什么区别? ... [详细]
author-avatar
蓝田学园赴嘉兴镇海吉林实践团队
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有