集合02
Set
1)概述
Set是无序的(存储和取出不一致),集合中的元素不可重复
Set接口的子实现类:hashSet:不保证set的迭代顺序,特别是它不保证该顺序恒久不变
import java.util.HashSet; import java.util.Set; public class Demo01 { public static void main(String[] args) { //创建set集合对象 Set<String> set = new HashSet<String>(); //给集合中添加元素 set.add("hello"); set.add("java"); set.add("world"); set.add("world"); set.add("java"); set.add("javaee"); set.add(null); //增强for遍历 for (String s : set) { System.out.println(s); } } }
2)HashSet
HashSet的add()方法,底层依赖于HashMap(属于Map接口的实现类)的put(Key,Value)
看原码
ineterface Collection{} interface Set extends Collection{} class HashSet implements Set{ private static final Object PRESENT = new Object(); //走无参构造方法,创建了一个HashMap对象 public HashSet() { map = new HashMap<>(); } public boolean add(E e) {//传递String类型的元素: e= hello world return map.put(e, PRESENT)==null; } } public class HashMap implements Map<K,V>{//k = hello ,world public V put(K key, V value) { if (table == EMPTY_TABLE) {//table:哈希表 ;判断哈希表是否为空! inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); //调用这个方法:其实调用了一个HashCode()方法 int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; //k=hello,world,java,world //依赖于Object中的一个equals() if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {//key=hello,world,java,world V oldValue = e.value;//Vaule oldValue = e=key =传入的元素 //world(这个元素Hashcode) //最后一次传入了一个world:hasCode() e.value = value; e.recordAccess(this); return oldValue;// 返回是以前的值;world只打印一次! } } modCount++; addEntry(hash, key, value, i); return null; } transient int hashSeed = 0; final int hash(Object k) {//k=hello,world,java int h = hashSeed; if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } //位^操作 //已经重写了Object类的hashcode()方法 h ^= k.hashCode();//每个字符串元素传递过来,hashCode(); // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } }
首先判断它们的hash(),底层是调用HashCode()方法,已经重写了
如果HashCode():哈希码值相同,再比较它们的内容是否相同(equals:底层重写了Object的equals())
public class Demo03 { public static void main(String[] args) { System.out.println("hello".hashCode());// 99162322 System.out.println("hello".hashCode());// 99162322 System.out.println("java".hashCode());// 3254818 System.out.println("world".hashCode());// 113318802 } }
3)HashSet存储自定义对象
还是调用前面的Student类(需要重写hashCode()和equals())
// Student public class Student { private String name; private int age; public Student() { super(); // TODO Auto-generated constructor stub } public Student(String name, int age) { super(); 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; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + "]"; } @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; Student other = (Student) 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; } }
测试类
import java.util.HashSet; public class Demo02 { public static void main(String[] args) { // 创建HashSet集合对象 HashSet<Student> hs = new HashSet<Student>(); // 创建学生对象 Student s1 = new Student("刘备", 30); Student s2 = new Student("刘备", 38); Student s3 = new Student("刘备", 30); Student s4 = new Student("关羽", 28); Student s5 = new Student("关羽", 29); Student s6 = new Student("张飞", 30); Student s7 = new Student("张飞", 30); Student s8 = new Student("赵云", 25); // 将学生对象添加到集合中 hs.add(s1); hs.add(s2); hs.add(s3); hs.add(s4); hs.add(s5); hs.add(s6); hs.add(s7); hs.add(s8); // 遍历 for (Student s : hs) { System.out.println(s); } } }
4)LinkedHashSet
底层数据结构是由哈希表和链表实现,与hashSet(无序性)有点区别
哈希表:保证元素的唯一性
链表:元素的存储和取出一致(有序性)
import java.util.LinkedHashSet; public class Demo04 { public static void main(String[] args) { // 创建LinkedHashSet集合对象 LinkedHashSet<String> link = new LinkedHashSet<String>(); // 添加元素 link.add("hello"); link.add("world"); link.add("java"); link.add("java"); link.add("hello"); link.add("world"); // 遍历 for (String s : link) { System.out.println(s); } } }
5)TreeSet集合中有两种排序(重点)
保证元素的唯一性并且排序
自然排序
比较器排序
TreeMap<K,V>---><String, Integer>/<Integer, String>基于红黑树
import java.util.TreeSet; public class Demo05 { public static void main(String[] args) { // 创建TreeSet集合对象 TreeSet<Integer> ts = new TreeSet<Integer>(); // 给集合中添加元素 ts.add(20); ts.add(17); ts.add(23); ts.add(18); ts.add(22); ts.add(24); ts.add(19); ts.add(18); ts.add(24); // 使用增强for进行遍历 for (Integer i : ts) { System.out.print(i + " "); } } }
为什么能保证元素的唯一性,排序到底怎么选择两种排序?
6)用TreeSet来存储自定义对象(自然排序)
自定义的对象一定要实现Compareable接口
要给排序指定一个主要条件:按照年龄大小
有个问题:如果对象的成员变量值一样(年龄一样),就认为是同一个变量
所以:要有附加条件:如果年龄一样,比较姓名
测试类// 要定义的类中实现Comparable接口:进行自然排序 public class Student implements Comparable<Student> { private String name; private int age; public Student() { super(); } public Student(String name, int age) { super(); 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; } @Override public String toString() { return name + "----" + age; } // 重写Comparable接口中的compareTo()方法,自己设定排序规则 @Override public int compareTo(Student s) { /* * 返回值是要按照刚才的主要条件进行实现 * 要按照年龄进行冲小到大排序:主要条件(需求中明确给出主要条件) * 你自己要分析附件的次要条件 * 用当前对象的年龄和传入的时间年龄进行比较 */ int num = this.age - s.age; // 从小到大 /* * num若为0,表示相等; * num若为正,表示this < age; * num若为负,表示大this > age; */ /* * 当年龄相等,再判断姓名是否一样 * 使用三元运算符 */ int num2 = (num == 0) ? this.name.compareTo(s.name) : num; // 返回 return num2; } }
// 测试类 public class Demo01 { public static void main(String[] args) { // 创建TreeSet集合对象 TreeSet<Student> ts = new TreeSet<Student>(); // 创建8个学生对象 Student s1 = new Student("zhangguorong", 38); Student s2 = new Student("gaoyuanyuan", 27); Student s3 = new Student("wanglihong", 29); Student s4 = new Student("liushishi", 28); Student s5 = new Student("gaoyuanyuan", 27); Student s6 = new Student("zhangsan", 27); Student s7 = new Student("linqingxia", 49); Student s8 = new Student("wuqilong", 38); // 将学生对象存储到集合中 // java.lang.ClassCastException: org.westos_treeset.Student cannot be cast to // java.lang.Comparable /** * 类转换异常 * 当前使用的集合是TreeSet集合:使用元素的自然顺序对元素进行排序(默认),当前并且没有实现Compareable接口进行自然排序 * 他底层要依赖于:compareTo:用此对象与指定对象进行比较 */ ts.add(s1); ts.add(s2); ts.add(s3); ts.add(s4); ts.add(s5); ts.add(s6); ts.add(s7); ts.add(s8); // 增强for变量 for (Student s : ts) { System.out.println(s); } } }
7)TreeSet集合里面的add()的原码分析
public interface Collection{ } public interface Set extends Collection{ } public class TreeMap(双列集合)<K,V>extends AbstractMap<K,V> implements NavigableMap<K,V>{ public V put(K key, V value) { Entry<K,V> t = root; //通过键值对对象创建了一个根节点 if (t == null) {//现在的代码有根节点有元素 compare(key, key); // type (and possibly null) check root = new Entry<>(key, value, null); size = 1; modCount++; return null; } int cmp; Entry<K,V> parent; // split comparator and comparable paths Comparator<? super K> cpr = comparator;//创建Comparator对象(本身就是TreeSet集合的比较排序) if (cpr != null) { do { parent = t; cmp = cpr.compare(key, t.key); //(s1,s2) if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } else { if (key == null)//里面的元素是空的 throw new NullPointerException();//对象是空的,那么抛出一个空指针异常! Comparable<? super K> k = (Comparable<? super K>) key;// 创建一个Comparable对象,实现自然排序 do { parent = t;//生成一个根节点(存储第一个元素的时候:作为根节点):parent=entry<K,V>=t=root cmp = k.compareTo(t.key);//比较此对象与指定对象的顺序:根节点值和后面存储的元素进行比较 if (cmp < 0) t = t.left;//小了,作为左孩子 else if (cmp > 0) t = t.right;//大了,作为孩子 else return t.setValue(value);//不添加到集合中 } while (t != null);//给根节点做了一个非空判断 } Entry<K,V> e = new Entry<>(key, value, parent); //取出元素 if (cmp < 0) parent.left = e;//前序遍历(左边取出) else parent.right = e;//后续遍历右边取出 fixAfterInsertion(e); size++; modCount++; return null; } } class TreeSet implements Set{ //成员变量 private transient NavigableMap<E,Object> m; private static final Object PRESENT = new Object(); public boolean add(E e) { return m.put(e, PRESENT)==null; } } ts.add(20) ; ts.add(17) ; ts.add(23) ; ts.add(18) ; ts.add(22) ; ts.add(24) ; ts.add(19) ; ts.add(18) ; ts.add(24) ; //17 18 19 20 22 23 24 总结: TreeSet集合会默认的进行自然排序,最终底层实现的是Compareable接口!
8)使用TreeSet集合存储自定义对象,并遍历
要求:按照姓名的长度进行排序(主要条件)
测试类// 要定义的类中实现Comparable接口:进行自然排序 public class Student implements Comparable<Student> { private String name; private int age; public Student() { super(); } public Student(String name, int age) { super(); 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; } @Override public String toString() { return name + "----" + age; } @Override public int compareTo(Student s) { // 逻辑:主要条件:姓名的长度 int num1 = this.name.length() - s.name.length(); // 次要条件:长度一样,姓名的内容不一样 int num2 = (num1 == 0) ? this.name.compareTo(s.name) : num1; // 次要条件:如果姓名内容一样,年龄有可能不一样 int num3 = (num2 == 0) ? this.age - s.age : num2; return num3; } }
import java.util.TreeSet; // 测试类 public class Demo01 { public static void main(String[] args) { // 创建TreeSet集合对象 TreeSet<Student> ts = new TreeSet<Student>(); // 创建8个学生对象 Student s1 = new Student("zhangguorong", 38); Student s2 = new Student("gaoyuanyuan", 27); Student s3 = new Student("wanglihong", 29); Student s4 = new Student("liushishi", 28); Student s5 = new Student("gaoyuanyuan", 27); Student s6 = new Student("zhangsan", 27); Student s7 = new Student("linqingxia", 49); Student s8 = new Student("wuqilong", 38); // 给集合中添加元素 ts.add(s1); ts.add(s2); ts.add(s3); ts.add(s4); ts.add(s5); ts.add(s6); ts.add(s7); ts.add(s8); // 增强for变量 for (Student s : ts) { System.out.println(s); } } }
9)使用比较器排序,方法一:子实现类
public TreeSet(Comparator<T> comparator)
构造一个新的空TreeSet,它根据指定比较器进行排序
形式参数是个接口类型,所以需要该类的子实现类对象(自定义一个接口子实现类)
子实现类// 学生类 public class Student { private String name; private int age; public Student() { super(); // TODO Auto-generated constructor stub } public Student(String name, int age) { super(); 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; } @Override public String toString() { return name + "---" + age; } }
import java.util.Comparator; // 自定义一个类:是Comparator接口的子实现类 public class MyComparator implements Comparator<Student> { @Override public int compare(Student s1, Student s2) { // 主要条件是姓名的长度 int num1 = s1.getName().length() - s2.getName().length(); // 姓名内容 int num2 = (num1 == 0) ? s1.getName().compareTo(s2.getName()) : num1; // 年龄 int num3 = (num2 == 0) ? s1.getAge() - s2.getAge() : num2; return num3; } }
测试类
import java.util.TreeSet; public class Demo01 { public static void main(String[] args) { // 创建TreeSet集合对象 TreeSet<Student> ts = new TreeSet<Student>(new MyComparator()); // 创建学生对象 Student s1 = new Student("gaoyuanyuan", 27); Student s2 = new Student("zhangguorong", 29); Student s3 = new Student("wanglihong", 27); Student s4 = new Student("liushishi", 27); Student s5 = new Student("feqingyan", 38); Student s6 = new Student("linqingxia", 38); Student s7 = new Student("gaoyuanyuan", 26); Student s8 = new Student("gaoyuanyuan", 27); // 给集合中添加元素 ts.add(s1); ts.add(s2); ts.add(s3); ts.add(s4); ts.add(s5); ts.add(s6); ts.add(s7); ts.add(s8); //增强for遍历 for (Student s : ts) { System.out.println(s); } } }
10)使用比较器排序,方法二:匿名内部类(开发中常用)
import java.util.Comparator; import java.util.TreeSet; public class Demo01 { public static void main(String[] args) { // 创建TreeSet集合对象 TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() { @Override public int compare(Student s1, Student s2) { // 按照姓名长度比较 int num1 = s2.getName().length() - s1.getName().length(); // 长度一样,比较内容 int num2 = (num1 == 0) ? s2.getName().compareTo(s1.getName()) : num1; // 内容一样,比较年龄 int num3 = (num2 == 0) ? s2.getAge() - s1.getAge() : num2; return num3; } }); // 创建学生对象 Student s1 = new Student("gaoyuanyuan", 27); Student s2 = new Student("zhangguorong", 29); Student s3 = new Student("wanglihong", 27); Student s4 = new Student("liushishi", 27); Student s5 = new Student("feqingyan", 38); Student s6 = new Student("linqingxia", 38); Student s7 = new Student("gaoyuanyuan", 26); Student s8 = new Student("gaoyuanyuan", 27); // 给集合中添加元素 ts.add(s1); ts.add(s2); ts.add(s3); ts.add(s4); ts.add(s5); ts.add(s6); ts.add(s7); ts.add(s8); // 增强for遍历 for (Student s : ts) { System.out.println(s); } } }
11)练习
键盘录入5个学生信息(姓名,语文成绩,数学成绩,英语成绩),按照总分从高到低输出到控制台
分析:
1)创建学生类,里面提供成员变量,姓名,语文成绩,数学成绩,英语成绩
2)创建TreeSet集合对象,使用有参构造方式(比较器排序:Coparator comparator)
3)开始录入数据写一个for循环,录入5个学生姓名,成绩等等..
4)遍历,输出
测试类// 学生类 public class Student { private String name; private int chinese; private int math; private int english; public Student() { super(); // TODO Auto-generated constructor stub } public Student(String name, int chinese, int math, int english) { super(); this.name = name; this.chinese = chinese; this.math = math; this.english = english; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getChinese() { return chinese; } public void setChinese(int chinese) { this.chinese = chinese; } public int getMath() { return math; } public void setMath(int math) { this.math = math; } public int getEnglish() { return english; } public void setEnglish(int english) { this.english = english; } public int getSum() { return getChinese() + getMath() + getEnglish(); } @Override public String toString() { return name + "\t" + chinese + "\t" + math + "\t" + english + "\t" + getSum(); } }
import java.util.Comparator; import java.util.Scanner; import java.util.TreeSet; public class Demo01 { public static void main(String[] args) { // 创建TreeSet学生对象 TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() { @Override public int compare(Student s1, Student s2) { // 主要条件:总分从高到底 int num1 = s2.getSum() - s1.getSum(); // 总分相同比语文 int num2 = (num1 == 0) ? s2.getChinese() - s1.getChinese() : num1; // 语文相同比数学 int num3 = (num2 == 0) ? s2.getMath() - s1.getMath() : num2; // 数学相同比英语(这步可以省略,前面三个都相同,这个肯定相同) // 最后比姓名 int num4 = (num3 == 0) ? s2.getName().compareTo(s1.getName()) : num3; return num4; } }); // 开始录入数据 System.out.println("开始录入学生成绩:"); System.out.println("-------------------------------------"); // for循环,明确次数 for (int i = 0; i < 5; i++) { // 键盘录入对象 Scanner sc = new Scanner(System.in); System.out.println("请输入第" + (i + 1) + "个学生的信息:"); System.out.println("姓名:"); String name = sc.next(); // 之所以用sc.next(),不用nextLine():nextLine()后面再输入nextInt()类型会报错 System.out.println("语文成绩:"); int chinese = sc.nextInt(); System.out.println("数学成绩:"); int math = sc.nextInt(); System.out.println("英语成绩:"); int english = sc.nextInt(); // 创建Student对象 Student s = new Student(name, chinese, math, english); // 将学生对象添加到集合中 ts.add(s); System.out.println("-------------------------------------"); } System.out.println("成绩录入完毕!"); System.out.println("姓名\t语文\t数学\t英语\t总分"); // 使用增强for遍历 for (Student s : ts) { System.out.println(s); } } }