【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);
		
	}

}
        运行如下所示:

        【java集合系列】---HashSet

        通过这个例子我们可以看出来集合的第一个特点,无序,我们添加的时候,顺序是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);
		
	}

}
       运行效果如下所示:

       【java集合系列】---HashSet

       我们来看一下,到底是谁没有添加进去呢?编写相关代码,如下所示:

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);
		
	}

}
        运行如下所示:

        【java集合系列】---HashSet

        通过上面的截图,我们知道,第一个元素加进去了,第二个元素没有加进去,通过上面的小例子,我们知道,集合有两个特点,一个是无序,一个是无重复,接着,我们再来新建一个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;
	}
}
       运行如下所示:   【java集合系列】---HashSet

通过结果我们知道,打印出来两个对象,但是我们无法确定第一个就是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;
	}
}
运行如下所示:
【java集合系列】---HashSet

三个对象的地址不一样,所以可以添加进来,接着,我们来修改代码,如下所示:

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;
	}
}
运行效果如下所示:

【java集合系列】---HashSet

虽然引用了两个对象,但是实际上是一个对象,接着,我们来修改代码部分,如下所示:

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;
	}
}
运行如下所示:

【java集合系列】---HashSet

对象是两个,但是内容是一样的;上面的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);
		
	}

}
运行效果如下所示:

【java集合系列】---HashSet

通过这个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,看看她们之间的区别。

【java集合系列】---HashSet
小编寄语:该博文,小编主要简单介绍了Set集合中的HashSet,从Set集合的基本特点开始入手。通过简单的demo来介绍set集合的特点,以及equals和hashCode方法,最后看一下HashSet的底层源码以及与hashMap的对比,,她是基于HashMap实现的,HashSet底层采用HashMap来保存所有元素,因此HashSet的实现比较简单,通过源码我们可以看出来,HashSet的实现只是封装了一个HashMap对象来存储所有的集合元素,java集合,未完待续......
上一篇:揭秘 DockerCon 重量级演讲嘉宾(七)


下一篇:揭秘 DockerCon 重量级演讲嘉宾(二)