1. Set集合类型
Set :
- 特点:无序 唯一(不重复)
HashSet :
- 采用Hashtable哈希表存储结构(神奇的结构)
- 优点:添加速度快 查询速度快 删除速度快
- 缺点:无序
LinkedHashSet :
- 采用哈希表存储结构,同时使用链表维护次序
- 有序(添加顺序)
TreeSet
采用二叉树(红黑树)的存储结构
- 优点:有序 查询速度比List快(按照内容查询)
- 缺点:查询速度没有HashSet快
【示例1】使用各种Set集合存储课程名称
public class TestSet1 {
public static void main(String[] args) {
//创建一个集合set对象
//Set<String> set = new HashSet<String>();
//Set<String> set = new LinkedHashSet<String>();
Set<String> set = new TreeSet<String>();
//添加多个课程
set.add(new String("Java"));
set.add("Oracle");
set.add("HTML");
set.add(new String("Java"));
//输出课程
System.out.println(set.size());
System.out.println(set);
//不可以使用for循环遍历set
for(int i=0;i<set.size();i++){
//set.get(i);
}
//支持增强的for循环
//支持Iterator
Iterator<String> it = set.iterator();
while(it.hasNext()){
//
System.out.println(it.next());
}
}
}
- HashSet 哈希表 唯一 无序
- LinkedHashSet 哈希表+链表 唯一 有序(添加顺序)
- TreeSet 红黑树 一种二叉平衡树 唯一 有序(自然顺序)
- List针对Collection增加了一些关于索引位置操作的方法 get(i) add(i,elem),remove(i),set(i,elem)
- Set是无序的,不可能提供关于索引位置操作的方法,set针对Collection没有增加任何方法
- List的遍历方式:for循环、for-each循环、Iterator迭代器、流式编程forEach
- Set的遍历方式: for-each循环、Iterator迭代器、流式编程forEach
【示例2】使用各种Set存储多个自定义学生信息
public class TestSet2 {
public static void main(String[] args) {
//创建一个集合set对象
//Set<Student> set = new TreeSet<Student>();
//Set<Student> set = new HashSet<Student>();
Set<Student> set = new LinkedHashSet<Student>();
//添加多个学生
Student stu2 = new Student(2, "lisi", 23, 98);
Student stu3 = new Student(3, "wangwu", 22, 87);
Student stu1 = new Student(1, "zhangsan", 23, 90);
Student stu4 = new Student(1, "zhangsan", 23, 90);
set.add(stu1);
set.add(stu2);
set.add(stu3);
set.add(stu4);
//输出学生
System.out.println(set.size());
System.out.println(set);
}
}
问题1:HashSet、LinkedHashSet :为什么String有重复,会保持唯一;为什么Student有重复,不会保持唯一。
问题2:TreeSet 为什么String可以添加,而Student就不让添加到TreeSet中呢? 而是抛出异常:
java.lang.ClassCastException: com.bjsxt.entity.Student cannot be cast to java.lang.Comparable
思考:String是系统类,Student是自定义类,应该是String已经做了某些事情,但是Student没有做
解答1:HashSet、LinkedHashSet 需要Student实现hashCode()和equals()
解答2:TreeSet 需要Student实现Comparable接口并指定比较的规则
【示例3】让各种Set可以可以存储自定义类型的对象Student
public class Student implements Comparable<Student>{
private int sno;
private String name;
private int age;
private double score;
@Override
public int compareTo(Student o) {
//return this.sno - o.sno;
//return o.sno - this.sno;
return -(this.sno - o.sno);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (sno != student.sno) return false;
if (age != student.age) return false;
if (Double.compare(student.score, score) != 0) return false;
return name != null ? name.equals(student.name) :
student.name == null;
}
@Override
public int hashCode() {
int result;
long temp;
result = sno;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + age;
temp = Double.doubleToLongBits(score);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
}
2.比较器Comparator的作用和使用
问题:内部比较器Comparable只有一个,如果希望指定多种比较的规则,怎么办?
解决:可以定义多个外部比较器,定义额外的类实现Comparator接口
<创建Person类以及 比较器>
package comparable;
public class Person implements Comparable<Person> {
private String name;
private double score;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person(String name, double score, int age) {
this.name = name;
this.score = score;
this.age = age;
}
public Person() {
}
@Override
public int compareTo(Person o) {
if (this.age - o.age == 0) {
return Integer.valueOf((int) (-(this.score - o.score)));
}
return Integer.valueOf(this.age - o.age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", score=" + score +
", age=" + age +
'}';
}
}
《建立Person类型的TreeSet,进行比较》
package comparable;
import java.util.TreeSet;
public class Set {
public static void main(String[] args) {
Person p1 = new Person("李白", 99.3, 33);
Person p2 = new Person("李黑", 93.3, 11);
Person p3 = new Person("啊白", 91.3, 33);
Person p4 = new Person("啊2", 94.3, 41);
TreeSet treeSet = new TreeSet();
treeSet.add(p1);
treeSet.add(p2);
treeSet.add(p3);
treeSet.add(p4);
for (Object set : treeSet) {
System.out.println("set = " + set);
}
}
}
3.哈希表的原理
引入
在无序数组中按照内容查找,效率低下,时间复杂度是O(n)
在有序数组中按照内容查找,可以使用折半查找,时间复杂度O(log2n)
在二叉平衡树中按照内容查找,时间复杂度O(log2n)
在数组中按照索引查找,不进行比较和计数,直接计算得到,效率最高,时间复杂度O(1)
问题:按照内容查找,能否也不进行比较,而是通过计算得到地址,实现类似数组按照索引查询的高效率呢O(1)
用哈希表来实现
前面查找方法共同特点:通过将关键字值与给定值比较,来确定位置。效率取决比较次数。
理想的方法是:不需要比较,根据给定值能直接定位记录的存储位置。
这样,需要在记录的存储位置与该记录的关键字之间建立一种确定的对应关系,使每个记录的关键字与一个存储位置相对应。
哈希表的结构和特点
hashtable 也叫散列表;特点:快 很快 神奇的快
结构:结构有多种。最流行、最容易理解:顺序表+链表
主结构:顺序表,每个顺序表的节点在单独引出一个链表
哈希表是如何添加数据的
计算哈希 码(调用hashCode(),结果是一个int值,整数的哈希码取自身即可)
- 计算在哈希表中的存储位置 y=k(x)=x%11
x:哈希码 k(x) 函数y:在哈希表中的存储位置
- 存入哈希表
- 情况1:一次添加成功
- 情况2:多次添加成功(出现了冲突,调用equals()和对应链表的元素进行比较,比较到最后,结果都是false,创建新节点,存储数据,并加入链表末尾)
- 情况3:不添加(出现了冲突,调用equals()和对应链表的元素进行比较, 经过一次或者多次比较后,结果是true,表明重复,不添加)
结论1:哈希表添加数据快(3步即可,不考虑冲突)
结论2:唯一、无序
哈希表是如何查询数据的
和添加数据的过程是相同的
- 情况1:一次找到 23 86 76
- 情况2:多次找到 67 56 78
- 情况3:找不到 100 200
结论1:哈希表查询数据快
hashCode和equals到底有什么神奇的作用
- hashCode():计算哈希码,是一个整数,根据哈希码可以计算出数据在哈希表中的存储位置
- equals():添加时出现了冲突,需要通过equals进行比较,判断是否相同;查询时也需要使用equals进行比较,判断是否相同
各种类型数据的哈希码应该如何获取 hashCode()
- int 取自身 看Integer的源码
- double 3.14 3.15 3.145 6.567 9.87 取整不可以 看Double的源码
- String java oracle j+a+v+a 将各个字符的编码值相加不可以
abc cba bac a:97 b:98 c:99
abc 1*97+2*98+3*99
cba 1*99+2*98+3*97
- Student 先各个属性的哈希码,进行某些相加相乘的运算
int id String name int age double score;
.如何减少冲突
1)哈希表的长度和表中的记录数的比例--装填因子:
如果Hash表的空间远远大于最后实际存储的记录个数,则造成了很大的空间浪费, 如果选取小了的话,则容易造成冲突。 在实际情况中,一般需要根据最终记录存储个数和关键字的分布特点来确定Hash表的大小。还有一种情况时可能事先不知道最终需要存储的记录个数,则需要动态维护Hash表的容量,此时可能需要重新计算Hash地址。
装填因子=表中的记录数/哈希表的长度, 4/ 16 =0.25 8/ 16=0.5
如果装填因子越小,表明表中还有很多的空单元,则添加发生冲突的可能性越小;而装填因子越大,则发生冲突的可能性就越大,在查找时所耗费的时间就越多。 有相关文献证明当装填因子在0.5左右时候,Hash性能能够达到最优。
因此,一般情况下,装填因子取经验值0.5。
2)哈希函数的选择
直接定址法 平方取中法 折叠法 除留取余法(y = x%11)
3)处理冲突的方法
链地址法 开放地址法 再散列法 建立一个公共溢出区