Java基础学习总结

266 阅读9分钟

复习参考了Hollis大神的[Java工程师成神之路]:hollischuang.github.io/toBeTopJava…

一、大纲图

Java基础.png

二、知识点复习梳理

快捷键

  • 格式化代码:command+option+L
  • 自动补齐返回类型:option+command+V

Random和Scanner的应用

public class NumberGuess {
    public static void main(String[] args) {
        Random random = new Random();
        int result = random.nextInt(100) + 1; // 生成[1,101)的随机整数

        while (true) {
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入你要猜测的数字:");
            int guessNumer = scanner.nextInt();

            if (guessNumer > result) {
                System.out.println("你猜测的数字大了");
            } else if (guessNumer < result) {
                System.out.println("你猜测的数字小了");
            } else {
                System.out.println("恭喜你猜中了");
                break;
            }
        }
    }

工具类的设计思想

  • 构造方法用private修饰(防止外界创建对象)
  • 成员用public static修饰(可以通过类名调用)

SimpleDateFormat

日期转字符串(格式化):String str = sdf.format(d);

字符串转日期(解析):Date start = sdf.parse(startTime);

public static String Date2String(Date date, String pattern) {
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern);
    String result = simpleDateFormat.format(date);
    return result;
}

public static Date String2Date(String dateString, String pattern) throws ParseException {
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern);
    Date date = simpleDateFormat.parse(dateString);
    return date;
}

正则表达式

java.lang.util.regex.*

字符类语法规则:

  1. [abc]:代表a或者b或者c字符中的一个。
  2. [^abc]:代表除a,b,c以外的任何字符。
  3. [a-z]:代表a-z的所有小写字符中的一个。
  4. [A-Z]:代表A-Z的所有大写字符中的一个。
  5. [0-9]:代表0-9之间的某一个数字字符。
  6. [a-zA-Z0-9]:代表a-z或者A-Z或者0-9之间的任意一个字符。
  7. [a-dm-p]:a 到 d 或 m 到 p之间的任意一个字符。 预定义字符规则:
  8. "." : 匹配任何字符。
  9. "\d":任何数字[0-9]的简写;
  10. "\D":任何非数字[^0-9]的简写;
  11. "\s": 空白字符:[ \t\n\x0B\f\r] 的简写
  12. "\S": 非空白字符:[^\s] 的简写
  13. "\w":单词字符:[a-zA-Z_0-9]的简写
  14. "\W":非单词字符:[^\w] 数量词:
  15. X? : 0次或1次
  16. X* : 0次到多次
  17. X+ : 1次或多次
  18. X{n} : 恰好n次
  19. X{n,} : 至少n次
  20. X{n,m}: n到m次(n和m都是包含的) String类中的正则表达式:
  • public String[] split(String regex)    //参数regex就是一个正则表达式。可以将当前字符串中匹配regex正则表达式的符号作为"分隔符"来切割字符串。
  • public String replaceAll(String regex,String newStr)    //参数regex就是一个正则表达式。可以将当前字符串中匹配regex正则表达式的字符串替换为newStr。

文件夹递归查找文件

    public static void getAllFilePath(File srcFile) {
        File[] files = srcFile.listFiles();
        if (files != null) {
            for (File file : files) {
                // 判断file是否是目录
                if (file.isDirectory()) {
                    getAllFilePath(file);
                } else {
                    System.out.println(file.getAbsolutePath());
                }
            }
        }
    }

IO流分类

  • 字节流:可以复制任意文件数据
    • 字节输入流(InputStream 抽象类)
      • FileInputStream
      • BufferedInputStream【缓冲流,一次读写一个字节数组】
    • 字节输出流(OutputStream 抽象类)
      • FileOutputStream
      • BufferedOutputStream【缓冲流,一次读写一个字节数组】
  • 字符流:只能复制文本数据
    • 字符输入流(Reader)
      • InputStreamReader
        • FileReader
      • BufferedReader【字符缓冲流,readLine()一次读取一行】
    • 字符输出流(Writer) - OutputStreamWriter - FileWriter
      • BufferedWriter【字符缓冲流,write(String line)一次写一行】

FileInputStream

// 一次读取一个字节数组数据
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
    System.out.print(new String(bytes, 0, len));
}

BufferedInputStream

BufferedOutputStream bos = null;
BufferedInputStream bis = null;
try {
    bos = new BufferedOutputStream(new FileOutputStream("bufferfly.txt"));
            bos.write("hello \r\nbufferfly".getBytes());
            bos.write("\r\nworld".getBytes());

            bis = new BufferedInputStream(new FileInputStream("bufferfly.txt"));
            int by;
            while ((by = bis.read()) != -1) {
                System.out.print((char) by);
            }
        }

BufferedReader

    public static void main(String[] args) throws IOException {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("hello");
        arrayList.add("world");
        arrayList.add("java");
        arrayList.add("laotie");

        BufferedWriter writer = new BufferedWriter(new FileWriter("arraylist.txt"));
        for (String string : arrayList) {
            writer.write(string);
            writer.newLine();
            writer.flush();
        }
        writer.close();

        BufferedReader reader = new BufferedReader(new FileReader("arraylist.txt"));
        String list;
        while ((list = reader.readLine()) != null) {
            arrayList.add(list);
        }
        reader.close();

        System.out.println(arrayList);
    }

对象序列化流ObjectOutputStream

  • ObjectOutputStream(OutputStream out),其中writeObject(Object obj):将指定的对象写入ObjectOutputStream
  • ObjectInputStream(InputStream in),其中readObject():从ObjectInputStream读取一个对象

线程生命周期

image.png

生产者消费者模式

graph TD
生产者 --> 共享数据区域 
消费者 --> 共享数据区域

案例:

/**
 * 奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
 */
public class Box {
    private int milk;
    private boolean state = false; // 表示奶箱的状态

    public synchronized void put(int milk) {
        // 如果有牛奶,就等待消费
        if (state) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 如果没有牛奶,就生产牛奶
        this.milk = milk;
        System.out.println("送奶工将第" + this.milk + "瓶牛奶放入奶箱");
        // 生产完毕,修改状态,做唤醒操作
        state = true;
        notifyAll();
    }

    public synchronized void get() {
        // 如果没有牛奶,就等待生产
        if (!state) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 如果有牛奶,就消费牛奶
        System.out.println("用户拿到第" + this.milk + "瓶牛奶");
        // 消费完毕,修改状态,做唤醒操作
        state = false;
        notifyAll();
    }
}
/**
 * 生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作
 */
public class Producer implements Runnable {
    private Box box;

    public Producer(Box box) {
        this.box = box;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            box.put(i);
        }
    }
}
/**
 * 消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作
 */
public class Customer implements Runnable {
    private Box box;

    public Customer(Box box) {
        this.box = box;
    }

    @Override
    public void run() {
        while (true) {
            box.get();
        }
    }
}
/**
 * 测试类(BoxDemo):里面有main方法,main方法中的代码步骤如下
 * • 创建奶箱对象,这是共享数据区域
 * • 创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
 * • 创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
 * • 创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
 * • 启动线程
 */
public class BoxDemo {
    public static void main(String[] args) {
        Box box = new Box();
        Producer producer = new Producer(box);
        Customer customer = new Customer(box);

        new Thread(producer).start();
        new Thread(customer).start();
    }
}

UDP协议

用户数据报协议(User Datagram Protocol),无连接通信协议。在数据传输时,数据的发送端和接收端不建立逻辑连接。

通信的两端各建立一个Socket对,在Java中DatagramSocket 类作为基于UDP协议的Socket。

发送数据的步骤:

  1. 创建发送端的Socket对象(DatagramSocket())
  2. 创建数据,并把数据打包(DatagramPacket(byte[] buf,int len,InetAddress add,int port))
  3. 调用DatagramSocket对象的方法发送数据(void send(DatagramPacket p))
  4. 关闭发送端(void close()) 接收数据的步骤:
  5. 创建接收端的Socket对象(DatagramSocket(int port)需要指定端口)
  6. 创建一个数据包,用于接收数据(DatagramPacket(byte[] buf, int len))
  7. 调用DatagramSocket对象的方法接收数据(void receive(DatagramPacket p))
  8. 解析数据包,并把数据在控制台显示(byte[] getData())
  9. 关闭接收端(void close())

案例:

public class SendDemo {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket();

        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String line;
        while ((line = reader.readLine()) != null) {
            if ("886".equals(line)) {
                break;
            }
            byte[] bytes = line.getBytes();
            DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("127.0.0.1"), 10090);
            socket.send(packet);
        }

        socket.close();
    }
}

public class ReceiveDemo {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket(10090);

        while (true) {
            byte[] bytes = new byte[1024];
            DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
            socket.receive(packet);
            System.out.println("解析到的数据是:" + new String(packet.getData(), 0, packet.getLength()));
        }
    }
}

TCP协议

传输控制协议 (Transmission Control Protocol),是面向连接的通信协议。传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据。

Java为客户端提供了Socket类,为服务器端提供了ServerSocket类。

发送数据的步骤:

  1. 创建客户端的Socket对象(Socket)
  2. 获取输出流,写数据(OutputStream getOutputStream()/write())
  3. 释放资源 接收数据的步骤:
  4. 创建服务器端的Socket对象(ServerSocket(int port))
  5. 监听客户端连接,返回一个Socket对象(Socket accept())
  6. 获取输入流,读数据(InputStream getInputStream())
  7. 释放资源

案例:

public class ClientTest1 {
    public static void main(String[] args) throws IOException {
        Socket client = new Socket("127.0.0.1", 10000);

        // 获取字节输出流,写数据
        OutputStream outputStream = client.getOutputStream();
        outputStream.write("Client-send message".getBytes());

        // 接收服务器反馈
        InputStream inputStream = client.getInputStream();
        byte[] bytes = new byte[1024];
        int len = inputStream.read(bytes);
        System.out.println("客户端接收来自服务器的数据:" + new String(bytes, 0, len));

        client.close();
    }
}

public class ServerTest1 {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(10000);
        Socket socket = serverSocket.accept();

        // 获取字节输入流,读数据
        InputStream inputStream = socket.getInputStream();
        byte[] bytes = new byte[1024];
        int len = inputStream.read(bytes);
        System.out.println("服务端接收来自客户端的数据:" + new String(bytes, 0, len));

        // 反馈给客户端
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("服务端已接收!客户端请回答!".getBytes());

        serverSocket.close();
    }
}

Lambda

语法格式:

(形式参数) -> { 代码块; } 案例:

// lambda表达式
useAddable((int x, int y) -> {
    return x - y;
});

反射

获取Class类对象:

  • 类名.class 属性
  • 对象名.getClass() 方法
  • Class.forName(全路径类名) 方法 获取构造方法:
  • Constructor<?>[] getConstructors():返回所有公共构造方法对象的数组
  • Constructor<?>[] getDeclaredConstructors():返回所有构造方法对象的数组
  • Constructor getConstructor(Class<?>... parameterTypes):返回单个公共构造方法对象
  • Constructor getDeclaredConstructor(Class<?>...parameterTypes):返回单个构造方法对象
  • Constructor类用于创建对象的方法:T newInstance(Object...initargs) 获取成员变量:
  • Field[] getFields():返回所有公共成员变量对象的数组
  • Field[] getDeclaredFields():返回所有成员变量对象的数组
  • Field getField(String name):返回单个公共成员变量对象
  • Field getDeclaredField(String name):返回单个成员变量对象
  • Field类用于给成员变量赋值的方法:void set(Object obj,Object value) 获取成员方法:
  • Method[] getMethods():返回所有公共成员方法对象的数组,包括继承的
  • Method[] getDeclaredMethods():返回所有成员方法对象的数组,不包括继承的
  • Method getMethod(String name, Class<?>...parameterTypes):返回单个公共成员方法对象
  • Method getDeclaredMethod(String name, Class<?>... parameterTypes):返回单个成员方法对象
  • Method类用于执行方法的方法:Object invoke(Object obj,Object... args) 案例1:越过泛型检查
/**
 * 通过反射技术,向一个泛型为Integer的集合中添加一些字符串数据
 */
public class ReflectTest1 {
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(10);
        list.add(20);

        Class<? extends ArrayList> listClass = list.getClass();
        Method add = listClass.getMethod("add", Object.class);
        add.invoke(list, "hello");
        add.invoke(list, "world");

        System.out.println(list); // [10, 20, hello, world]
    }
}

案例2:运行配置文件中指定类的指定方法

/**
 * 通过反射运行配置文件中指定类的指定方法
 */
public class ReflectTest2 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Properties properties = new Properties();
        FileReader reader = new FileReader("class.txt");
        properties.load(reader);
        reader.close();

        String className = properties.getProperty("className");
        String methodName = properties.getProperty("methodName");

        // 获取类
        Class<?> aClass = Class.forName(className);
        Constructor<?> constructor = aClass.getConstructor();
        // 获取实例
        Object instance = constructor.newInstance();
        // 获取方法
        Method method = aClass.getMethod(methodName);
        // 方法调用
        method.invoke(instance);
    }
}

函数式接口

概述

public class MyInterfaceTest {
    public static void main(String[] args) {
        MyInterface myInterface = () -> System.out.println("函数式接口-show方法");
        myInterface.show(); // 函数式接口-show方法
    }
}

@FunctionalInterface
interface MyInterface {
    void show();
    // 会报错
    // void func();
}

函数式接口作为方法的参数:

// 查看Runnable源代码定义
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
public class RunnableDemo {
    public static void main(String[] args) {
        // 匿名内部类
        startThread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "启动线程");
            }
        });

        // lambda
        startThread(() -> System.out.println(Thread.currentThread().getName() + "启动线程"));
    }

    private static void startThread(Runnable runnable) {
        new Thread(runnable).start();
    }
}

函数式接口作为方法的返回值:

// 查看Comparator源代码定义
@FunctionalInterface
public interface Comparator<T> {
}
public class ComparatorDemo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList();
        list.add("cccc");
        list.add("aa");
        list.add("b");
        list.add("ddd");
        System.out.println("排序前:" + list);
        Collections.sort(list, getComarator());
        System.out.println("排序后:" + list);
    }

    private static Comparator<String> getComarator() {
        /*// 匿名内部类实现
        return new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.length() - o2.length();
            }
        };*/
        // lambda实现
        return ((o1, o2) -> o1.length() - o2.length());
    }
}

Supplier接口

java.util.function.Supplier接口也被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据供我们使用。

常用方法:

  • T get():按照某种实现逻辑(由Lambda表达式实现)返回一个数据

Consumer接口

Consumer接口也被称为消费型接口,它消费的数据的数据类型由泛型指定。

常用方法:

  • void accept(T t):对给定的参数执行此操作
  • default Consumer andThen(Consumer after):返回一个组合的Consumer,依次执行此操作,然后执行after操作

Predicate接口

Predicate接口通常用于判断参数是否满足指定的条件。

常用方法:

  • boolean test(T t):对给定的参数进行判断(判断逻辑由Lambda达式实现),返回一个布尔值
  • default Predicate negate():返回一个逻辑的否定,对应逻辑非
  • default Predicate and(Predicate other):返回一个组合判断,对应短路与
  • default Predicate or(Predicate other):返回一个组合判断,对应短路或

Function接口

Function<T,R>接口通常用于对参数进行处理,转换(处理逻辑由Lambda表达式实现),然后返回一个新的值。

常用方法:

  • R apply(T t):将此函数应用于给定的参数
  • default Function andThen(Function after):返回一个组合函数,首先将该函数应用于输入,然后将after函数应用于结果

Stream流

中间操作:

方法名说明
Stream filter(Predicate predicate)用于对流中的数据进行过滤
Stream limit(long maxSize)返回此流中的元素组成的流,截取前指定参数个数的数据
Stream skip(long n)跳过指定参数个数的数据,返回由该流的剩余元素组成的流
static Stream concat(Stream a, Stream b)合并a和b两个流为一个流
Stream distinct()返回由该流的不同元素(根据Object.equals(Object) )组成的流
Stream sorted()返回由此流的元素组成的流,根据自然顺序排序
Stream sorted(Comparatorcomparator)返回由该流的元素组成的流,根据提供的Comparator进行排序
Stream map(Function mapper)返回由给定函数应用于此流的元素的结果组成的流
IntStream mapToInt(ToIntFunctionmapper)返回一个IntStream其中包含将给定函数应用于此流的元素的结果

终结操作:

方法名说明
void forEach(Consumer action)对此流的每个元素执行操作
long count()返回此流中的元素数

收集操作:

方法名说明
R collect(Collector collector)把结果收集到集合中

工具类Collectors提供了具体的收集方式:

方法名说明
public static Collector toList()把元素收集到List集合中
public static Collector toSet()把元素收集到Set集合中
public static Collector toMap(Function keyMapper, Function valueMapper)把元素收集到Map集合中

单例设计模式

Singleton模式,指的是一个类、在一个JVM里,只有一个实例存在。三要素:

  • 构造方法私有化
  • 静态属性指向实例
  • public static的 getInstance方法,返回第二步的静态属性
  1. 饿汉式单例模式 通俗的说,无论如何饿汉模式都会先创建一个实例,占用内存
/*
 * 饿汉式单例模式:
 * GiantDragon 应该只有一只,通过私有化其构造方法,使得外部无法通过new得到新的实例。
 * GiantDragon 提供了一个public static的getInstance方法,外部调用者通过该方法获取定义的对象
 * 而且每一次都是获取同一个对象。 从而达到单例的目的。
 * 这种单例模式又叫做饿汉式单例模式,无论如何都会创建一个实例
 */
public class GiantDragon {
 
    // 私有化构造方法,外面无法通过new进行实例化
    private GiantDragon() {
    }
 
    // 类属性,指向一个实例化对象。类属性只有一个,所以每次调用的都相同
    private static GiantDragon instance = new GiantDragon();
 
    // public static方法,提供给调用者获取定义的对象
    public static GiantDragon getInstance() {
        return instance;
    }
}
public class TestGiantDragon {
    public static void main(String[] args) {
        // 报错。
        /* GiantDragon dragon = new GiantDragon(); */
 
        GiantDragon dragon1 = GiantDragon.getInstance();
        GiantDragon dragon2 = GiantDragon.getInstance();
        GiantDragon dragon3 = GiantDragon.getInstance();
 
        // 结果为True
        System.out.println(dragon1 == dragon2);
        System.out.println(dragon1 == dragon3);
    }
}
  1. 懒汉式单例模式 通俗的说,用到他的时候他才会创建实例,一旦创建后就使用这一个了

注意,懒汉单例设计模式在多线程环境下可能会实例化出多个对象,不能保证单例的状态。

/*
 * 懒汉式单例模式与饿汉式单例模式不同
 * 只有在调用getInstance的时候,才会创建实例
 */
public class GiantDragon1 {
    private GiantDragon1() {
    }
    // 准备一个类属性,用于指向一个实例化对象,但是暂时指向null
    private static GiantDragon1 instance;
 
    public static GiantDragon1 getInstance() {
        // 第一次访问的时候,发现instance没有指向任何对象,这时实例化一个对象
        if (instance == null) {
            instance = new GiantDragon1();
        }
        return instance;
    }
}
public class TestGiantDrsgon1 {
    public static void main(String[] args) {
        /* GiantDragon1 dragon = new GiantDragon1(); */
 
        GiantDragon1 dragon1 = GiantDragon1.getInstance();
        GiantDragon1 dragon2 = GiantDragon1.getInstance();
        GiantDragon1 dragon3 = GiantDragon1.getInstance();
 
        System.out.println(dragon1 == dragon2);
        System.out.println(dragon1 == dragon3);
    }
}

看业务需求,如果业务上允许有比较充分的启动和初始化时间,就使用饿汉式,否则就使用懒汉式。

模板设计模式

思想:将固定流程写到父类中,不同的地方就定义成抽象方法,让不同的子类去重写。模板模式的优势是,模板已经定义了通用架构,使用者只需要关心自己需要实现的功能即可。

// 司机开车的模板类
public abstract class Driver {
    public void go() {
        System.out.println("开门");
        System.out.println("点火");
        // 开车姿势不确定?定义为抽象方法
        ziShi();
        System.out.println("刹车");
        System.out.println("熄火");
    }
    public abstract void ziShi();
}

public class NewDriver extends Driver {
    @Override
    public void ziShi() {
        System.out.println("新司机双手紧握方向盘");
    }
}
public class OldDriver extends Driver {
    @Override
    public void ziShi() {
        System.out.println("老司机右手握方向盘左手抽烟...");
    }
}

排序算法-冒泡排序

一种排序的方式,对要进行排序的数据中相邻的数据进行两两比较,将较大的数据放在后面,依次对所有的数据进行操作,直至所有数据按要求完成排序。如果有n个数据进行排序,总共需要比较n-1次。每一次比较完毕,下一次的比较就会少一个数据参与。

public class ArrayDemo {
    public static void main(String[] args) {
        //定义一个数组
        int[] arr = {7, 6, 5, 4, 3};
        System.out.println("排序前:" + Arrays.toString(arr));

        // 这里减1,是控制每轮比较的次数
        for (int x = 0; x < arr.length - 1; x++) {
            // -1是为了避免索引越界,-x是为了调高比较效率
            for (int i = 0; i < arr.length - 1 - x; i++) {
                if (arr[i] > arr[i + 1]) {
                    int temp = arr[i];
                    arr[i] = arr[i + 1];
                    arr[i + 1] = temp;
                }
            }
        }
        System.out.println("排序后:" + Arrays.toString(arr));
    }
}

排序算法-选择排序

一种排序的方式,选中数组的某个元素,其后面的元素依次和选中的元素进行两两比较,将较大的数据放在后面,依次从前到后选中每个元素,直至所有数据按要求完成排序。如果有n个数据进行排序,总共需要比较n-1次。每一次比较完毕,下一次的比较就会少一个数据参与

public class ArrayDemo {
    public static void main(String[] args) {
        //定义一个数组
        int[] arr = {7, 6, 5, 4, 3};
        System.out.println("排序前:" + Arrays.toString(arr));
  		// 这里减1,是控制比较的轮数
        for (int x = 0; x < arr.length; x++) {
            // 从x+1开始,直到最后一个元素
            for (int i = x+1; i < arr.length; i++) {
                if (arr[x] > arr[i]) {
                    int temp = arr[x];
                    arr[x] = arr[i];
                    arr[i] = temp;
                }
            }
        }
        System.out.println("排序后:" + Arrays.toString(arr));
    }
}