java集合系列---HashSet
Posted 丁国华
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java集合系列---HashSet相关的知识,希望对你有一定的参考价值。
在前面的博文中,小编主要简单介绍了java集合中的总体框架,以及list接口中典型的集合ArrayList和LinkedList,接着,我们来看set的部分集合,set集合和数学意义上的集合没有差别,作为集合,可以容纳多个元素,而且,集合里面没有重复的元素,Set集合是Collection的子集,Set集合与Collection基本相同,没有提供任何额外的方法,只是Set不允许包含重复的元素,今天这篇博文小编主要介绍Set集合中的HashSet,小编会通过简单的demo来介绍set集合的特点,以及equals和hashCode方法,最后看一下HashSet的底层源码以及与hashMap的对比`(*∩_∩*)′,请小伙伴们多多指教`(*∩_∩*)′。我们知道Set的集合是无序、不可重复的集合,首先,我们来看一下HashSet,HashSet是set集合中用的最多的,so,我们来看下面的一个小例子:
package j2se.demo;
import java.util.HashSet;
public class SetTest1
public static void main(String[] args)
HashSet set = new HashSet();
set.add("a");
set.add("b");
set.add("c");
set.add("d");
System.out.println(set);
运行如下所示:
通过这个例子我们可以看出来集合的第一个特点,无序,我们添加的时候,顺序是abcd,出来的时候是dbca,这是集合的第一个特点;我们再向集合里面添加一个a,如下所示:
package j2se.demo;
import java.util.HashSet;
public class SetTest1
public static void main(String[] args)
HashSet set = new HashSet();
set.add("a");
set.add("b");
set.add("c");
set.add("d");
set.add("a");
System.out.println(set);
运行效果如下所示:
我们来看一下,到底是谁没有添加进去呢?编写相关代码,如下所示:
package j2se.demo;
import java.util.HashSet;
public class SetTest1
public static void main(String[] args)
HashSet set = new HashSet();
System.out.println(set.add("a"));
set.add("b");
set.add("c");
set.add("d");
System.out.println(set.add("a"));
System.out.println(set);
运行如下所示:
通过上面的截图,我们知道,第一个元素加进去了,第二个元素没有加进去,通过上面的小例子,我们知道,集合有两个特点,一个是无序,一个是无重复,接着,我们再来新建一个class,命名为SetTest2,编写代码如下所示:
package jihe;
import java.util.HashSet;
public class SetTest2
public static void main(String[] args)
HashSet set = new HashSet();
set.add(new People("zhangsan"));
set.add(new People("lisi"));
System.out.println(set);
class People
String name;
public People(String name)
this.name= name;
运行如下所示:
通过结果我们知道,打印出来两个对象,但是我们无法确定第一个就是zhangsan这个对象,第二个就是lisi这个对象。我们再来添加一个,如下所示:
package jihe;
import java.util.HashSet;
public class SetTest2
public static void main(String[] args)
HashSet set = new HashSet();
set.add(new People("zhangsan"));
set.add(new People("lisi"));
set.add(new People("zhangsan"));
System.out.println(set);
class People
String name;
public People(String name)
this.name= name;
运行如下所示:三个对象的地址不一样,所以可以添加进来,接着,我们来修改代码,如下所示:
Package jihe;
import java.util.HashSet;
public class SetTest2
public static void main(String[] args)
HashSet set = new HashSet();
// set.add(new People("zhangsan"));
// set.add(new People("lisi"));
// set.add(new People("zhangsan"));
People p1 = new People("zhangsan");
set.add(p1);
set.add(p1);
System.out.println(set);
class People
String name;
public People(String name)
this.name= name;
运行效果如下所示:
虽然引用了两个对象,但是实际上是一个对象,接着,我们来修改代码部分,如下所示:
package jihe;
import java.util.HashSet;
public class SetTest2
public static void main(String[] args)
HashSet set = new HashSet();
// set.add(new People("zhangsan"));
// set.add(new People("lisi"));
// set.add(new People("zhangsan"));
// People p1 = new People("zhangsan");
//
// set.add(p1);
// set.add(p1);
String s1 = new String("a");
String s2 = new String("a");
set.add(s1);
set.add(s2);
System.out.println(set);
class People
String name;
public People(String name)
this.name= name;
运行如下所示:
对象是两个,但是内容是一样的;上面的demo,小编主要介绍了set集合中的添加一个元素的相关操作,通过查看api文档,我们发现出现了一个关键字equals,接着,我们来看,关于Object类的equals方法的特点,ps:这里的无序是指x和y两个对象是否equals。
关于Object类的equals方法的特点:
a、自反性:x.equals(x)应该返回true。
b、对称性:x.equals(y)为true。
c、传递性:x.equals(y)为true并且y.equals(z)为true,那么x.equals(z)也应该为true。
d、一致性:x.equals(y)的第一次调用true,那么x.equals(y)的第二次、第三次、第n次调用也应该为true,前提田间是在比较之间没有修改x也没有修改y。
e、对于非空引用x,x.equals(null)返回false。
当我们override equals方法的时候,同时我们也要override HashCode方法,so我们来看HashCode方法的特点:
a、在Java应用的一次执行过程当中,对于同一个对象的hashCode方法的多次调用,他们应该返回同样的值,前提是该对象的信息没有发生变化。
b、对于两个对象来说,如果使用equals方法比较返回true,那么这两个对象的hashCode值一定是相同的。
c、对于两个对象来说,如果使用equals方法比较返回false,那么这两个对象的hashCode值不要求一定不同,可以相同,可以不同,但是如果不同则可以提高应用的性能。
d、对于Object类来说,不同的Object对象的hashCode值是不同的,Object类的hashCode值表示的是对象的地址。
当我们使用HashSet的时候,hashCode方法就会得到调用,判断已经存储在集合中的对象的hashCode值是否一致,如果不一致,直接加进去,如果一致,再进行equals方法的比较,equals方法如果返回true,表示对象已经添加进去了,就不会增加新的对象,反之,加进去,特别需要注意的是,如果我们重写了equals方法,那么也要重写hashCode方法,反之亦然。通过分析,我们知道,判断一个对象能否放入到集合里面,是通过hashCode以及equals方法来共同完成的,接着,我们再来看一个demo,如何将我们自定义的类,放到集合当中,相同的名字就不能放入到集合中,我们需要做的就是重写equals方法和hashCode方法,新建class,取名为SetTest3,编写相关代码,如下所示:
package jihe;
import java.util.HashSet;
public class SetTest3
public static void main(String[] args)
HashSet set = new HashSet();
class Student
String name;
public Student(String name)
this.name=name;
public int hashCode()
return this.name.hashCode();
public boolean equals(Object obj)
if(this==obj)
return true;
if(null !=obj && obj instanceof Student)
Student s = (Student)obj;
if(name.equals(s.name))
return true;
return false;
接着,编写SetTest里面的代码,如下所示:
import java.util.HashSet;
public class SetTest
public static void main(String[] args)
HashSet set = new HashSet();
Student s1 = new Student("dingguohua");
Student s2 = new Student("dingguohua");
set.add(s1);
set.add(s2);
System.out.println(set);
运行效果如下所示:
通过这个demo,我们知道,对象不同,内容相同,所以添加不进去,这是在实际应用中,很常用的一种方式,我们不使用object提供的hashcode和equals方法,转而,使用自己的实现,在实际应用中,我们向集合中添加元素的时候,我们都是根据内容而不是根据地址决定的。
HashSet的底层部分源码
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
// 使用 HashMap 的 key 保存 HashSet 中所有元素
private transient HashMap<E,Object> map;
// 定义一个虚拟的 Object 对象作为 HashMap 的 value
private static final Object PRESENT = new Object();
...
// 初始化 HashSet,底层会初始化一个 HashMap
public HashSet()
map = new HashMap<E,Object>();
// 以指定的 initialCapacity、loadFactor 创建 HashSet
// 其实就是以相应的参数创建 HashMap
public HashSet(int initialCapacity, float loadFactor)
map = new HashMap<E,Object>(initialCapacity, loadFactor);
public HashSet(int initialCapacity)
map = new HashMap<E,Object>(initialCapacity);
HashSet(int initialCapacity, float loadFactor, boolean dummy)
map = new LinkedHashMap<E,Object>(initialCapacity
, loadFactor);
// 调用 map 的 keySet 来返回所有的 key
public Iterator<E> iterator()
return map.keySet().iterator();
// 调用 HashMap 的 size() 方法返回 Entry 的数量,就得到该 Set 里元素的个数
public int size()
return map.size();
// 调用 HashMap 的 isEmpty() 判断该 HashSet 是否为空,
// 当 HashMap 为空时,对应的 HashSet 也为空
public boolean isEmpty()
return map.isEmpty();
// 调用 HashMap 的 containsKey 判断是否包含指定 key
//HashSet 的所有元素就是通过 HashMap 的 key 来保存的
public boolean contains(Object o)
return map.containsKey(o);
// 将指定元素放入 HashSet 中,也就是将该元素作为 key 放入 HashMap
public boolean add(E e)
return map.put(e, PRESENT) == null;
// 调用 HashMap 的 remove 方法删除指定 Entry,也就删除了 HashSet 中对应的元素
public boolean remove(Object o)
return map.remove(o)==PRESENT;
// 调用 Map 的 clear 方法清空所有 Entry,也就清空了 HashSet 中所有元素
public void clear()
map.clear();
...
对于Hash来说,她是基于HashMap实现的,HashSet底层采用HashMap来保存所有元素,因此HashSet的实现比较简单,通过源码我们可以看出来,HashSet的实现只是封装了一个HashMap对象来存储所有的集合元素,如果放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个 Object 对象。HashSet 的绝大部分方法都是通过调用 HashMap 的方法来实现的,因此 HashSet 和 HashMap 两个集合在实现本质上是相同的。
HashSet && HashMap
在比较她们两个之前,我们来分别看一下什么是HashSet以及HashMap:
什么是HashSet?
通过前面的介绍,我们知道HashSet实现的是Set接口,她有两个特点,无序和无重复,在存储HashSet的时候,要先确保对象重写equals和hashCode方法,这样才能比较对象的值是否相等,所以确保set中没有存储相等的对象,如果我们没有重写过这两个方法, 即public boolean add(Object o)方法用来在Set中添加元素,当元素值重复时则会立即返回false,如果成功添加返回true。
什么是HashMap?
通过java集合框架这篇博文,我们知道HashMap实现了Map这个接口,Map接口是对键值对进行映射,Map中不允许有重复的键,Map接口又有个基本的实现HashMap和TreeMap,TreeMap保存了对象的排列次序,而HashMap则不能,HashMap允许键和值为null,HashMap是非synchronized的,但collection框架提供方法能保证HashMap synchronized,这样多个线程同时访问HashMap的时,能保证只有一个线程更改Map;
public Object put(Object Key,Object value)方法用来将元素添加到map中。接着,我们来对比一下HashSet和HashMap,看看她们之间的区别。
小编寄语:该博文,小编主要简单介绍了Set集合中的HashSet,从Set集合的基本特点开始入手。通过简单的demo来介绍set集合的特点,以及equals和hashCode方法,最后看一下HashSet的底层源码以及与hashMap的对比,,她是基于HashMap实现的,HashSet底层采用HashMap来保存所有元素,因此HashSet的实现比较简单,通过源码我们可以看出来,HashSet的实现只是封装了一个HashMap对象来存储所有的集合元素,java集合,未完待续......
以上是关于java集合系列---HashSet的主要内容,如果未能解决你的问题,请参考以下文章
Java集合系列四HashSet和LinkedHashSet解析