3.13(day17)
晨写
1.什么是内部类,分为哪些
类中类
成员内部类/局部内部类
2.直接访问非静态的成员内部类的成员方法的格式(重点)
3.静态的成员内部类的特点以及直接访问静态成员内部类的成员方法的格式
特点:静态的成员内部类的成员方法里面:访问外部类的成员必须为静态
class Outer{
static class Inner{
public void show()[
System.out.println("show Inner") ;
public static void show2(){
System.out.println("show2 Inner") ;
}
}
}
4.接口和抽象类的区别?
1>成员区别:
接口:
成员变量:只能是常量
成员方法:只能是抽象方法
构造方法:没有
抽象类:
成员变量:既可以是常量,也可以是变量
成员方法:既可以抽象方法,也可以非抽象方法
构造方法:存在 有参构造/无参构造方法,目的为了类的数据进行构造初始化!----分层初始化
2>关系区别:
类与类:继承关系,单继承,可以多层继承,不支持多继承
类接口:实现关系,一个类继承另一个类的同时,可以实现多个接口
接口与接口:继承关系,支持单继承,多继承,多层继承!
3>设计理念:
抽象类:存在继承关系,体现的是一种"is a"的关系:当一个类A是类B的一种,使用继承!
接口:与子实现类实现关系,事物本身不具备的功能,体现的一种”like a"的关系(额外扩展功能)
5.jdk8以前局部内部类访问局部变量为什么加入final修饰?
局部变量的生命周期,随着方法调用而存在随着方法调用结束而消失;当方法结束了,但是局部内部类正在被创建对象,通过成员方法访问里面的局部变量,就会导致访问不到,此时这个变量必须加入fina1修饰,为自定义常量
6.匿名内部类的格式以及本质(重点)
格式:
new 类名或者接口名(){
重写方法(){
...
}
}
本质:
继承了该类或者是实现类该接口的子类对象!
1.面试题
方法的形式参数如果是抽象类或者接口—需要传递当前抽象类或者接口的子类对象(可以使用匿名内部类)
方法返回值是接口或者抽象类–可以放回该抽象类或者接口的子类对象,---->使用匿名内部类
匿名内部类在开发中的使用:
方法的方法返回值是接口类型,需要返回 的该接口的子实现类对象!
//有一个接口
interface Love{
void love() ;
}
//LoveDemo类
class LoveDemo {
public Love getInstance() {//返回值接口类型---需要返回接口的子实现类对象---使用匿名内部类
//?
//直接使用匿名内部类
return new Love() {
@Override
public void love() {
System.out.println("love 高圆圆");
}
};
}
}
//定义一个子实现类--实现接口---重写方法
//测试类
public class ReturnDemo {
public static void main(String[] args) {
//访问 LoveDemo类中的getInstance()方法
LoveDemo ld = new LoveDemo() ;
Love l = ld.getInstance();
l.love() ;
}
}
2.选择排序
1)使用0角标对应的元素和后面角标对应的元素依次比较,第一次比较完毕,最小值出现在最小索引处!
2)比较的次数:数组长度-1次
public static int[] selectSort(int[] array){
for(int x = 0 ; x < array.length-1;x++){
//里面元素比较
for(int y = x +1; y< array.length ; y ++){
//后面元素比较前面元素小,小的往前放
if(array[y] < array[x]){
int temp = array[y] ;
array[y] = array[x] ;
array[x] = temp ;
}
}
}
return array ;
}
3.14(day18)
常用类:Object/String/StringBuffer/Date日期/Integer/Character
1.Object
equals()判断两个引用数据类型是否相等
toString()打印对应的字符串形式
clone()克隆
hashCode()哈希码值
在Java中获取一个类的字节码文件对象的方式有几种?
有三种:
1)public final Class getClass():获取正在运行的类; (返回字节码文件对象)
2)在Java中任意类型的class属性
举例: Class c = 类名.class
3)Class类提供的一个静态方法
public static Class forName(String classPathName) :参数为当前类的全限定名称
toString()
public String toString():返回对象的字符串表示形式。
结果应该是一个简明扼要的表达,容易让人阅读。 建议所有子类覆盖此方法(否则,打印出来的是地址值!)
public int hashCode():返回对象的哈希码值,不同的对象,它的哈希码值不同
(理解为 "地址值",它不是实际意义的地址值----通过哈希算法算出来的)
clone()
克隆方法:创建并返回此对象的"副本"
Object类的垃圾回收方法
当前垃圾回收器开始运行,会调用Object类的finalize()方法,来回收对象,子类重写了,会调用子类的finalize方法
Scanner
获取功能:除过不能录入char类型之外.都可以录入!
int nextInt():录入int类型
String next():录入字符串
String nextLine():录入一行字符串
判断功能
boolean hasNextXXX()方法:判断下一个录入的是为XXX类型
2.String
获取功能
charAt(int index) 获取指定索引值对应的字符
length() :字符串特有功能:获取字符串长度
concat(String str):拼接功能 将指定的字符串拼接到该字符串末尾
indexOf(String str)返回指定字符串第一次出现索引值
lastIndexOf(String str):返回指定子字符串最后一次出现的索引值
split(String regex):字符串拆分功能
substring(int beginIndex,int endIndex)截取
转换功能
getBytes(): 将字符串转换成字节数组
toCharArray() :将字符串转换成字符数组
valueOf():常用类型--转换成String
toUpperCase():将指定字符串转换成大写
toLowerCase():将指定字符串转换成小写
判断功能以及
constains(String str):判断大串中是否包指定的子字符串
equals(Object anObject)):比较两个字符串内容是否相同
equalsIgnoreCase(String anotherString)):忽略大小写比较
startsWith(String prefix):字符串是否以指定的前缀开头
endsWith(String str):字符串是否以指定的后缀结尾
其他功能
replace(char oldChar,char newChar)替换功能:使用指定的新的字符替换以前的字符
trim():删除前后两端空格
3.15(day19)
晨写
1.列举出Object类中常用的方法
equals()判断两个引用数据类型是否相等
toString()打印对应的字符串形式
clone()克隆
hashCode()哈希码值
2.String类的特点是什么?
不可变的,一旦被创建,其值不可被修改(常量池中地址)
特殊的引用类型,方法形参,形参改变不会影响实参(和基本类型一致)
3.列举出String类的转换功能有哪些?(4个)
toCharArray()字符串转换字符数组
getBytes()字符串转换字节数组
toUppercase()全部大写
toLowercase()全部小写
valueOf()其他类型转换成字符串
4.列举出String类的获取功能(4个)
substring()截取
charAt()
length()
spilt()通过指定分隔号拆分成字符串数组
concat()拼接
5.String类中两个字符串按照字典顺序如何比较的?
取两个字符串最小长度,如果相同,返回长度差值,如果不同,返回ACSII的差值
1.StringBuffer
字符串缓冲区,线程安全的,支持可变的字符序列
构造方法
StringBuffer():无参构造方法,里面空的字符序列,初始容量16个字符(默认容量足够大)
StringBuffer(String str):将字符串构造为StringBuffer类型,容量是大小:当前的里面的字符串长度+16
获取字符串缓冲区的长度 ---int length()
获取字符串缓冲区的容量---int capacity()
增删改功能
增:
append(任意java类型) 添加(在末尾追加)到字符串缓冲区中,返回值是字符串缓冲区本身
insert(int offset,任意java类型):在指定位置处的序列前面插入新的序列
删:
deleteCharAt(int index):在指定位置处删除指定的字符,返回字符串缓冲区本身----开发中使用多
delete(int start,int end):从指定位置start处开始到end-1处的字符序列删除,返回字符串缓冲区本身
改:
replace(int start,int end,String str)使用指定的字符串str从指定位置开始到end-1处进行替换,返回该字符串缓冲区本身
substring(int start):从指定位置开始截取,默认截取到末尾,返回被截取后的字符串
类型转换 (重点)
String-->StringBuffer
String s = "hello" ;
1) 可以通过StringBuffer的有参构造方法
//StringBuffer(String str)
StringBuffer sb = new StringBuffer(s) ;
2) 通过StringBuffer的无参构造方法+append(String str)
StringBuffer sb2 = new StringBuffer() ;
sb2.append(s) ;
StringBuffer-->String
StringBuffer buffer = new StringBuffer("100") ;
1) public String toString()
String str = buffer.toString();
2) String类的构造方法
//public String(StringBuffer buffer)
String str2 = new String(buffer) ;
String和StringBuffer的区别
String特点:
字符串是一个常量,一旦被创建,其值不能被更改
String作为形参,形参的改变不会影响实参;
StringBuffer特点:
字符串缓冲区支持可变的字符串,线程安全,执行效率低!
StringBuffer和StringBuilder的区别
这两个类都是字符串缓冲区,都支持可变的字符序列,
前者:使用在多线程环境里面,能够保证线程安全----意味着同步----->执行效率低
后者:是StringBuffer的简易替换,用于在单线程环境中,线程不安全---不同步---执行效率高!
单线程使用StringBuilder:效率高(速度快)
其他功能
reverse() :字符串缓冲区反转功能,将字符串缓冲区中所有的字符序列反转!
2.Integer以及Character
Integer
提供了一些功能:十进制转换成对应的进制的方法(静态方法)
四类八种的基本数据类型都会自动提升 对应的引用类型
jdk5以后新特性:自动拆装箱
拆箱:
包装类类型--->降为基本类型
装箱:
基本类型--->提升对应的引用类型
基本类型 | 引用类型(默认null) |
---|---|
byte | Byte |
short | Short |
int | Integer(特殊) |
long | Long |
float | Float |
double | Double |
char | Character(特殊) |
boolean | Boolean |
构造方法
Integer(int value):创建一个Integer实例,里面包含的int类型的值
Integer(String s): 将数字字符串转换成Integer的实例
int基本类型和引用String的相互转换 (重点)
String---int:
int 变量名 = Integer.parseInt(已知的数字字符串) 推荐
int---->String: Integer类提供静态方法
String 变量名 = Integer.toString(int类型的值)
范围
将int类型赋值给Integer变量,执行底层方式valueOf(int i)
Integer valueOf(int i)
如果i的值在-128~127之间,直接从内部类--->IntegerCache缓存去中取数据
否则重新开辟新的空间创建Integer实例
Character
构造方法:
public Character(char value):包含char类型的值
Character ch = new Character((char)97) ;
成员方法:(返回boolean)
Character.isDigit(char ch):判断当前这个字符是否为数字
Character.isLowerCase(char ch):判断当前这个字符是否小写
Character.isUpperCase(char ch):判断当前这个字符是否大写
3.16(day20)
晨写
1.String和int的相互转换
int-->String
Integer.toString()
String-->int
Integer.parseInt()
2.StringBuffer和String的相互转换
String-->StringBuffer
StringBuffer sb = new StringBuffer();
StringBuffer-->String
.toString()
new String()
3.StringBuffer和StringBuilder的区别?
Buffer是多线程,安全,执行效率低
Builder是Buffer的一种,单线程,执行效率高不安全
4.Integer i= int值; 这个方法底层怎么执行的?
public static Integer valueof(int i) {}
//在Integer类中---IntegerCache:Integer的内部缓存区
如果i在-128~127,内部缓存区取数据,否则return new Integer(i) ;
5.什么是自动拆装箱
拆箱:引用数据类型-->基本数据类型
装箱:基本数据类型-->引用数据类型
6.String和StringBuffer的区别
String是引用数据类型,形参的改变会影响到实参,且一旦定义不可修改
StringBuffer是字符串缓冲区,可以修改,且安全,形参的改变会改变实参
1.其他类
System
不能实例化:提供了静态字段(静态变量)
PrintStream ps = System.out ;标准 打印输出流
PrintStream ps2 = System.err ;标准错误输出流
InputStream is = System.in ;标准输入流
常用的成员方法:
public static long currentTimeMillis() 计算当前系统时间毫秒值
用来计算当前某个程序的时间复杂度
long time = System.currentTimeMillis() ;
gc() 收到开启垃圾回收器 ---会调用finalize(),回收没有更多引用的对象
exit(int status): 参数为0:终止jvm
arraycopy(
Object src, //原对象
int srcPos, //原对象中的某个位置
Object dest, //目标对象
int destPos, //目标对象的某个位置
int length) //指定的长度
Random
伪随机数生成器 可以产生随机数 比Math.random()麻烦
两个构造方法:
public Random() :无参构造方法(推荐)
public Random(long seed):有参构造,创建一个指定long类型的变量的随机数生成器
两个成员方法:
public int nextInt(): 产生伪随机数在,取值是int类型 的范围内
public int nextInt(int n):产生0-n之间的随机数(推荐)
Calendar日历
提供了获取年,年中的月,月中的日期,小时,小时分钟数,分钟中的秒数都可以获取到
getInstance() 创建日历对象
Calendar c = Calendar.getInstance();
get(int field):通过日历对象获取日历中的字段值
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH) ;//默认计算0-11
int date = c.get(Calendar.DAY_OF_MONTH) ;
add(int field,int amount):给指定的日历字段值添加或者减去时间偏移量
c.add(Calendar.YEAR,-5);
c.add(Calendar.DAY_OF_MONTH,-10);
BigDecimal
对小数进行精确计算
构造方法:
BigDecimal(String val):将String类型数据构造成BigDecimal,将double数据---使用"小数字符串"
成员方法:
add(BigDecimal augend):求和
subtract(BigDecimal subtrahend):相减
multiply(BigDecimal multiplicand):乘法
divide(BigDecimal divisor):小数除法
divide(BigDecimal divisor,int roundingMode):除法,第二个参数四舍五入(BigDecimal.ROUND_HALF_UP)
divide(BigDecimal divisor,
int scale,//保留几位小数
int roundingMode) //舍入模式
2.Date时间
表单特定的日期时间,还允许格式化和解析日期字符串
构造方法:
Date():创建当前日期对象:获取当前系统日期时间
Date date = new Date() ;
Date(long date):里面传入long类型的时间毫秒值,获取的表示从标准基准时 间(称为“时代”)即1970年1月1日00:00:00 GMT起的指定毫秒数
成员方法:
getTime():将Date对象转换成long类型时间毫秒值
long time = date.getTime();
setTime(long time):设置日期时间,参数时间毫秒值
和String日期文本相互转换
DateFormat是日期/时间格式化子类的抽象类,它以语言无关的方式格式化和分析日期或时间
它具体的子类:SimpleDateFormat是一个具体的类,允许格式化(日期文本),解析(文本日期)
构造方法:
SimpleDateFormat(String pattern):参数是日期模式 "yyyy-MM-dd"
成员方法:
format(Date date)将Date---->String
Date date = new Date() ;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss") ;
String dataStr = sdf.format(date);
parse(String source)String日期---->Date
String source = "2008-5-12" ;
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd") ;
//当SimpletDateFormat和给定日期文本格式不一致,就解析出错
Date date2 = sdf2.parse(source);
3.集合框架
Collection:单例集合顶层次的根接口,不能new,没有直接子实现类,是通过它子接口的子实现类来实例化!
泛型: 集合里面创建的时候,如果不带泛型可能程序安全问题;带了<>,模拟创建数组的时候,明确了数据类型!jdk7以后新特性;泛型推断 (加入泛型:保证数据的安全性),创建集合就明确了数据类型
Collection<Student> c = new ArrayList<>() ;
<E>:element
<T>:Type
<K>:Key键
<V>:Value值
基本功能:
增:
.add(E e):添加元素
删:
.remove(Object o):删除指定的元素
.clear():暴力删除:清空集合
判断:
.contains(Object o):是否包含指定的元素
.isEmpty():判断集合是否为空
Iterator
Collection专有迭代器(遍历集合) (过渡)
Iterator迭代器接口:
.hasNext(): 判断迭代器里面是否下一个可以遍历的元素
.next():获取迭代器的下一个元素 (和迭代器中的类型一致)
开发中---集合遍历---->都用jdk5以后新特性:增强for循环 代替上面迭代器
高级功能
Object[] toArray():集合的传统方式遍历 将集合转换成对象数组(以后不会用的)
Object[] objects = c.toArray();
3.17(day21)
晨写
1.将Collection集合进行遍历的方式
//Collection集合迭代器
Iterator iterator();
//将Collection转换成对象数组
Object[] toArray()
Collection<Student> c = new ArrayList<>() ;
Student sl = new Student("张三丰",55) ;
Student s2 = new Student("高圆圆",44) ;
c.add(s1) ;
c.add(s2) ;
Object[] objects =c.toArray() ;
//遍历数组对象
for(int x = 0 ;x < objects.length; x++){
// objects[x] 栈内存的数据类型 object---->堆内存都是学生对象(父类用指向子类对象)
//直接输出objects[x] 执行学生对象tostring方法(tostring (),方便自己测试用的)
//通过student的getxxx() 获取信息表达式
//向下转型
Student s = (Student)objects[x] ;
System.out.printIn(s.getName()+"---"+s.getAge()) ;
}
2.集合和数组的区别
集合的元素不可以有重复,无序,存储任意引用类型
数组的元素可以重复,元素类型要一致
3.Date和String日期文本的相互转换
date-->String:
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒")
String s = sdf.format(date)
String-->date:
String source = "2008-5-12" ;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒")
String s = sdf.parse(source)
4.Collection的Iterator的使用方式
//Iterator iterator()
Collection<Student> c = new ArrayList<>() ;
Student sl = new Student("张三丰",55) ;
Student s2 = new Student("高圆圆",44) ;
c.add(s1) ;
c.add(s2) ;
//将集合中元素交给迭代器
Iterator<Student> it =c.iterator() ;
while(it.hasNext()){
//判断是否有下一个可以迭代的元素
//存在,进来
//获取下一个元素
Student s = it.next() ;
System.out.println(s.getName()+"----"+s,getAge()) ;
}
5.获取一个类的字节码文件对象的方式?
1) 0bject类的getclass() 方法--->需要使用对象名来访问的 class(字节码文件对象/正在运行的类对象)
2)任意Java类型的class属性---类名.class---->Class
3)反射---class.forName ("当前类或者接口的全限定名称”);-->Class
1.Collection迭代器的原理
Collection<xx> c = new ArrayList<>() ;
--->ArrayList里面的内部类间接实现Iterator (代码体现结构)
Collection
//新建一个空集合思想
Collection<String> newColl = new ArrayList<>() ;
//然后:去遍历以前的集合,获取所有的元素
//在新集合中判断,是否包含这个以前的元素,如果不包含,把这个元素添加新集合中!
for(String s :c){
//判断
if(!newColl.contains(s)){//不包含
/**
* ArrayList里面的contains底层依赖Object的equals方法,由于现在存String类型
* String类型重写了Object的equals比较的是两个字符串内容是否相同,相同只存储一个!
*/
newColl.add(s) ;
}
}
2.List集合去重思想
Iterator<String> it = c.iterator() ; //接口多态 通过ArrayList的内部类Itr实现迭代器中的方法
while(it.hasNext()){
String s = it.next();
//判断,如果这里面存在"world",然后添加"JavaEE
if("world".equals(s)){
c.add("JavaEE") ;
}
}
增强for
是在集合/数组中存储的引用类型中使用居多,代替集合迭代器的,简化书写代码
for(集合中存储的数据类型 变量名 : 集合对象){
使用这个变量名即可;
}
注意:
要使用增强for遍历集合,集合对象不能null;
防止空指针异常,在使用增强for之前,对集合进行非空判断!
3.List三个子实现类的特点以及特有功能
List集合常用三个子实现类特点:
实际需求:单线程中,使用List完成一些事情,没有明确规定具体使用哪个实现类;默认使用ArrayList集合!
ArrayList
底层数据结构是数组-->查询快,增删慢! (数组,通过整数索引查询)
元素查找: 顺序查找,二分搜索(折半查找),哈希查找
线程角度:
线程不安全的类,是一个不同步,执行效率高!
底层扩容机制1.5倍
ArrayList集合的contains()方法依赖于Object的equals方法,底层实现的"==",比较的是地址值是否相同
ArrayList存储自定义类型,要保证元素唯一,必须重写equals和hashCode方法
//先创建一个大的集合
ArrayList<ArrayList<Student>> bigArray = new ArrayList<>() ;
ArrayList<Student> firstArray = new ArrayList<>() ;
bigArray.add(firstArray) ;
ArrayList<Student> secondArray = new ArrayList<>() ;
bigArray.add(secondArray) ;
ArrayList<Student> thirdArray = new ArrayList<>() ;
bigArray.add(thirdArray) ;
//遍历大集合
System.out.println("学生信息是:\t");
for(ArrayList<Student> array:bigArray){
//每一个ArrayList集合里面存储的学生对象
for(Student s: array){
System.out.println(s.getName()+"\t"+s.getAge());
}
}
Vector
底层数据结构是数组-->查询快,增删慢!
线程角度:
线程安全的类,是同步的,执行效率低! 多线程环境下使用居多,单线程使用ArrayList代替Vector
LinkedList
底层数据是链表, 查询慢,增删快
线程角度:
线程不安全的类,不同步的,执行效率高!
4.Set集合中两个子实现类特点
HashSet--->底层依赖HashMap---存储元素,取元素---能够保证元素唯一
TreeSet--->底层依赖TreeMap---存储元素,取元素---能够保证元素唯一,而且排序(Integer/String,自然排序)
List集合特点:
元素有序(存储和取出一致),而且元素可以重复!
Set集合:
元素唯一的,无序(存储和取出不一致)
5.List
List集合是Collection集合的子接口,它集合遍历方式:
1)Collection的Object[] toArray()
2)Collection的Iterator iterator() ;
3)使用size()+ 存储的数据类型 get(int index)通过角标获取元素内容 :普通for循环格式
4)ListIterator<E> listIterator()List集合的专有遍历方式 :列表迭代器
5)增强for循环
//3)4)5)特有方式
//使用size()+ 存储的数据类型 get(int index)通过角标获取元素内容 :普通for循环格式
for(int x = 0 ; x < list.size() ; x++){
//通过x角标值获取元素内容
String s = list.get(x);
System.out.println(s);
}
//ListIterator<E> listIterator()List集合的专有遍历方式 :列表迭代器
ListIterator<String> lit = list.listIterator();//获取列表迭代器
while(lit.hasNext()){
String s = lit.next();
System.out.println(s);
}
//列表迭代器提供了一些功能,进行逆序遍历(前提必须有上面的正向遍历)
//boolean hasPrevious():判断是有上一个元素
//E previous():获取上一个元素
while(lit.hasPrevious()){
String previous = lit.previous();
System.out.println(previous);
}
3.18(day22am)
1.List(续)
LinkedList
特有方法:
.addFirst(E e) :在链表开头插入元素
.addLast(E e) :在链表末尾插入元素
.removeFirst(): 删除链表开头的第一个元素并返回该元素
.removeLast()从此列表中删除并返回最后一个元素。
.getFirst():返回链表头元素
.getLast():返回链表链尾元素
ListIterator
List继承Collection—>Iterator是Collection的专有迭代器,没有提供添加功能,而List集合专有遍历方式ListIterator:列表迭代器
List有特有方式:
void add(int index,E element):在指定位置处,添加指定的元素
List<String> list = new ArrayList<>() ;
list.add("hello") ;
list.add("world") ;
list.add("javaee") ;
ListIterator<String> lit = list.listIterator(); //ListItr extends Itr :都是ArrayList的内容类
while(lit.hasNext()){
//获取下一个元素
String s = lit.next();
//判断:如果s是"world",添加一个新的"go"
if("world".equals(s)){
lit.add("go") ;
}
}
Vector
底层数组,查询快,增删慢,线程安全的,执行效率低
Vector集合的特有功能:
.addElement(E obj)--->相当于List的add()
.elementAt(int index)--->相当于List的get(int index)+size方法相结合
特有遍历方式:
Enumeration<String> en = v.elements() ;// 相当于集合的迭代器
while(en.hasMoreElements()){ //相当于boolean hasNext()
String s = en.nextElement(); //相当于E next()
System.out.println(s);
}
2.Set接口
无序(存储和取出不一致),不能保证迭代次序,但是可以唯一!
HashSet<E> : 存储和取出,保证元素唯一!
---底层是HashMap实例(哈希表)
TreeSet<E> : 存储和取出,同时需要进行排序的取出!
---底层是依赖TreeMap实例(红黑树结构)
HashSet
HashSet<String> hs = new HashSet<>() ;
TreeSet
自然排序:
TreeSet():使用自然排序,当前里面存储的类型必须实现Comparable
TreeSet<String> ts = new TreeSet<>() ;
比较器排序