【java集合系列】---HashSet

简介: 在前面的博文中,小编主要简单介绍了java集合中的总体框架,以及list接口中典型的集合ArrayList和LinkedList,接着,我们来看set的部分集合,set集合和数学意义上的集合没有差别,作为集合,可以容纳多个元素,而且,集合里面没有重...

在前面的博文中,小编主要简单介绍了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集合,未完待续......
目录
相关文章
|
29天前
|
算法 Java 数据处理
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。HashSet基于哈希表实现,提供高效的元素操作;TreeSet则通过红黑树实现元素的自然排序,适合需要有序访问的场景。本文通过示例代码详细介绍了两者的特性和应用场景。
40 6
|
29天前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
38 3
|
29天前
|
存储 Java 数据处理
Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位
【10月更文挑战第16天】Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位。本文通过快速去重和高效查找两个案例,展示了Set如何简化数据处理流程,提升代码效率。使用HashSet可轻松实现数据去重,而contains方法则提供了快速查找的功能,彰显了Set在处理大量数据时的优势。
32 2
|
1月前
|
存储 算法 Java
Java HashSet:底层工作原理与实现机制
本文介绍了Java中HashSet的工作原理,包括其基于HashMap实现的底层机制。通过示例代码展示了HashSet如何添加元素,并解析了add方法的具体过程,包括计算hash值、处理碰撞及扩容机制。
|
9天前
|
Java
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式。本文介绍了 Streams 的基本概念和使用方法,包括创建 Streams、中间操作和终端操作,并通过多个案例详细解析了过滤、映射、归并、排序、分组和并行处理等操作,帮助读者更好地理解和掌握这一重要特性。
18 2
|
9天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
14天前
|
存储 Java
判断一个元素是否在 Java 中的 Set 集合中
【10月更文挑战第30天】使用`contains()`方法可以方便快捷地判断一个元素是否在Java中的`Set`集合中,但对于自定义对象,需要注意重写`equals()`方法以确保正确的判断结果,同时根据具体的性能需求选择合适的`Set`实现类。
|
14天前
|
存储 Java 开发者
在 Java 中,如何遍历一个 Set 集合?
【10月更文挑战第30天】开发者可以根据具体的需求和代码风格选择合适的遍历方式。增强for循环简洁直观,适用于大多数简单的遍历场景;迭代器则更加灵活,可在遍历过程中进行更多复杂的操作;而Lambda表达式和`forEach`方法则提供了一种更简洁的函数式编程风格的遍历方式。
|
14天前
|
Java 开发者
|
26天前
|
安全 Java 程序员
深入Java集合框架:解密List的Fail-Fast与Fail-Safe机制
本文介绍了 Java 中 List 的遍历和删除操作,重点讨论了快速失败(fail-fast)和安全失败(fail-safe)机制。通过普通 for 循环、迭代器和 foreach 循环的对比,详细解释了各种方法的优缺点及适用场景,特别是在多线程环境下的表现。最后推荐了适合高并发场景的 fail-safe 容器,如 CopyOnWriteArrayList 和 ConcurrentHashMap。
53 5