Java学习笔记:HashSet,TreeSet和LinkedHashSet

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/sinat_38393872/article/details/91128302

这篇文章是对自己学习的一个总结,学习资料是疯狂Java讲义第三版,李刚编,电子工业出版社出版。

注:本文只是对HashSet,TreeSet,LinkedHashSet和EnumSet中常使用到的方法进行总结,以及对他们的性质做总结。

这三个是继承Collection,Collection的常用方法在这个博客里面https://blog.csdn.net/sinat_38393872/article/details/90904958

先说一下HashSet的三个特点

  • 排列顺序和添加顺序不一致
  • 非同步集合:如果保持同步的话要手动添加代码
  • 集合元素可以是null

说一下HashSet存储元素的过程。HashSet是在Set的基础上加上了Hash表的特性,所以HashSet的唯一有点就是存储和查找过程很快,时间开销是常数级。HashSet存储时,会调用存储存储对象的hashCode()方法获取hashCode,然后根据hashCode得到该存储对象的存储位置。

如果有两个元素值相等但是hashCode不相等,那么hashSet认为这两个是不同的元素。所以想要使用hashSet存储某个自定义类对象时,不仅要对equals()方法覆写,还要对hashCode()方法覆写。下面讲一下hashCode()的覆写规则。

假如我们的value有多个,根据value的类型,对应的hashCode的计算方式也不一样,下面是计算规则,设f就是value的值

实例变量类型 计算方式
boolean hashCode = (f ? 0 : 1)
byte, short, char, int hashCode = (int) f
long hashCode = (int) (f^(f>>>32))
float hashCode = Float.floatToIntBits(f)
double

long l = Double.doubleToLongBits(f);

hashCode = (int) (l ^ (l >>> 32));

引用类型 hashCode = f.hashCode()

得到各value的hashCode之后,将这些hashCode乘以不同的数字再相加,最后得到的数就是要返回的hashCode。

比如我们自定义一个类

class A{
    int value1;
    long value2;
}

这个类A的hashCode是要通过value1和value2计算出来的。那么根据上面的规则,value1和value2的hashCode如下代码

hashCode1 = value1;
hashCode2 = (int) (value2 ^ (value2 >>> 32));

最终返回的hashCode就应该是

return ((hashCode1 << 5)- hashCode1) + ((hashCode2 << 3) - hashCode2); //乘31和乘7是随机选
//的两个数,也可以选其它的数字,只要是两个不同的质数就行

完整代码如下

class A{
    int value1;
    long value2;
    public int hashCode(){
        hashCode1 = value1;
        hashCode2 = (int) (value2 ^ (value2 >>> 32));
        return ((hashCode1 << 5)- hashCode1) + ((hashCode2 << 3) - hashCode2); 
    }
}

各自的value计算出对应hashCode后还要乘以不同质数相加的原因是,为了防止明明关键字不同,但相加结果恰好相等这样的情况。比如一个存储对象有两个value计算出的哈希值分别是1和2,另一个存储对象的两个value计算出的哈希值分别是2和1,这两个对象不一样计算出来的哈希值应该要不一样才对,直接相加的话就错了,所以要分别乘上不同的数,这样可以有效防止这样的情况发生。至于为什么要乘以质数,我还想不明白,最好先和书上写的保持一致。


现在开始将LinkedHashSet

LinkedHashSet没什么好讲的,只有它的性质值得提一下。

LinkedHashSet内部也是根据hashCode()来存储的,但是它内部是用链表进行维护的,所以性能略低于HashSet。但是遍历元素的时候比HashSet强。


现在是TreeSet的环节

TreeSet是自动排序的集合,和一般的Set相比,它还提供了更多的方法

Comparator comparator()

如果TreeSet采用的是自定义的排序方法,那么这个方法返回的是自定义的comparator。如果是使用默认排序方式,那么返回的是null。

Object first();
Object last();

分别是返回第一个和最后一个元素

Object lower(Object e);
Object higher(Object e);

分别是返回位于指定元素之前和之后的元素。其中的e可以不是TreeSet中的元素,e只是一个参照。

SortedSet subSet(Object fromElement, Object toElement)

返回Set的子集,范围是从fromElement(包含)到toElement(不包含)。注意,这里返回的不是一个新的集合,只是源集合的一个View,一个视图,一个投影,Java文档里是这样写的

这意味着你改变这个子集的话,在源集合中也会遭受同样的改变。

代码验证如下

public class test{
	public static void main(String[] args){
		TreeSet c = new TreeSet();  //c是源集合
		c.add(1);
		c.add(2);
		c.add(3);
		c.add(4);
		c.add(5);
		//现在c中的元素是(1,2,3,4,5)
		SortedSet a = c.subSet(1,3);
		//a是(1,2)
		a.remove(2);
		//删除a中的[2]之后,源集合中的[2]也应该被删除了
		Iterator it = c.iterator();
		while(it.hasNext()) {
			System.out.print(it.next() + " ");
		}
	}
}

这段程序的输出是

这就验证了上面所说的内容。下面开始讲其它方法

SortedSet headSet(Object toElement);    //小于
SortedSet tailSet(Object toElement);    //大于等于

分别是返回小于指定元素和大于等于指定元素的子集,返回的也是投影。

  • TreeSet定制排序

TreeSet是排序的一个集合,默认情况下是根据系统的compareTo(Object obj)方法进行排序。我们不想用默认方法想用定制方法是就要自定义排序规则,下面讲解如何自定义排序规则。

TreeSet排序是根据compareTo(Object obj)来排的,所以自定义排序规则时我们只要让欲添加到TreeSet里排序的元素继承Comparable接口,覆写其中的compareTo(Object obj)和equals(Object obj)方法即可。代码示例如下。

//我们要将A添加到TreeSet,就要对A添加排序规则。
class A implements Comparable{
	private int value;
	private String string;
	A(int value, String string){
		this.value = value;
		this.string = string;
	}
	public int getValue() {
		return value;
	}
	//两个A的对象相等的标准应该是值相等,即value和string都相等
	public boolean equals(Object obj) {
		if(this == obj) {
			return true;
		}
		if(obj != null && obj.getClass() == A.class) {
			A a = (A) obj;
			if(a.value == this.value) {
				if(a.string.equals(this.string)) {
					return true;
				}
			}	
		}
		return false;
	}
	//这里设定比较的规则是先比较value,value相等的情况再比较字符串
	@Override
	public int compareTo(Object obj) {
		A a = (A) obj;
		if(this.value > a.value) return 1;
		if(this.value < a.value) return -1;
		if(this.value == a.value) {
			int result = this.string.compareTo(a.string);
			if(result > 0) return 1;
			if(result < 0) return -1;
		}
		return 0;
	}	
}

现在开始讲EnumSet类

EnumSet是专门为枚举类设计的集合,集合内的元素是有序的,以枚举值在Enum类中的定义顺序相同。

Enum内部以位向量的形式存储,所以Enum占用的空间非常小,而且运行效率非常高。

Enum没有任何构造器来创建类的实例,程序只能通过Enum的类方法来创建实例。类方法如下所示。

EnumSet allOf(Class elementType)    //创建一个包含指定枚举类里所有枚举值的EnumSet集合。
EnumSet complementOf(EnumSet s)    //创建一个其元素类型和指定EnumSet里元素类型相同的EnumSet集合,新的

猜你喜欢

转载自blog.csdn.net/sinat_38393872/article/details/91128302