目录
9.1.3.Set没有对父接口Collection进行功能上扩充
9.2.4.HashSet存储自定义类型元素必须重写哪两个方法,为什么
9.5.1.LinkedHashSet存储结构、存储元素特点、与HashSet区别
9.Set集合
9.1.Set集合
9.1.1.Set集合遍历方式
9.1.2.Set集合特点
9.1.3.Set没有对父接口Collection进行功能上扩充
9.1.4.Set存储元素为什么不重复
1.java.util.Set接口和java.util.List接口一样,同样继承自Collection接口,它与Collection接口中方法基本一致。并没有对Collection接口进行功能上的扩充,只是比Collection接口更加严格。与List接口不同的是,Set接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。
2.set集合有多个子类,主要介绍其中java.util.HashSet和java.utill.LinkedHashSet两个集合。
3.Set集合取出元素的方式:迭代器,增强for;
4.Set接口特点:
1.不允许存储重复元素,子类集合既有有序也有无序的。
2.没有索引,没有带索引的方法,也不能使用普通for循环遍历
5.java.util.Set接口 extends Collection接口
Java.util.HashSet集合 implements Set接口
6.Set接口中的方法和Collection接口中方法一致,不再学习,下面主要学习Set接口的子类(实现类)。
/** * Set集合不允许重复元素的原理: */ public class Demo3_HashSet { public static void main(String[] args) { //创建HashSet集合对象 HashSet<String> set = new HashSet<>(); String s1 = new String("abc"); String s2 = new String("abc"); set.add(s1); set.add(s2); set.add("重地"); set.add("通话"); set.add("abc"); System.out.println(set);//不重复[重地, 通话, abc] } }
1.当new HashSet<>()时在堆内存开辟空间创建集合,HashSet底层是哈希表:数组+链表/红黑树。哈希表横向是数组结构,竖向是链表/红黑树结构。
2.set集合在调用add方法存储元素时会调用元素的hashCode方法和equals方法判断元素是否重复。set.add(s1):add方法会调用s1的hashCode方法,计算字符串"abc"的哈希值为96354,然后在集合中找有没有96354这个哈希值的元素,若没有则将s1存储到集合中。
3.set.add(s2):add方法会调用s2的hashCode方法,计算字符串"abc"的哈希值为96354,然后在集合中找有没有96354这个哈希值的元素,发现有(哈希冲突),s2会调用equals方法和哈希值相同的元素进行比较即s2.equals(s1),若两个元素的哈希值相同并且equals方法返回true,则认定这两个元素相同,就不会把s2存储到集合中。
4.set.add("重地"):add方法会调用“重地”的hashCode方法,计算字符串“重地”哈希值1179395,然后在集合中找有没有1179395这个哈希值元素,若没有就会把"重地"字符串存储到集合中。
5.set.add("通话"):add方法会调用“通话”的hashCode方法,计算字符串“通话”的哈希值为1179395,然后在集合中找有没有1179395这个哈希值的元素,发现有(哈希冲突),“通话”会调用equals方法和哈希值相同的元素进行比较即“通话”.equals("重地"),若两个元素的哈希值相同但equals方法返回 false,则认定这两个元素不相同,就会把"通话"存储到集合中。
9.2.Set接口实现类_HashSet
9.2.1.HashSet存储结构
9.2.2.HashSet特点
9.2.3.HashSet没有对父接口Set进行功能上补充
9.2.4.HashSet存储自定义类型元素必须重写哪两个方法,为什么
1.HashSet特点:
* 1.存储不重复元素
* 2.没有索引,没有带索引的方法,也不能使用普通的for循环遍历
* 3.是一个无序的集合,存储元素和取出元素的顺序有可能不一致。
* 4.底层是一个HashMap哈希表结构(特点是查询速度非常快)
* 5.因为HashSet中没有除Set中方法外的特有方法,所以可以使用多态创建集合对象。因为多态弊端就是看不到子类的特有方法public class Demo1_HashSet { public static void main(String[] args) { //使用多态创建HashSet集合对象 Set<Integer>set=new HashSet<>(); //使用add方法向集合添加元素 set.add(1); set.add(3); set.add(2); set.add(1); //使用迭代器(增强for)遍历set集合 Iterator<Integer> it = set.iterator(); while(it.hasNext()){ Integer next = it.next(); System.out.println(next);//1,2,3(无序,不重复) } } }
2.HashSet存储自定义类型元素:
给HashSet中存放自定义类型的元素时,必须重写对象中hashCode方法和equals方法,建立自己的比较方式才能保证HashSet集合中的对象唯一(元素不重复)。之前使用hashSet存储字符串,Integer等类型元素,他们都是java已经定义好的类,这些类都已经重写了hashCode和equals方法。因此我们使用hashSet存储自定义类型元素,必须重写这两个方法才能保证元素 的唯一性。
* HashSet存储自定义类型元素: * Set集合存储元素唯一(不重复)前提:必须重写hashCode方法和equals方法 * 要求:同名同年龄的人,视为同一个人,只能存储一次。 public class Demo4_HashSet { public static void main(String[] args) { //创建HashSet集合存储Person HashSet<Demo4_Person> set=new HashSet(); Demo4_Person p1 = new Demo4_Person("zhangsan",18); Demo4_Person p2 = new Demo4_Person("zhangsan", 18); Demo4_Person p3 = new Demo4_Person("zhangsan", 19); //p1、p2两个对象哈希值不同 System.out.println(p1.hashCode());//21685669 System.out.println(p2.hashCode());//2133927002 //p1、p2两个对象地址值不同(未重写的equals方法比较的是元素的地址,等价==运算符) System.out.println(p1==p2); System.out.println(p1.equals(p2)); set.add(p1); set.add(p2); set.add(p3); System.out.println(set); /*显然在没有重写hashCode方法和equals方法时,p1,p2虽然内容重复但 * 还是被写进set集合了,违反了set集合的唯一性。hashCode获取的是元素哈希值, 未重写的equals比较的是地址而不是内容,这两个方法未重写导致比较出来的结果都是false, 所以当元素哈希值不同但内容相同时也会被存入集合。 * [Demo4_Person{name='zhangsan', age=19}, * Demo4_Person{name='zhangsan', age=18}, * Demo4_Person{name='zhangsan', age=18}] */ /** * 下面使用Demo4_Person1创建对象,该类重写了hashCode和equals方法 */ HashSet<Demo4_Person1> set1 = new HashSet<>(); Demo4_Person1 q1 = new Demo4_Person1("zhangsan",18); Demo4_Person1 q2 = new Demo4_Person1("zhangsan", 18); Demo4_Person1 q3 = new Demo4_Person1("zhangsan", 19); set1.add(q1); //调用add时会调用hashCode和equals方法进行比较是否重复 set1.add(q2); set1.add(q3); System.out.println(q1.hashCode()); System.out.println(q2.hashCode()); System.out.println(q1==q2);//比较的是地址 System.out.println(q1.equals(q2));//重写equals后比较内容 System.out.println(set1); } }
9.3.哈希值HashCode
* 1.哈希值:
* 哈希值是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑地址,是模拟出来得到的地址,不是数据实际存储的物理地址)。
* 2.Object类中有一个方法hashCode()可以获取对象的哈希值。
* int hashCode():返回该对象的哈希值
* hashCode()方法的源码:public native int hashCode(); native:代表该方法调用的是本地操作系统的方法。
9.4.哈希表
9.4.1.jdk8前后哈希表区别
9.4.2.哈希表特点,为什么查询速度快(哈希表原理)
1.HashSet集合存储数据的结构是:哈希表
2.jdk1.8版本之前与之后,哈希表的区别(或jdk1.8与jdk1.9中哈希表区别)
1.8版本之前:哈希表=数组+链表
1.8版本之后:哈希表=数组+链表+红黑树(提高查询速度,折半查找,一次排除一半)
3.哈希表特点:查询速度快
4.哈希表的原理:
1.数组结构:把元素进行了分组,相同哈希值的元素是一组。
2.链表/红黑树结构:把相同哈希值的元素连接到一起。
3.数组结构初始容量是16,即0-15,当向集合中存储数据时,先计算元素的哈希值(即在数组中存储的位置),比如元素“abc”的哈希值96354,即为这个元素在数组中存储位置。将哈希值存储在数组中,将元素挂到这个索引的下面。元素"重地"的哈希值和"通话"的哈希值都是1179395,称为哈希冲突,将这两个元素都挂到数组中值为1179395的索引下面。这就是为什么哈希表查询非常快,它先是把数据根据哈希值相同进行分组,然后通过链表或红黑树将相同哈希值的元素连接到一起。
4.jdk1.9开始,当链表中的节点元素超过8个,就会将链表结构转化为红黑树结构(红黑树查询非常快)目的是提高查询速度。
9.5.Set接口实现类_LinkedHashSet
9.5.1.LinkedHashSet存储结构、存储元素特点、与HashSet区别
1.在java.util包中找到set接口,set接口的一个实现类HashSet类下有一个子类LinkedHashSet,也就是LinkedHashSet继承了HashSet并实现了Set接口。
2.java.util.LinkedHashSet集合 extends HashSet集合
3.LinkedHashSet集合特点:
* 底层是一个哈希表(数组+链表/红黑树)+链表。多了一条链表(记录元素的存储顺序),保证元素有序.因此它与HashSet集合的区别就在于前者是有序的,后者是无序的,
* 其它特点相同即元素不能重复,元素没有索引。public class Demo5_LinkedHashSet { public static void main(String[] args) { HashSet<String> set = new HashSet<>(); set.add("www"); set.add("abc"); set.add("abc"); set.add("itcast"); System.out.println(set);//[abc, www, itcast]无序,不允许重复 LinkedHashSet<String> linked = new LinkedHashSet<>(); linked.add("www"); linked.add("abc"); linked.add("abc"); linked.add("itcast"); System.out.println(linked);//[www, abc, itcast]有序,不允许重复 } }
9.6.TreeSet
HashSet是散列存储,TreeSet是二叉树有序存储,这个有序不是存储有序而是根据排序规则进行排序,自定义元素需要实现comparable接口重写compartor方法定义自己排序规则。
排序说明
如果现在要是想进行排序的话,则必须在 Person 类中实现 Comparable 接口。
public class Person implements Comparable<Person> {
private String name;
private int age;
public int compareTo(Person per) {
if (this.age > per.age) {
return 1;
} else if (this.age < per.age) {
return -1;
} else {
return 0;
}
}
public Person() {}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "姓名:" + this.name + ",年龄:" + this.age;
}
}
public class TreeSetPersonDemo01 {
public static void main(String[] args) {
Set<Person> all = new TreeSet<Person>();
all.add(new Person("张三", 10));
all.add(new Person("李四", 10));
all.add(new Person("王五", 11));
all.add(new Person("赵六", 12));
all.add(new Person("孙七", 13));
System.out.println(all);
}
}
- [姓名:张三,年龄:10, 姓名:王五,年龄:11, 姓名:赵六,年龄:12, 姓名:孙七,年龄:13]
- 从以上的结果中可以发现,李四没有了。因为李四的年龄和张三的年龄是一样的,所以会被认为是同一个对象。则 此时必须修改 Person 类,如果假设年龄相等的话,按字符串进行排序。
public int compareTo(Person per) {
if (this.age > per.age) {
return 1;
} else if (this.age < per.age) {
return -1;
} else {
return this.name.compareTo(per.name);
}
}
小结:
- 关于 TreeSet 的排序实现,如果是集合中对象是自定义的或者说其他系统定义的类没有实现 Comparable 接口,则不能实现 TreeSet 的排序,会报类型转换(转向 Comparable 接口)错误。
- 换句话说要添加到 TreeSet 集合中的对象的类型必须实现了 Comparable 接口。 不过 TreeSet 的集合因为借用了 Comparable 接口,同时可以去除重复值,而 HashSet 虽然是 Set 接口子类,但是对于没有复写 Object 的 equals 和 hashCode 方法的对象,加入了 HashSet 集合中也是不能去掉重复值的。