之前复习过了集合框架的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的接口不保证它会用哪种顺序来存储元素。
二、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;
}
}
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部分了。