关闭

HashSet (容器)学习

292人阅读 评论(0) 收藏 举报
分类:

set代表无序、不可重复的集合

 

HashSet按Hash算法来存储集合中的元素,因此具有很好的存取和查找性能。

HashSet具有以下特点:

1、不能保证元素的排列顺序,顺序有可能发生变化

2、Hash不是同步的,如果多个线程同时访问一个HashSet,假设有两个或者两个以上线程同时修改了HashSet集合时,则必须通过代码来保证其同步

3、集合元素的值可以是null

 

    当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该HashCode值决定该对象在HashSet中的存储位置。如果有两个元素通过equals()方法比较返回true,但他们的hashCode()方法返回值不相等,HashSet将会把他们存储在不同的位置,依然可以添加成功。

 

简单的说:HashSet集合判断两个元素相等的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法返回值也相等

 

    如果两个对象通过equals()方法比较返回true,但这两个对象的hashCode()方法返回不同的hashCode值时,这将导致HashSet会把这两个对象保存在Hash表不同的位置,从而使两个对象都可以添加成功,这就与Set集合的规则有些不符了

 

    如果两个对象的hashCode()方法返回的hashCode值相同,但他们通过equals()方法比较返回false时将更麻烦:因为两个对象的hashCode值相同,HashSet将试图把他们保存在同一位置,但又不行(否则将只剩下一个对象),所以实际上会在这个位置用链式结构来保存多个对象;而HashSet访问集合元素时也是根据元素的hashCode值来快速定位的,如果HashSet中两个以上的元素具有相同的hashCode值,将会导致性能下降

 

为什么不直接使用数组还需要使用HashSet呢?

因为数组元素的索引是连续的,并且数组的长度是固定的,无法自由增加数组的长度。

而HashSet就不一样了:

1、HashSet采用每个元素的hashcode值来计算其索引

2、可以自由增加HashSet的长度,并可以根据元素的hashCode值来访问元素——这就是HashSet速度快的原因

 

import java.util.*;

//类A的equals方法总是返回true,但没有重写其hashCode()方法
class A
{
	public boolean equals(Object obj)
	{
		return true;
	}
}
//类B的hashCode()方法总是返回1,但没有重写其equals()方法
class B
{
	public int hashCode()
	{
		return 1;
	}
}
//类C的hashCode()方法总是返回2,但没有重写其equals()方法
class C
{
	public int hashCode()
	{
		return 2;
	}
	public boolean equals(Object obj)
	{
		return true;
	}
}
public class TestHashSet
{
	public static void main(String[] args) 
	{
		HashSet books = new HashSet();
		//分别向books集合中添加2个A对象,2个B对象,2个C对象
		books.add(new A());
		books.add(new A());
		books.add(new B());
		books.add(new B());
		books.add(new C());
		books.add(new C());
		System.out.println(books);
	}
}

 

运行结果:

[hb.B@1, hb.B@1, hb.C@2, hb.A@4f1d0d, hb.A@1fc4bec]

 

如果需要把某个类的对象保存到HashSet集合中,重写这个类的equals()方法和hashCode()方法时,应该尽量保证两个对象通过equals()方法比较返回true时,他们的hashCode()方法返回值也相等。

 

LinkedHashSet是HashSet的子类,它也是根据元素的hashCode值来决定存储位置,但它同时使用链表维护元素的次序,使得元素看起来是以插入的顺序保存的。即,当遍历LinkedHashSet集合里元素时,

1、LinkedHashSet将会按元素的添加顺序来访问集合里的元素

2、LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet性能,但在迭代访问Set里的全部元素时将有很好的性能,因为它以链表来维护内部顺序

 

TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。

因为TreeSet中的元素是有序的,所以增加了访问第一个、前一个、后一个、最后一个元素的方法,并提供了三个从TreeSet中截取子TreeSet的方法。

 

TreeSet支持两种排序方式:自然排序和定制排序;默认情况下TreeSet采用自然排序。

TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序排列,这种方式就是自然排序。

定制排序:例如降序排列。

 

class M
{
	int age;
	public M(int age)
	{
		this.age = age;
	}
}

class N
{
	int age;
	public N(int age)
	{
		this.age = age;
	}
}

public class TestTreeSet3
{
	public static void main(String[] args) 
	{
		TreeSet ts = new TreeSet(new Comparator()
		{
			public int compare(Object o1, Object o2)
			{
				int age1 = o1 instanceof M ? ((M)o1).age :((N)o1).age;
				int age2 = o1 instanceof M ? ((M)o2).age :((N)o2).age;
				return age2 - age1;
			}
		});	
		ts.add(new M(5));
		ts.add(new M(-3));
//		ts.add(new N(9));//必须是同一类型,否则报错
		ts.add(new M(9));
		System.out.println(((M)ts.first()).age);
		System.out.println(((M)ts.last()).age);
		
	}
}

 打印结果:

9

-3

 

使用TreeSet注意如下问题:

1、想TreeSet集合中添加元素时,只有第一个元素无须实现Comparable接口,后面添加的所有元素都必须实现Comparable接口。即如果试图把一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口,否则程序将会抛出异常。

2、大部分类在实现compareTo(Object obj)方法时,都需要将被比较对象obj强制类型转换成相同类型,因此只有相同类型的两个实例才会比较大小。即向TreeSet中添加的应该是同一个类的对象,否则也会引发ClassCastException.

3、对于TreeSet集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过compareTo(Object obj)方法比较是否返回0,如果compareTo(Object obj)方法比较返回0则TreeSet会认为他们相等,否则就是不相等。

 

EnumSet是一个专门为枚举类设计的集合类,EnumSet中的所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet时显示或隐士地指定。EnumSet的集合元素也是有序的,EnumSet以枚举值在Enum类的定义顺序来决定集合的元素顺序。

 

HashSet(包括LinkedHashSet)、TreeSet、EnumSet都是线程不安全的。

 

 

java的HashCode方法介绍:

有许多人学了很长时间的Java,但一直不明白hashCode方法的作用, 

我来解释一下吧。首先,想要明白hashCode的作用,你必须要先知道Java中的集合。   

总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set。 

你知道它们的区别吗?前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。 

那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢? 

这就是Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。 

也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。    

于是,Java采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。 

哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。如果详细讲解哈希算法,那需要更多的文章篇幅,我在这里就不介绍了。 

初学者可以这样理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。   

这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。 

如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了, 

就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。 

所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。   

所以,Java对于eqauls方法和hashCode方法是这样规定的: 

1、如果两个对象相同,那么它们的hashCode值一定要相同;2、如果两个对象的hashCode相同,它们并不一定相同     上面说的对象相同指的是用eqauls方法比较。   

你当然可以不按要求去做了,但你会发现,相同的对象可以出现在Set集合中。同时,增加新元素的效率会大大下降。

 

hashcode这个方法是用来鉴定2个对象是否相等的。 那你会说,不是还有equals这个方法吗? 不错,这2个方法都是用来判断2个对象是否相等的。但是他们是有区别的。 一般来讲,equals这个方法是给用户调用的,如果你想判断2个对象是否相等,你可以重写equals方法,然后在代码中调用,就可以判断他们是否相等 了。简单来讲,equals方法主要是用来判断从表面上看或者从内容上看,2个对象是不是相等。举个例子,有个学生类,属性只有姓名和性别,那么我们可以 认为只要姓名和性别相等,那么就说这2个对象是相等的。 hashcode方法一般用户不会去调用,比如在hashmap中,由于key是不可以重复的,他在判断key是不是重复的时候就判断了hashcode 这个方法,而且也用到了equals方法。这里不可以重复是说equals和hashcode只要有一个不等就可以了!所以简单来讲,hashcode相 当于是一个对象的编码,就好像文件中的md5,他和equals不同就在于他返回的是int型的,比较起来不直观。我们一般在覆盖equals的同时也要 覆盖hashcode,让他们的逻辑一致。举个例子,还是刚刚的例子,如果姓名和性别相等就算2个对象相等的话,那么hashcode的方法也要返回姓名 的hashcode值加上性别的hashcode值,这样从逻辑上,他们就一致了。 要从物理上判断2个对象是否相等,用==就可以了。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:311029次
    • 积分:8725
    • 等级:
    • 排名:第2259名
    • 原创:1096篇
    • 转载:14篇
    • 译文:0篇
    • 评论:5条
    最新评论