JAVA 集合
集合类的特点
提供一种存储空间可变的存储模型,存储的数据容量可以随时发生改变。
集合类体系结构
Collection 集合概述和使用
概述
-
是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素
-
JDK 不提供此接口的任何直接实现,它提供更具体的子接口(如Set和List)实现
常用方法:
实例:
1 Collection<String> c = new ArrayList<>(); 2 3 // boolean add(E e); 4 c.add("hello"); 5 c.add("world"); 6 c.add("java"); 7 8 System.out.println(c); 9 System.out.println(c.size()); 10 11 c.remove("java"); 12 13 System.out.println(c); 14 System.out.println(c.contains("java")); 15 16 System.out.println(c.isEmpty()); 17 18 c.clear(); 19 20 System.out.println(c);
Collection 集合的遍历
Iterator:迭代器,集合的专用遍历方式
-
- Iterator<E> iterator(): 返回此集合中元素的迭代器,通过集合的iterator()方法得到
- 迭代器是通过集合的iterator()方法得到的,所以说它是依赖于集合而存在的
Iterator中的常用方法
- E next():返回迭代中的下一个元素
- boolean hasNext():如果迭代具有更多元素,则返回true
1 public class Iterator1 { 2 public static void main(String[] args) { 3 Collection<String> c = new ArrayList<>(); 4 c.add("hell"); 5 c.add("word"); 6 c.add("java"); 7 8 //Iterator1<E> iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到 9 Iterator<String> it = c.iterator(); 10 11 //boolean hasNext():如果迭代具有更多元素,则返回true 12 while (it.hasNext()){ 13 String i = it.next(); 14 System.out.println(i); 15 } 16 } 17 }
List集合概述和特点
概述
-
- 有序集合(也称为序列),用户可以精确控制列表中每个元素的插入位置。用户可以通过整数索引访问元素,并搜索列表中的元素
- 与Set集合不同,列表允许重复
特点
-
- 有序
- 可重复
1 // List集合 2 List<String> list = new ArrayList<>(); 3 4 list.add("hello"); 5 list.add("world"); 6 list.add("java"); 7 list.add("java"); 8 9 Iterator<String> iterator1 = list.iterator(); 10 11 while (iterator1.hasNext()) { 12 String next = iterator1.next(); 13 System.out.println(next); 14 }
常用方法:
并发修改异常
并发修改异常:
-
- ConcurrentModificationException
产生原因:
-
- 迭代器遍历的过程中,通过集合对象修改了集合中元素的长度,造成了迭代器获取元素中判断预期修改值和实际修改值不一致
- ArrayList的内部类Itr生产迭代器对象时,会定义变量 int expectedModCount = modCount; ,通过add() 方法修改集合时,modCount会+1,迭代器执行next()方法时,会先检查expectedModCount与modCount是否相等,此时不相等,抛出ConcurrentModificationException()异常。
1 public class ArrayList<E> extends AbstractList<E> 2 implements List<E>, RandomAccess, Cloneable, java.io.Serializable 3 { 4 ... 5 6 protected transient int modCount = 0; 7 8 9 public Iterator<E> iterator() { 10 return new Itr(); 11 } 12 13 14 private class Itr implements Iterator<E> { 15 16 ... 17 18 int expectedModCount = modCount; 19 20 ... 21 } 22 23 ... 24 }
- ArrayList的内部类Itr生产迭代器对象时,会定义变量 int expectedModCount = modCount; ,通过add() 方法修改集合时,modCount会+1,迭代器执行next()方法时,会先检查expectedModCount与modCount是否相等,此时不相等,抛出ConcurrentModificationException()异常。
- 迭代器遍历的过程中,通过集合对象修改了集合中元素的长度,造成了迭代器获取元素中判断预期修改值和实际修改值不一致
解决方案:
-
- 用for循环遍历,然后用集合对象做对应的操作即可
1 import java.util.ArrayList; 2 import java.util.Iterator; 3 import java.util.List; 4 5 /*需求: 有一个List集合 里面有三个元素 hello world java 遍历集合,得到每一个元素,看有没有world,如果有,就添加一个javaee 6 7 ConcurrentModificationException:当不允许这样的修改时,可以通过检测到对象的并发修改的方法来抛出此异常 8 实际修改集合的次数不等于预期修改集合的次数 9 */ 10 11 public class ListDemo3 { 12 public static void main(String[] args) { 13 // 创建集合对象 14 List<String> list = new ArrayList<String>(); 15 16 // 添加元素 17 list.add("hello"); 18 list.add("world"); 19 list.add("java"); 20 21 //遍历集合,得到每一个元素,看有没有world,如果有,就添加一个javaee 22 Iterator<String> it = list.iterator(); 23 while (it.hasNext()) { 24 String s = it.next();//【***】 25 System.out.println(s); 26 if (s.equals("world")) { 27 list.add("javaee"); 28 } 29 } 30 /*运行结果: 31 ConcurrentModificationException 32 33 【***】通过迭代器获取元素时,每次都要判断它的预期修改值和实际修改值是否相同 34 35 */ 36 37 //用for循环遍历 38 for (int i = 0; i < list.size(); i++) { 39 String s = list.get(i); 40 if (s.equals("world")) { 41 list.add("javaee"); 42 } 43 } 44 /*运行结果: 45 [hello, world, java, javaee]*/ 46 47 //输出集合对象 48 System.out.println(list); 49 50 } 51 }
ListIterator
ListIterator:列表迭代器
-
- 通过List集合的listIterator()方法得到,所以说它是List集合特有的迭代器
- 用于允许程序员沿任一方向遍历列表的列表迭代器,在迭代期间修改列表,并获得列表中迭代器的当前位置
ListIterator中的常用方法
-
- E next():返回迭代中的下一个元素
- boolean hasNext():如果迭代具有更多元素,则返回true
- E previous():返回列表中的上一个元素
- boolean hasPrevious():如果此列表迭代器在相反方向遍历列表时具有更多元素,则返回true
- void add(E e):将指定的元素插入列表
1 import java.util.ArrayList; 2 import java.util.List; 3 import java.util.ListIterator; 4 5 /* 6 ListIterator:列表迭代器 7 8 通过List集合的listIterator()方法得到,所以说它是List集合特有的迭代器 9 用于允许程序员沿任一方向遍历列表的列表迭代器,在迭代期间修改列表,并获得列表中迭代器的当前位置 10 11 ListIterator中的常用方法 12 13 E next():返回迭代中的下一个元素 14 boolean hasNext():如果迭代具有更多元素,则返回true 15 E previous():返回列表中的上一个元素 16 boolean hasPrevious():如果此列表迭代器在相反方向遍历列表时具有更多元素,则返回true 17 void add(E e):将指定的元素插入列表 18 19 */ 20 21 public class ListIteratorDemo { 22 public static void main(String[] args) { 23 //创建集合对象 24 List<String> list = new ArrayList<String>(); 25 //添加元素 26 list.add("hello"); 27 list.add("world"); 28 list.add("java"); 29 30 //通过List集合的listIterator()方法得到,所以说它是List集合特有的迭代器 31 ListIterator<String> lit = list.listIterator(); 32 //正向遍历 33 while (lit.hasNext()) { 34 String s = lit.next(); 35 System.out.println(s); 36 } 37 System.out.println("-----------"); 38 //E previous():返回列表中的上一个元素 39 //boolean hasPrevious():如果此列表迭代器在相反方向遍历列表时具有更多元素,则返回true 40 //逆向遍历 41 while (lit.hasPrevious()) { 42 String s = lit.previous(); 43 System.out.println(s); 44 } 45 /*运行结果: 46 hello 47 world 48 java 49 ----------- 50 java 51 world 52 hello 53 */ 54 55 //void add(E e):将指定的元素插入列表 56 57 //获取列表迭代器 58 ListIterator<String> lit1 = list.listIterator(); 59 while (lit1.hasNext()) { 60 String s = lit1.next(); 61 if (s.equals("world")) { 62 lit1.add("javaee"); 63 //Itetator里无法实现添加,但是ListIterator可以实现 64 /*public void add(E e) { 65 checkForComodification(); 66 67 try { 68 int i = cursor; 69 ArrayList.this.add(i, e); 70 cursor = i + 1; 71 lastRet = -1; 72 expectedModCount = modCount; // 将修改次数赋值给期待的修改次数,下次检查时不会异常 73 } catch (IndexOutOfBoundsException ex) { 74 throw new ConcurrentModificationException(); 75 } 76 }*/ 77 } 78 } 79 /*运行结果: 80 [hello, world, javaee, java]*/ 81 82 System.out.println(list); 83 84 } 85 }
增强for循环
增强for的目的:简化数组和Collection集合的遍历
-
- 实现Iterator接口的类允许其对象成为增强型for语句的目标
- 它是JDK5之后出现的,其内部原理是一个Iterator迭代器
格式:
1 for(元素数据类型 变量名:数组或者Collection集合){ 2 //在此处使用变量即可,该变量就是元素 3 }
数据结构
数据结构是计算机存储、组织数据的方式。是指相互之间存在一种或多种特定关系的数据元素的集合
通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率
常见数据结构之栈
数据进入栈模型的过程称为:压/进栈
数据离开栈模型的过程称为:弹/出栈
例如:进栈时,是从栈底到栈顶,为:ABCD;出栈时,是从栈顶到栈底,为:DCBA
由此可见:栈是一种数据先进后出的模型
常见数据结构之队列
数据从后端进入队列模型的过程称为:入队列
数据从前端离开队列模型的过程称为:出队列
例如:入队时,是按照ABCD的顺序,从队列最后进入队列的;出队时,是按照ABCD的顺序,从队列前面离开队列的
由此可见:队列是一种数据先进先出的模型
常见数据结构之数组
查询数据通过索引定位,查询任意数据耗时相同,查询速度快
删除数据时,要将原始数据删除,同时后面每个数据前移,删除效率低
添加数据时,添加位置后的每个数后移,再添加元素,添加效率低
由此可见:数组是一种查询快,增删慢的模型
常见数据结构之链表
链表中的每个元素被称为结点
结点的存储位置被称为地址 (结点的地址)
地址里面存储有具体的数据以及下一个结点的地址
头结点里面有一个head和空地址(表示结束)
例如:
- 首先,有一个头结点,(head,空地址)
- 要存储一个数据A,保存在地址11位置
则在11位置上有一个(A,空地址),将头结点的空地址改为11,即(head,11)指向(A,空地址)
- 要存储一个数据B,保存在地址37位置
则在37位置上有一个(B,空地址),将A结点的空地址改为37,即(head,11)指向(A,37)指向(B,空地址)
- 要在数据AB之间添加一个数据C,保存在54位置
则在54位置上有一个(C,空地址),将C结点的空地址改为37,再把A结点的地址值改为54,即(head,11)指向(A,54)指向(C,37)指向(B,空地址)
- 要删除数据A
则把头结点的地址改为54,再删除数据A,即head,54)指向(C,37)指向(B,空地址)
- 查询数据B是否存在
则要从头head开始查询
由此可见:链表是一种增删快,查询慢的模型(对比数组)
List集合子类特点
List集合常用子类:ArrayList,LinkedList
-
- ArrayList:底层数据结构是数组,查询快,增删慢
- LinkedList:底层数据结构是链表,查询慢,增删快
LinkedList集合的特有功能
1 import java.util.LinkedList; 2 3 /* 4 LinkedList集合的特有功能 5 6 public void addFirst(E e) 在该列表开头插入指定的元素 7 public void addLast(E e)将指定的元素追加到此列表的末尾 8 public E getFirst()返回此列表中的第一个元素 9 public E getLast()返回此列表中的最后一个元素 10 public E removeFirst()从此列表中删除并返回第一个元素 11 public E removeLast()从此列表中删除并返回最后一个元素 12 */ 13 public class LinkedListDemo { 14 public static void main(String[] args) { 15 //创建集合对象 16 LinkedList<String> llist = new LinkedList<String>(); 17 18 llist.add("hello"); 19 llist.add("world"); 20 llist.add("java"); 21 22 // public void addFirst(E e) 在该列表开头插入指定的元素 23 // public void addLast(E e)将指定的元素追加到此列表的末尾 24 llist.addFirst("javase"); 25 llist.addLast("javaee"); 26 /*运行结果: 27 [javase, hello, world, java, javaee] 28 */ 29 30 // public E getFirst()返回此列表中的第一个元素 31 // public E getLast()返回此列表中的最后一个元素 32 System.out.println(llist.getFirst()); 33 System.out.println(llist.getLast()); 34 /*运行结果: 35 hello 36 java 37 [hello, world, java] 38 */ 39 40 // public E removeFirst()从此列表中删除并返回第一个元素 41 // public E removeLast()从此列表中删除并返回最后一个元素 42 System.out.println(llist.removeFirst()); 43 System.out.println(llist.removeLast()); 44 System.out.println(llist); 45 /*运行结果: 46 hello 47 java 48 [world] 49 */ 50 51 52 } 53 }
Set集合概述和特点
Set集合特点
-
- 不包含重复元素的集合
- 没有带索引的方法,所以不能使用普通for循环遍历 (使用增强for循环、迭代器遍历)
1 import java.util.HashSet; 2 import java.util.Set; 3 4 /* 5 Set集合特点 6 7 不包含重复元素的集合 8 没有带索引的方法,所以不能使用普通for循环遍历 9 10 HashSet:对集合的迭代顺序不作任何保证 11 12 */ 13 public class SetDemo { 14 public static void main(String[] args) { 15 //创建集合对象 16 //HashSet:对集合的迭代顺序不作任何保证 17 Set<String> set = new HashSet<String>(); 18 19 //添加元素 20 set.add("hello"); 21 set.add("world"); 22 set.add("java"); 23 //不包含重复元素 24 set.add("hello"); 25 26 //遍历 27 for (String s : set) { 28 System.out.println(s); 29 } 30 /*运行结果: 31 world 32 java 33 hello 34 */ 35 36 } 37 }
哈希值
哈希值:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
Object类中有一个方法可以获取对象的哈希值
-
- public int hashCode():返回对象的哈希码值
对象的哈希值特点
-
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的
- 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同
HashSet集合概述和特点
HashSet集合特点
-
- 底层数据结构是哈希表
- 对集合的迭代顺序不作任何保证,也就是说不保证存储组和取出的元素顺序一致
- 没有带索引的方法,所以不能使用普通for循环遍历
- 由于是Set集合,所以是不包含重复元素的集合
1 public class HashSetDemo { 2 public static void main(String[] args) { 3 //创建集合对象 4 HashSet<String> hs = new HashSet<String>(); 5 6 //添加元素 7 hs.add("hello"); 8 hs.add("world"); 9 hs.add("java"); 10 hs.add("world"); 11 //遍历 12 for (String s : hs) { 13 System.out.println(s); 14 } 15 16 } 17 }
HashSet集合保证元素唯一性源码分析
常见数据结构之哈希表
哈希表
-
- JDK8之前,底层采用数组+链表实现,可以说是一个元素为链表的数组
- JDK8之后,在长度比较长的时候,底层实现了优化
HashSet集合存储学生对象并遍历
需求:创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合
要求:学生对象的成员变量值相同,我们就认为是同一个对象
1 import java.util.HashSet; 2 3 /* 4 案例:HashSet集合存储学生对象并遍历 5 需求:创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合 6 要求:学生对象的成员变量值相同,我们就认为是同一个对象 7 8 */ 9 public class HashSetDemo2 { 10 public static void main(String[] args) { 11 //创建HashSet集合对象 12 HashSet<Student> hs = new HashSet<Student>(); 13 14 // 创建学生对象 15 Student s1 = new Student("小白", 12); 16 Student s2 = new Student("小黑", 13); 17 Student s3 = new Student("小红", 11); 18 19 Student s4 = new Student("小红", 11); 20 21 // 把学生添加到集合 22 hs.add(s1); 23 hs.add(s2); 24 hs.add(s3); 25 hs.add(s4); 26 27 //遍历集合 28 for (Student s : hs) { 29 System.out.println(s.getName() + "," + s.getAge()); 30 } 31 32 } 33 }
1 import java.util.Objects; 2 3 //定义学生类 4 public class Student { 5 private String name; 6 private int age; 7 8 public String getName() { 9 return name; 10 } 11 12 public void setName(String name) { 13 this.name = name; 14 } 15 16 public int getAge() { 17 return age; 18 } 19 20 public void setAge(int age) { 21 this.age = age; 22 } 23 24 public Student() { 25 super(); 26 } 27 28 public Student(String name, int age) { 29 super(); 30 this.name = name; 31 this.age = age; 32 } 33 34 //快捷键重写hashCode()和equals() 35 @Override 36 public int hashCode() { 37 return Objects.hash(age, name); 38 } 39 40 @Override 41 public boolean equals(Object obj) { 42 if (this == obj) 43 return true; 44 if (obj == null) 45 return false; 46 if (getClass() != obj.getClass()) 47 return false; 48 Student other = (Student) obj; 49 return age == other.age && Objects.equals(name, other.name); 50 } 51 52 }
使用HashSet存储学生对象时,是通过对象的成员变量值判断是否相同,需重写hashCode、equals 方法。
LinkedHashSet集合概述和特点
LinkedHashSet集合特点:
-
- 哈希表和链表实现的Set接口,具有可预测的迭代次序
- 有链表保证元素有序,也就是说元素的存储和取出顺序是一致的
- 有哈希表保证元素唯一性,也就是说没有重复的元素
TreeSet集合概述和特点
TreeSet集合特点:
- 元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方式取决于构造方法
- TreeSet():根据其元素的自然排序进行排序
- TreeSet(Comparator comparator):根据指定的比较器进行排序
- 没有带索引的方法,所以不能使用普通for循环遍历
- 由于是Set集合,所以不包含重复元素的集合
1 import java.util.TreeSet; 2 3 4 public class TreeSetDemo { 5 public static void main(String[] args) { 6 //创建集合对象 7 TreeSet<Integer> ts = new TreeSet<Integer>();//由于<>里要用引用类型,所以要用int的包装类Integer 8 9 //添加元素 10 ts.add(55); 11 ts.add(22); 12 ts.add(44); 13 ts.add(33); 14 ts.add(11); 15 16 ts.add(11);//不包含重复元素 17 18 //遍历集合 19 for (int i : ts) { 20 System.out.println(i); 21 } 22 /*运行结果:按照自然顺序从小到大排序 23 11 24 22 25 33 26 44 27 55 28 */ 29 30 } 31 }
自然排序Comparable的使用
- 存储学生对象并遍历,创建TreeSet集合使用无参构造方法
- 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
1 //定义学生类 2 public class Student implements Comparable<Student> { 3 private String name; 4 private int age; 5 6 public String getName() { 7 return name; 8 } 9 10 public void setName(String name) { 11 this.name = name; 12 } 13 14 public int getAge() { 15 return age; 16 } 17 18 public void setAge(int age) { 19 this.age = age; 20 } 21 22 public Student() { 23 super(); 24 } 25 26 public Student(String name, int age) { 27 super(); 28 this.name = name; 29 this.age = age; 30 } 31 32 @Override 33 public int compareTo(Student s) { 34 // TODO Auto-generated method stub 35 // return 0; // 相同,不存储 36 // return 1; // 升序37 // return -1; // 降序38 39 //按照年龄从小到大排序 40 int num = this.age - s.age;//这里this表示s2,s表示s1 41 // int num=s.age-this.age;//降序排列 42 43 //年龄相同时,按照姓名的字母顺序排序 44 int num2 = num == 0 ? this.name.compareTo(s.name) : num; // 字符串本来就可以自然排序 45 46 return num2;//可以保证元素的唯一性 47 } 48 49 }
1 import java.util.TreeSet; 2 3 /* 4 自然排序Comparable的使用 5 6 - 存储学生对象并遍历,创建TreeSet集合使用**无参构造方法** 7 - 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序 8 */ 9 10 public class TreeSetDemo2 { 11 public static void main(String[] args) { 12 //创建集合对象 13 TreeSet<Student> ts = new TreeSet<Student>(); 14 15 //创建学生对象 16 Student s1 = new Student("xiaobai", 12); 17 Student s2 = new Student("dabai", 13); 18 Student s3 = new Student("xiaohei", 14); 19 Student s4 = new Student("dahei", 10); 20 21 Student s5 = new Student("xiaohong", 10); 22 Student s6 = new Student("xiaohong", 10);//元素唯一 23 24 //学生添加到集合 25 ts.add(s1); 26 ts.add(s2); 27 ts.add(s3); 28 ts.add(s4); 29 ts.add(s5); 30 ts.add(s6); 31 32 //遍历集合 33 for (Student s : ts) { 34 System.out.println(s.getName() + "," + s.getAge()); 35 } 36 /*运行结果: 37 ClassCastException 38 因为:学生类没有实现Comparable接口 39 */ 40 41 /*运行结果: 42 xiaobai,12 43 因为:此时compareTo()里返回的是0 44 */ 45 46 /*将compareTo()改为return 1 47 运行结果: 48 xiaobai,12 49 dabai,13 50 xiaohei,14 51 dahei,10 52 按照存储的顺序输出 53 */ 54 55 /*将compareTo()改为return -1 56 运行结果: 57 dahei,10 58 xiaohei,14 59 dabai,13 60 xiaobai,12 61 按照存储的顺序逆序输出 62 */ 63 64 } 65 }
结论:
-
- 用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的
- 自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(T o)方法
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
比较器排序Comparator的使用
- 存储学生对象并遍历,创建TreeSet集合使用带参构造方法
- 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
1 //定义学生类 2 public class Student { 3 private String name; 4 private int age; 5 6 public String getName() { 7 return name; 8 } 9 10 public void setName(String name) { 11 this.name = name; 12 } 13 14 public int getAge() { 15 return age; 16 } 17 18 public void setAge(int age) { 19 this.age = age; 20 } 21 22 public Student() { 23 super(); 24 } 25 26 public Student(String name, int age) { 27 super(); 28 this.name = name; 29 this.age = age; 30 } 31 32 }
1 /* 2 *比较器排序Comparator的使用** 3 4 - 存储学生对象并遍历,创建TreeSet集合使用**带参构造方法** 5 - 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序 6 */ 7 8 import java.util.Comparator; 9 import java.util.TreeSet; 10 11 public class TreeSetDemo3 { 12 public static void main(String[] args) { 13 //创建集合对象 14 TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {//使用匿名内部类 15 @Override 16 public int compare(Student s1, Student s2) { 17 // TODO Auto-generated method stub 18 //this.age--s.age 19 //s1--s2 20 int num = s1.getAge() - s2.getAge(); 21 int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num; 22 return num2; 23 } 24 }); 25 26 //创建学生对象 27 Student s1 = new Student("xiaobai", 12); 28 Student s2 = new Student("dabai", 13); 29 Student s3 = new Student("xiaohei", 14); 30 Student s4 = new Student("dahei", 10); 31 32 Student s5 = new Student("xiaohong", 10); 33 Student s6 = new Student("xiaohong", 10);//元素唯一 34 35 //学生添加到集合 36 ts.add(s1); 37 ts.add(s2); 38 ts.add(s3); 39 ts.add(s4); 40 ts.add(s5); 41 ts.add(s6); 42 43 //遍历集合 44 for (Student s : ts) { 45 System.out.println(s.getName() + "," + s.getAge()); 46 } 47 48 } 49 }
结论:
-
- 用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
- 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)方法
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写