当前位置: 首页 > news >正文

Java 进阶-全面解析

目录

异常处理​

集合框架​

List 集合​

Set 集合​

Map 集合​

文件与字符集​

IO 流​

多线程​

通过继承Thread类创建线程

通过实现Runnable接口创建线程

线程同步示例​

线程通信示例

网络编程

Java 高级技术

反射机制

动态代理

注解


异常处理​

        在 Java 编程的过程中,异常处理是保障程序稳健运行的关键环节。异常主要分为 Checked Exception(受检异常)和 Unchecked Exception(非受检异常)这两大类。​

        受检异常要求开发者在代码中必须显式地进行处理。以IOException为例,它常常在程序与外部资源进行交互时出现,像从文件读取数据或者建立网络连接这类操作。如果不处理IOException,程序在编译阶段就会报错,强制开发者考虑可能出现的问题并进行妥善处理。这是因为与外部资源交互时存在诸多不确定性,例如文件可能不存在、网络可能中断等,通过强制处理受检异常,能让开发者提前做好应对策略,提升程序的可靠性。​

        非受检异常,比如NullPointerException(空指针异常)和ArrayIndexOutOfBoundsException(数组越界异常),大多是由于编程逻辑上的失误导致的。编译器不会强制要求处理这类异常,但一旦它们在程序运行时出现,往往会致使程序崩溃。例如,当代码尝试访问一个null对象的方法或属性时,就会抛出NullPointerException。这类异常通常意味着代码中存在潜在的逻辑错误,需要开发者仔细排查和修复。​

        异常处理主要通过try-catch-finally块来实现。在try块中,放置的是可能会抛出异常的代码。当try块中的代码抛出异常时,程序流程会立即跳转到对应的catch块。每个catch块专门用于捕获并处理特定类型的异常。例如:

try {
    FileReader fileReader = new FileReader("nonexistent.txt");
} catch (FileNotFoundException e) {
    System.out.println("文件未找到,请检查路径");
} finally {
    // 这里可以关闭资源,即使fileReader为null也需处理,避免空指针异常
}

        在上述代码中,try块尝试创建一个FileReader对象来读取名为nonexistent.txt的文件。如果该文件不存在,就会抛出FileNotFoundException异常,此时程序会进入catch块,打印出提示信息。finally块则无论try块中是否发生异常,都会被执行。它通常用于释放资源,比如关闭文件流,确保资源的正确管理,避免资源泄漏。即使try块中由于文件不存在抛出异常,导致fileReader对象未能成功创建(即fileReader为null),在finally块中也需要进行相应的空指针检查,以确保安全地关闭可能存在的资源。​

        此外,开发者还可以使用throw关键字手动抛出异常,以便在特定的业务逻辑下,主动告知调用者出现了问题。例如,当某个业务规则不满足时,可以手动抛出一个自定义的异常。同时,通过throws声明方法可能抛出的异常,将异常处理的责任交给调用该方法的代码。这样,调用者在调用方法时,就需要明确知道可能会遇到的异常情况,并进行相应的处理。​

集合框架​

        Java 的集合框架为开发者提供了一系列丰富的接口和类,用于高效地存储和操作对象。该框架主要划分为Collection和Map两大体系。​

List 集合​

        List集合的特点是有序且允许元素重复。这意味着元素在集合中的存储顺序与它们被添加的顺序一致,并且可以存在多个相同的元素。在实际应用中,常用的List实现类有ArrayList和LinkedList。​

        ArrayList是基于数组实现的。数组的特性使得ArrayList在查询元素时具有很高的效率,因为可以通过元素的索引直接访问数组中的对应位置。例如:

ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("apple");
arrayList.add("banana");
String element = arrayList.get(0); // 高效获取元素
arrayList.add(1, "cherry"); // 插入元素,可能导致元素移动

        在上述代码中,首先创建了一个ArrayList对象,并向其中添加了两个元素"apple"和"banana"。当使用get(0)方法时,可以直接快速地获取到索引为 0 的元素"apple"。这是因为ArrayList内部维护了一个数组,通过索引可以直接定位到数组中对应位置的元素,时间复杂度为 O (1)。然而,当在列表中间位置(如索引为 1 的位置)插入新元素"cherry"时,ArrayList需要将索引 1 及之后的所有元素向后移动一个位置,以腾出空间插入新元素。如果列表中元素数量较多,这种移动操作会消耗较多的时间和资源,导致性能下降。例如,当列表中有大量元素时,每一次插入操作都可能需要移动大量元素,使得插入操作的时间复杂度变为 O (n),其中 n 为列表中元素的数量。​

        LinkedList则是基于链表结构实现的。链表由一系列节点组成,每个节点包含元素值以及指向下一个节点的引用(在双向链表中还包含指向前一个节点的引用)。这种结构使得LinkedList在进行插入和删除操作时表现出色。因为只需要修改相关节点的引用关系,而不需要像ArrayList那样移动大量元素。例如,在链表中删除一个元素,只需要将该元素前一个节点的next引用指向该元素的下一个节点即可。对于双向链表,还需要将后一个节点的prev引用指向该元素的前一个节点。这种操作的时间复杂度为 O (1),只涉及到对几个节点引用的修改。但在查询元素时,由于链表没有像数组那样的索引直接定位机制,需要从链表的头节点开始,依次遍历每个节点,直到找到目标元素,所以查询效率相对较低。在最坏情况下,查询一个不在链表中的元素需要遍历整个链表,时间复杂度为 O (n)。​

Set 集合​

        Set集合的特性是无序且不允许元素重复。这意味着元素在Set中的存储顺序并非按照添加顺序,并且集合中不会出现两个相同的元素。常见的Set实现类有HashSet和TreeSet。​

        HashSet是基于哈希表实现的。哈希表利用哈希函数将元素映射到特定的存储位置,这使得插入和查询操作在平均情况下具有 O (1) 的时间复杂度,性能表现非常优秀。为了保证元素的唯一性,HashSet要求存储的元素必须正确重写hashCode()和equals()方法。例如:

HashSet<Integer> hashSet = new HashSet<>();
hashSet.add(1);
hashSet.add(2);
hashSet.add(1); // 重复元素,不会被添加

        在这段代码中,首先创建了一个HashSet对象,并向其中添加了两个不同的整数1和2。当再次尝试添加已经存在的元素1时,HashSet会根据元素的哈希码和equals()方法来判断该元素是否已经存在于集合中。哈希函数会根据元素的值计算出一个哈希码,通过哈希码可以快速定位到哈希表中的一个位置。如果该位置已经有元素存在,就需要进一步通过equals()方法来比较两个元素是否相等。如果相等,则认为是重复元素,不会再次添加,从而保证了集合中元素的唯一性。如果元素没有正确重写hashCode()和equals()方法,可能会导致相同的元素被错误地认为是不同的元素,从而破坏集合的唯一性。​

        TreeSet是基于红黑树实现的。红黑树是一种自平衡的二叉搜索树,这使得TreeSet中的元素会按照一定的顺序排列。默认情况下,TreeSet会按照元素的自然顺序进行排序,例如对于整数类型,就是从小到大排序。同时,也可以通过传入自定义的Comparator接口实现,来指定特定的排序规则。例如,如果要对自定义的对象进行排序,可以实现Comparator接口,在compare()方法中定义对象之间的比较逻辑。这种有序性在需要对元素进行排序遍历的场景中非常有用,例如在统计数据、查找特定范围的元素等操作中。​

Map 集合​

        Map集合用于存储键值对(key - value pairs),其特点是一个键最多只能映射到一个值。在实际开发中,常用的Map实现类有HashMap、TreeMap和LinkedHashMap。​

        HashMap基于哈希表实现,具有较高的插入和查询效率。与HashSet类似,为了保证键的唯一性,键对象需要正确重写hashCode()和equals()方法。例如:

HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("one", 1);
hashMap.put("two", 2);
Integer value = hashMap.get("one");

        在上述代码中,创建了一个HashMap对象,并向其中添加了两个键值对,键分别为"one"和"two",对应的值分别为1和2。当使用get("one")方法时,可以快速地根据键"one"获取到对应的值1。HashMap通过哈希函数将键映射到哈希表中的特定位置,插入和查询操作在平均情况下时间复杂度为 O (1)。但如果哈希函数设计不合理,导致大量键映射到同一个位置(即哈希冲突严重),会使得插入和查询操作的性能下降,时间复杂度可能会接近 O (n)。​

        TreeMap基于红黑树实现,它会对键进行排序。这使得在遍历TreeMap时,键值对会按照键的顺序依次输出。排序方式可以是键的自然顺序,也可以通过自定义的Comparator来指定。这种有序性在需要对键值对进行有序处理的场景中非常有用。例如,在一个存储学生成绩的TreeMap中,以学生姓名作为键,成绩作为值。如果按照学生姓名的字母顺序遍历TreeMap,可以方便地查看成绩的分布情况。​

        LinkedHashMap继承自HashMap,它在HashMap的基础上,额外维护了一个双向链表来记录元素的插入顺序或者访问顺序。如果希望在遍历集合时,元素的顺序与它们被插入的顺序一致,或者按照访问顺序(最近访问的元素排在前面)进行遍历,LinkedHashMap就是一个很好的选择。例如,在实现一个缓存机制时,可以使用基于访问顺序的LinkedHashMap。当缓存满了需要移除元素时,可以移除最久未被访问的元素(即链表头部的元素),从而保证缓存中始终保留最近使用过的元素。​

文件与字符集​

        在 Java 中,File类主要用于处理文件和目录路径名。通过File类,开发者可以执行一系列操作,如创建新文件、删除文件、重命名文件或目录,以及检查文件或目录的属性。例如:

File file = new File("example.txt");
try {
    if (file.createNewFile()) {
        System.out.println("文件创建成功");
    } else {
        System.out.println("文件已存在");
    }
} catch (IOException e) {
    e.printStackTrace();
}

        在这段代码中,首先创建了一个File对象,指向名为example.txt的文件。然后使用createNewFile()方法尝试创建该文件。如果文件成功创建,该方法会返回true,并打印出相应的成功提示信息;如果文件已经存在,则返回false,并打印出文件已存在的提示。如果在创建文件过程中出现输入输出相关的错误,如磁盘空间不足、权限不够等,就会抛出IOException异常,通过catch块捕获并打印异常堆栈信息,以便开发者定位和解决问题。例如,如果磁盘空间已满,createNewFile()方法会抛出IOException,异常信息中会包含与磁盘空间相关的错误描述,帮助开发者快速找到问题所在。​

        字符集在处理文本数据时起着至关重要的作用。不同的字符集定义了如何将字符编码为字节序列,以及如何将字节序列解码为字符。Java 使用Charset类来表示各种字符集,常见的字符集有 UTF - 8、GBK 等。在读取和写入文本文件时,必须指定正确的字符集,否则可能会出现乱码问题。例如:

try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("file.txt"), "UTF - 8"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}

        在上述代码中,使用BufferedReader来读取文件内容。BufferedReader通过InputStreamReader与FileInputStream关联,并且指定了字符集为UTF - 8。这意味着在读取文件时,会按照UTF - 8字符集的规则将文件中的字节数据转换为字符数据。UTF - 8是一种广泛使用的字符集,它可以表示世界上几乎所有的字符,并且具有良好的兼容性和扩展性。如果文件实际采用的字符集与指定的UTF - 8不一致,就可能导致读取的内容出现乱码。例如,如果文件是使用 GBK 字符集编码的,而在读取时指定为UTF - 8,那么原本正确的中文字符可能会显示为乱码。在while循环中,通过readLine()方法逐行读取文件内容,并将每一行打印出来。如果在读取过程中出现输入输出错误,同样会抛出IOException异常,由catch块进行处理。​

IO 流​

        Java 的 IO 流体系分为字节流和字符流,它们为处理输入输出操作提供了不同的方式。​

        字节流以字节为单位处理数据,非常适合处理二进制数据,如图片、音频、视频等文件。字节流的主要类包括InputStream和OutputStream及其子类。以FileInputStream和FileOutputStream为例:

try (FileInputStream fis = new FileInputStream("source.jpg");
     FileOutputStream fos = new FileOutputStream("target.jpg")) {
    int b;
    while ((b = fis.read()) != -1) {
        fos.write(b);
    }
} catch (IOException e) {
    e.printStackTrace();
}

        在这段代码中,首先创建了FileInputStream对象fis,用于从source.jpg文件中读取字节数据;同时创建了FileOutputStream对象fos,用于将读取到的字节数据写入到target.jpg文件中。在while循环中,通过fis.read()方法每次读取一个字节的数据,并将其赋值给变量b。当read()方法返回-1时,表示已经读取到文件末尾。在循环内部,使用fos.write(b)方法将读取到的字节写入到目标文件中。这样就实现了文件的复制操作。如果在读取或写入过程中出现任何输入输出错误,都会抛出IOException异常,由catch块捕获并打印异常信息。例如,如果在读取过程中文件被意外删除,fis.read()方法会抛出IOException,提示文件不存在的错误。​

        字符流以字符为单位处理数据,更适合处理文本数据。字符流基于Reader和Writer类及其子类,如FileReader和FileWriter。字符流内部会根据指定的字符集进行字节和字符之间的转换。例如:

try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}

多线程​

        多线程技术允许程序同时执行多个任务,这极大地提高了程序的响应性和资源利用率。在 Java 中,创建线程主要有两种方式:继承Thread类和实现Runnable接口。​

通过继承Thread类创建线程

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " 打印: " + i);
            try {
                // 线程休眠100毫秒
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ThreadExample {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        // 启动线程
        thread.start();
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " 打印: " + i);
            try {
                // 线程休眠100毫秒
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

        在上述代码中,首先定义了一个MyThread类,它继承自Thread类。在MyThread类中重写了run()方法,这个方法是线程执行的主体。在run()方法中,通过一个for循环打印出当前线程的名称以及循环变量i的值。每次打印后,线程会调用Thread.sleep(100)方法进入休眠状态 100 毫秒,这模拟了线程执行一些耗时操作的情况。在main方法中,创建了MyThread类的实例thread,然后调用thread.start()方法启动线程。需要注意的是,不能直接调用thread.run()方法,因为这样只是在主线程中普通地调用一个方法,并不会启动一个新的线程。启动线程后,主线程也会执行一个类似的for循环,打印出主线程的相关信息。由于多线程执行的不确定性,主线程和新启动的线程的打印信息可能会交替出现。​

通过实现Runnable接口创建线程

class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " 打印: " + i);
            try {
                // 线程休眠100毫秒
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class RunnableExample {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        // 启动线程
        thread.start();
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " 打印: " + i);
            try {
                // 线程休眠100毫秒
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

        这种方式中,定义了一个MyRunnable类实现了Runnable接口,在MyRunnable类的run()方法中同样编写了线程的执行逻辑。在main方法中,首先创建了MyRunnable类的实例myRunnable,然后将这个实例作为参数传递给Thread类的构造函数,创建了一个Thread对象thread。最后调用thread.start()方法启动线程。实现Runnable接口相较于继承Thread类更具优势,因为 Java 不支持多重继承,通过实现接口可以避免继承带来的局限性,使得类可以同时实现多个接口,增强了代码的灵活性。​

线程同步示例​

        多线程编程中,线程安全问题是需要重点关注的。当多个线程同时访问共享资源时,如果没有进行适当的同步控制,可能会导致数据不一致等问题。例如,多个线程同时对一个共享的计数器进行增加操作,可能会出现计数不准确的情况。可以使用synchronized关键字来实现线程同步。

class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

class IncrementThread implements Runnable {
    private Counter counter;

    public IncrementThread(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            counter.increment();
        }
    }
}

public class SynchronizedExample {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        IncrementThread incrementThread = new IncrementThread(counter);
        Thread thread1 = new Thread(incrementThread);
        Thread thread2 = new Thread(incrementThread);

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println("最终计数: " + counter.getCount());
    }
}

线程通信示例

线程通信可以使用wait()notify()notifyAll()方法。

class Message {
    private String msg;
    private boolean empty = true;

    public synchronized String read() {
        while (empty) {
            try {
                wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        empty = true;
        notifyAll();
        return msg;
    }

    public synchronized void write(String msg) {
        while (!empty) {
            try {
                wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        empty = false;
        this.msg = msg;
        notifyAll();
    }
}

class Reader implements Runnable {
    private Message message;

    public Reader(Message message) {
        this.message = message;
    }

    @Override
    public void run() {
        for (String latestMessage = message.read(); !"EOF".equals(latestMessage); latestMessage = message.read()) {
            System.out.println("读取消息: " + latestMessage);
        }
    }
}

class Writer implements Runnable {
    private Message message;

    public Writer(Message message) {
        this.message = message;
    }

    @Override
    public void run() {
        String[] messages = {"消息1", "消息2", "消息3", "EOF"};
        for (String msg : messages) {
            message.write(msg);
            System.out.println("写入消息: " + msg);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

public class ThreadCommunicationExample {
    public static void main(String[] args) {
        Message message = new Message();
        Thread readerThread = new Thread(new Reader(message));
        Thread writerThread = new Thread(new Writer(message));

        readerThread.start();
        writerThread.start();
    }
}

        上述代码示例展示了创建线程的不同方式、线程同步以及线程通信的基本用法。通过继承Thread类和实现Runnable接口创建线程,使用synchronized关键字保证线程安全,利用wait()notify()notifyAll()方法实现线程间的协作。

网络编程

        Java 提供了丰富的网络编程类库,支持 TCP 和 UDP 协议。在 TCP 编程中,通过Socket类和ServerSocket类实现客户端和服务器端的通信。例如,一个简单的 TCP 服务器端:

try (ServerSocket serverSocket = new ServerSocket(8080);
     Socket socket = serverSocket.accept();
     BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
     PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {
    String inputLine;
    while ((inputLine = in.readLine()) != null) {
        System.out.println("收到客户端消息: " + inputLine);
        out.println("消息已收到");
    }
} catch (IOException e) {
    e.printStackTrace();
}

客户端通过创建Socket对象连接到服务器:

try (Socket socket = new Socket("localhost", 8080);
     PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
     BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
    out.println("Hello, Server!");
    String response = in.readLine();
    System.out.println("收到服务器响应: " + response);
} catch (IOException e) {
    e.printStackTrace();
}

        UDP 编程则使用DatagramSocket类和DatagramPacket类,适用于对实时性要求较高但对数据准确性要求相对较低的场景,如视频流、音频流传输。

Java 高级技术

反射机制

        反射允许程序在运行时获取类的信息,包括类的属性、方法、构造函数等,并可以动态创建对象、调用方法。通过Class类及其相关方法实现反射。例如:

try {
    Class<?> clazz = Class.forName("java.util.Date");
    Object object = clazz.newInstance();
    Method method = clazz.getMethod("getTime");
    long time = (long) method.invoke(object);
    System.out.println("当前时间毫秒数: " + time);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
    e.printStackTrace();
}

        反射在框架开发、动态代理等场景中广泛应用,但由于反射操作绕过了编译器的检查,性能相对较低,应谨慎使用。

动态代理

        动态代理是一种在运行时创建代理对象的机制,代理对象可以在不修改目标对象代码的情况下,对目标对象的方法调用进行增强。Java 的动态代理通过Proxy类和InvocationHandler接口实现。例如:

interface HelloService {
    void sayHello();
}

class HelloServiceImpl implements HelloService {
    @Override
    public void sayHello() {
        System.out.println("Hello!");
    }
}

class ProxyHandler implements InvocationHandler {
    private Object target;

    public ProxyHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("方法调用前增强");
        Object result = method.invoke(target, args);
        System.out.println("方法调用后增强");
        return result;
    }
}

public class Main {
    public static void main(String[] args) {
        HelloService target = new HelloServiceImpl();
        HelloService proxy = (HelloService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new ProxyHandler(target));
        proxy.sayHello();
    }
}

        动态代理在 AOP(面向切面编程)中起着核心作用,用于实现日志记录、事务管理等横切关注点。

注解

        注解是 Java 5.0 引入的一种元数据机制,可以为程序元素(类、方法、字段等)添加额外的信息。注解分为预定义注解(如@Override@Deprecated@SuppressWarnings)和自定义注解。自定义注解需要使用@interface关键字定义,并且可以通过反射读取注解信息。例如:

@interface MyAnnotation {
    String value();
}

@MyAnnotation("This is a test")
class MyClass {
    // 类体
}

public class Main {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        Class<?> clazz = myClass.getClass();
        MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
        if (annotation != null) {
            System.out.println(annotation.value());
        }
    }
}

        注解在框架开发中被大量使用,用于配置、代码生成等场景,提高了代码的可读性和可维护性。

        通过对上述 Java 基础进阶知识的深入学习和实践,能够显著提升 Java 编程能力,为开发复杂、高效的 Java 应用程序奠定坚实基础。

=================Java基础知识到此完结================= 

至此结束——

我是禹曦a

期待与你的下次相遇!!!

相关文章:

  • CPT208 Human-Centric Computing 人机交互 Pt.2 Prototype(原型)
  • 算力驱动未来:从边缘计算到高阶AI的算力革命
  • 嵌入式笔试(一)
  • Web应用权限绕过与横向移动
  • 【用Cursor 进行Coding 】
  • LU分解原理与C++实现:从理论到实践
  • NO.76十六届蓝桥杯备战|数据结构-单调栈|发射站|Largest Rectangle in a Histogram(C++)
  • 欧税通香港分公司办公室正式乔迁至海港城!
  • Dify平台
  • 企业级防火墙与NAT网关配置
  • SCimilarity:对人类相似细胞进行可扩展搜索的细胞图谱基础模型
  • 软件反模式全解手册(26种核心模式详解)
  • 【AI提示词】决策专家
  • reid查找余弦相似度计算修正(二)
  • python-64-前后端分离之图书管理系统的Vue前端
  • 面向对象(OOP)
  • 跨浏览器 Tab 通信工具-emit/on 风格 API(仿 mitt)
  • 【Unity】Unity Transform缩放控制教程:实现3D模型缩放交互,支持按钮/鼠标/手势操作
  • Python 快速搭建一个小型的小行星轨道预测模型 Demo
  • 裴蜀定理扩展欧几里得定理