集合框架复习总结(三)

之前复习过了集合框架的Collection的内容和List的内容

Collection:http://blog.csdn.net/m0_38012174/article/details/77488041

List:http://blog.csdn.net/m0_38012174/article/details/77712803


下面我复习完了Set的内容,下面开始总结Set的相关内容:

一,简单了解Set接口的概述 

(1)、 set接口也是Collection的一种扩展,与List不同的是,在Set中的对象元素不能重复,也就是说你不能把同样的东西两次放入同一个Set容器中。它的常用具体实现有HashSet和TreeSet类。

1、HashSet能快速定位一个元素,但是你放到HashSet中的对象需要实现hashCode()方法,它使用了的哈希码的算法。

2、TreeSet则将放入其中的元素按序存放,这就要求你放入其中的对象是可排序的,这就用到了集合框架提供的另外两个实用类Comparable和Comparator(比较器)。

3、一个类是可排序的,它就应该实现Comparable接口。有时多个类具有相同的排序算法,那就不需要在每分别重复定义相同的排序算法,只要实现Comparator接口即可。


(2)、集合框架中还有两个很实用的公用类:Collections和Arrays。

1、Collections提供了对一个Collection容器进行诸如排序、复制、查找和填充等一些非常有用的方法。

2、Arrays则是对一个数组进行类似的操作。

加入Set的每个元素必须是唯一的;否则,Set是不会把它加进去的。要想加进Set,Object必须定义equals(),这样才能标明对象的唯一性。Set的接口和Collection的一摸一样。Set的接口不保证它会用哪种顺序来存储元素。

1、HashSet*:为优化查询速度而设计的Set。要放进HashSet里面的Object还得定义hashCode()。
2、TreeSet:是一个有序的Set,其底层是一颗树。这样你就能从Set里面提取一个有序序列了。
3、LinkedHashSet(JDK 1.4):一个在内部使用链表的Set,既有HashSet的查询速度,又能保存元素被加进去的顺序(插入顺序)。用Iterator遍历Set的时候,它是按插入顺序进行访问的。

二、HashSet的基本用法

HashSet<String> hs = new HashSet<String>();		//创建HashSet对象
boolean b1 = hs.add("a");
boolean b2 = hs.add("a");				//当向set集合中存储重复元素的时候返回为false
hs.add("b");
hs.add("c");
hs.add("d");
System.out.println(hs);					//HashSet的继承体系中有重写toString方法
System.out.println(b1);
System.out.println(b2);

for (String string : hs) {				
	System.out.println(string);
}


得出的结果是:

[d, b, c, a]
true
false
d
b
c
a
[]


上面的代码基本可以概括出:

HashSet是无序存储的,不能重复储存,可以遍历。

得到HashSet的长度也是size()。

那么,存储的是字符串,数字等之类时,系统会知道存储的对象是否是重复的,那么,如果存储的是自定义类对象,又怎样区分是否已经存在呢?

举个简单的例子:

下面有一个Person类

public class Person {
	public String name;
	public int age;
	
	public Person() {
		super();
	}
	 
	public Person(String name,int age){
		this.name = name;
		this.age = age;
	}	
}


然后测试存入Person对象进HashSet

HashSet<Person> hs = new HashSet<Person>();
hs.add(new Person("小明", 11));
hs.add(new Person("小明", 11));
for (Person ps : hs) {				
	System.out.println(ps);
}

可是得出的结果是:

demo.Person@152b6651
demo.Person@6bbc4459

1、为什么呢?

答:因为没创建一个对象,他的Hashcode是不同的,相当于没创建一个新的自定义类对象,计算机会分配一个新的Hashcode给那个对象,所以就算创建的对象咯面的属性是相同的,也可以存储进HashSet,而不算重复。所以如果要不存储属性不同的对象进HashSet的话,可以重写hashCode()和equals()方法。


2、那么它的原理是什么?

(1)我们使用Set集合都是需要去掉重复元素的, 如果在存储的时候逐个equals()比较, 效率较低,哈希算法提高了去重复的效率, 降低了使用equals()方法的次数

(2)当HashSet调用add()方法存储对象的时候, 先调用对象的hashCode()方法得到一个哈希值, 然后在集合中查找是否有哈希值相同的对象

(3)如果没有哈希值相同的对象就直接存入集合

(4)如果有哈希值相同的对象, 就和哈希值相同的对象逐个进行equals()比较,比较结果为false就存入, true则不存

hashCode(): 属性相同的对象返回值必须相同, 属性不同的返回值尽量不同(提高效率)
equals(): 属性相同返回true, 属性不同返回false,返回false的时候存储


所以,我们可以重写hashCode()方法和equals()方法,我直接在eclipse中重写:



得到的Person类是:

public class Person {
	public String name;
	public int age;
	
	public Person() {
		super();
	}
	 
	public Person(String name,int age){
		this.name = name;
		this.age = age;
	}	
	
	public String getName() {
		return name;
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Person other = (Person) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
那么原理是什么?我参考了黑马的Java视频的源码得到下面的结果:

hashCode():

	/*
	 * 为什么是31?
	 * 1,31是一个质数,质数是能被1和自己本身整除的数
	 * 2,31这个数既不大也不小
	 * 3,31这个数好算,2的五次方-1,2向左移动5位
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}


equals():

	@Override
	public boolean equals(Object obj) {
		if (this == obj)					//调用的对象和传入的对象是同一个对象
			return true;					//直接返回true
		if (obj == null)					//传入的对象为null
			return false;					//返回false
		if (getClass() != obj.getClass())			//判断两个对象对应的字节码文件是否是同一个字节码
			return false;					//如果不是直接返回false
		Person other = (Person) obj;				//向下转型
		if (age != other.age)					//调用对象的年龄不等于传入对象的年龄
			return false;					//返回false
		if (name == null) {					//调用对象的姓名为null
			if (other.name != null)				//传入对象的姓名不为null
				return false;				//返回false
		} else if (!name.equals(other.name))			//调用对象的姓名不等于传入对象的姓名
			return false;					//返回false
		return true;						//返回true
	}

重写了方法过后再执行下面的代码:

public static void main(String[] args) {
		// TODO Auto-generated method stub
		HashSet<Person> hs = new HashSet<Person>();
		hs.add(new Person("小明", 11));
		hs.add(new Person("小明", 11));
		for (Person ps : hs) {				
			System.out.println(ps);
		}
	}
结果就变成了:

草稿.Person@b90f5


三、LinkedHashSet的基本用法

什么是LinkedHashSet呢?

底层是链表实现的,是set集合中唯一一个能保证怎么存就怎么取的集合对象,它是HashSet的子类,因为是HashSet的子类,所以也是保证元素唯一的,与HashSet的原理一样

例子:

public static void main(String[] args) {
	LinkedHashSet<String> lhs = new LinkedHashSet<String>();
	lhs.add("a");
	lhs.add("a");
	lhs.add("a");
	lhs.add("a");
	lhs.add("b");
	lhs.add("c");
	lhs.add("d");
	
	System.out.println(lhs);
}
得出的结果是:

[a, b, c, d]


证明LinkedHashSet也是元素唯一存储的,但是是按顺序的。



四、TreeSet的基本用法

TreeSet使用红黑树的数据结构来存储集合元素

TreeSet会自动排序,如果存放的对象不能排序则会报错,所以存放的对象必须指定排序规则。排序规则包括自然排序和客户排序。如果希望TreeSet能正常运转,只能添加同一种类型对象。

当把一个对象加入TreeSet集合时,TreeSet调用该对象的compareTo(Object obj)方法与容器中的其他对象比较大小,然后根据红黑树结构找到它的存储位置,如果这两个对象通过compareTo(Object obj)方法比较相等,新对象将无法添加到TreeSet集合中。


例子1

TreeSet<Integer> ts = new TreeSet<Integer>();
ts.add(3);
ts.add(1);
ts.add(1);
ts.add(2);
ts.add(2);
ts.add(3);
ts.add(3);
	
System.out.println(ts);


得出的结果是:

[1, 2, 3]

证明TreeSet是自动排序的。


比较器排序例子:

public static void main(String[] args) {
	TreeSet<Person> ts = new TreeSet<Person>(new Comparator<Person>() {
		@Override
		public int compare(Person o1, Person o2) {
			int num = o1.getName().compareTo(o2.getName());             
			return num == 0 ? o1.getAge() - o2.getAge() : num;            //比较器的用法
		}
	});
	ts.add(new Person("Jack", 22));
	ts.add(new Person("Tom", 27));
	ts.add(new Person("Marry", 25));
	ts.add(new Person("Jack", 22));
	System.out.println(ts);
}

得到的结果是:

[Person [name=Jack, age=22], Person [name=Marry, age=25], Person [name=Tom, age=27]]

这里的比较器是先比较名字,再比较年龄,Person类是使用上面提到的。



TreeSet是用来排序的, 可以指定一个顺序, 对象存入之后会按照指定的顺序排列


a.自然顺序(Comparable)
 TreeSet类的add()方法中会把存入的对象提升为Comparable类型
 调用对象的compareTo()方法和集合中的对象比较
 根据compareTo()方法返回的结果进行存储
b.比较器顺序(Comparator)
 创建TreeSet的时候可以制定 一个Comparator
 如果传入了Comparator的子类对象, 那么TreeSet就会按照比较器中的顺序排序
 add()方法内部会自动调用Comparator接口中compare()方法排序
 调用的对象是compare方法的第一个参数,集合中的对象是compare方法的第二个参数
 c.两种方式的区别
 TreeSet构造函数什么都不传, 默认按照类中Comparable的顺序(没有就报错ClassCastException)
 TreeSet如果传入Comparator, 就优先按照Comparator


Comparator的简介:http://blog.csdn.net/u012250875/article/details/55126531

Comparable的简介:http://www.cnblogs.com/gnuhpc/archive/2012/12/17/2822251.html

它们的区别:http://blog.csdn.net/mageshuai/article/details/3849143


参考了上面的博客,得出他们得区别。好了,Set的部分复习完了,接下来就是复习生下来的Map部分了。

猜你喜欢

转载自blog.csdn.net/m0_38012174/article/details/77718473
今日推荐