高频常问面试问题

目录

Java[虚拟机、GC、序列化、多线程(单例、死锁)、Collection]

JAVA虚拟机反射机制-什么是反射?

java虚拟机反射机制

1 空指针异常怎么预防

  • 避免使用未初始化的变量或引用
  • 进行判空操作
  • 安全调用操作符(?.): Java 14中引入。
    • String value = object?.getValue();
  • 使用断言: 在代码中使用断言来验证前置条件,确保对象引用不为空。
    • assert object != null;

2 内存泄漏和内存溢出的区别。

  • 内存泄漏:是指在程序中分配的内存无法被正常释放和回收。
    • 内存泄漏通常是由于程序中存在未正确释放的资源,例如对象、数组、文件句柄等。
    • 解决内存泄漏的关键是在不再需要使用某个资源时,确保正确释放它。这可能涉及到关闭文件、释放对象引用、解除循环引用等操作。
  • 内存溢出:是指程序分配的内存超出了系统或应用程序的限制,导致无法继续分配所需的内存空间。这通常会导致程序崩溃或异常终止。内存溢出可以发生在堆内存或栈内存上。
    • 堆内存溢出: 当程序在堆内存中分配了大量对象,而没有足够的空间来容纳这些对象时,就会发生堆内存溢出。这通常是由于程序中存在内存泄漏、对象生命周期过长等情况引起的。
    • 栈内存溢出: 栈内存用于存储方法调用的信息和局部变量等。当程序中存在过多的方法调用或者递归调用没有终止条件时,栈内存可能会溢出。
    • 解决内存溢出的方法通常是优化代码,减少内存使用量,或者在涉及递归的情况下,确保存在合适的终止条件。
  • 内存泄漏是指未能释放不再使用的内存资源,导致内存占用不断增加,而内存溢出则是指内存分配超过可用内存大小,导致程序异常终止。

3 深拷贝和浅拷贝-列表实现深拷贝有哪些方法

  • 浅拷贝:浅拷贝是指在复制对象时,只复制对象本身及其包含的所有基本数据类型的成员变量。对于对象引用类型的成员变量,浅拷贝只复制引用,而不会创建新的对象实例。这意味着原对象和浅拷贝后的对象共享相同的引用,对其中一个对象所做的修改会影响另一个对象。
  • 深拷贝(Deep Copy):
    深拷贝是指在复制对象时,除了复制对象本身,还会递归地复制对象引用的所有子对象。这样,原对象和深拷贝后的对象之间完全独立,修改一个对象不会影响另一个对象。
  • 要实现深拷贝,可以手动复制每个对象的成员变量,使用序列化/反序列化,或者使用第三方库来处理复制。
    • 使用序列化和反序列化来实现深拷贝的步骤如下:
      • 1.确保需要深拷贝的类和其引用的所有类都实现了Serializable接口。
      • 2.通过比特数组输出流外套对象输出流和比特数组输入流外套对象输入流,写和读io数据,以此实现深拷贝。
    • 使用第三方库
      • 1.确保你的类和其引用的所有类都实现了 Serializable 接口。
      • 2.使用 Apache Commons Lang 库中的 SerializationUtils.clone() 方法进行深拷贝。

4 抽象类和接口的区别

  • 定义:
    • 抽象类:抽象类是一个类,它可以包含抽象方法和具体方法。抽象类可以有成员变量、构造方法,也可以有普通方法。抽象类本身不能被实例化,只能被继承。
    • 接口:接口是一种完全抽象的类,它只包含方法的声明,没有成员变量。接口中的所有方法默认都是公共的(public),所有方法都需要被实现。
  • 多继承:
    • Java中一个类只能继承一个抽象类,但可以实现多个接口。
  • 构造方法:
    • 抽象类:可以有构造方法,它们在子类实例化时会被调用。
    • 接口:不能直接包含构造方法,因为接口不能被实例化。
  • 字段和常量:
    • 抽象类:可以包含字段(成员变量)和常量。
    • 接口:只能包含常量,不能包含字段。
  • 访问修饰符:
    • 抽象类:可以有各种访问修饰符的方法(public、protected、private等)。
    • 接口:接口中的方法默认为 public,且不能使用其他访问修饰符。

5 多线程的好处

  • 充分利用多核处理器,实现高性能高并发
  • 实现资源共享,提高资源利用率
  • 实现异步编程,解决削峰问题
  • 实现任务分解,高效利用资源
  • 实现系统的实时性要求
  • 尽管多线程有很多优点,但同时也存在一些挑战和风险,如线程安全问题、死锁、竞态条件等。
  • 多线程基本使用
public class MultiThreadExample {
    public static void main(String[] args) {
        // 创建并启动第一个线程
        Thread thread1 = new Thread(new TaskRunnable(1));
        thread1.start();
        
        // 创建并启动第二个线程
        Thread thread2 = new Thread(new TaskRunnable(2));
        thread2.start();
        
        // 等待两个线程完成
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("All tasks completed");
    }
}

class TaskRunnable implements Runnable {
    private int taskId;
    
    public TaskRunnable(int taskId) {
        this.taskId = taskId;
    }
    
    @Override
    public void run() {
        System.out.println("Task " + taskId + " started");
        
        // 模拟耗时操作
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("Task " + taskId + " completed");
    }
}
//join() :等待线程完成,当一个线程调用另一个线程的 join() 方法时,调用线程会等待被调用线程完成其任务,然后继续执行。这在需要确保某些线程的顺序执行时非常有用。

6 Java GC

  • Java中的"GC"代表"垃圾回收"(Garbage Collection),它是一种自动内存管理机制,用于在运行时自动识别和回收不再被程序使用的内存,以避免内存泄漏和提高应用程序的性能。

  • Java中的GC主要有一下几个阶段

    • 标记:垃圾回收器首先标记出所有仍然被程序使用的对象,这些对象被视为“存活”。
    • 清除:垃圾回收器会扫描内存中的所有对象,将未标记的对象(即未被引用的对象)标记为垃圾,并进行清除。清除的对象所占用的内存将被释放。
    • 整理:在清除过程中,会留下一些不连续的内存块。在整理阶段,垃圾回收器会将存活的对象移动到一侧,从而产生连续的内存块,以便在后续的内存分配中提供连续的内存区域。
  • GC 具体算法:

    • (1)标记-清除算法:效率不高,产生碎片,大对象无法分配。
    • (2)标记-整理算法:让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
    • (3)复制算法:内存分为相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过
    • (4)分代管理: 新生代:采用复制算法。 老年代:采用标记清理算法或则标记整理算法。
  • Java的垃圾回收器,主要有以下几种类型

    • 串行收集器:串行收集器适用于单线程环境,它会使用单个线程进行垃圾回收。在新生代进行Minor GC时,串行收集器会使用单个线程进行垃圾回收操作。

    • 并行收集器:并行收集器适用于多核CPU环境,允许多个线程并行执行垃圾回收。在新生代进行Minor GC时,并行收集器会使用多个线程并行地进行垃圾回收,从而加速回收过程。

    • CMS(Concurrent Mark-Sweep)收集器:CMS收集器以减少停顿时间为目标,它在标记和清除阶段尽量与应用程序同时进行。CMS主要应用于老年代的回收,在进行Full GC时,CMS会尝试在标记和清除阶段与应用程序并发地执行,以减少对应用程序的影响。

    • G1(Garbage-First)收集器:G1收集器面向大堆、低停顿时间的收集器,它将内存划分为多个区域,以便更加灵活地管理内存。G1在新生代和老年代都可以进行垃圾回收,而且它的回收过程会自适应地根据堆内存使用情况来调整。

    • ZGC和Shenandoah:ZGC和Shenandoah是Java 11及以后版本引入的新型垃圾收集器,旨在减少GC停顿时间,特别是对大内存堆来说。它们在进行垃圾回收时,会尽量缩短停顿时间,以提高应用程序的响应性。

  • Minor GC(小型垃圾回收):Minor GC 主要发生在新生代中。当新生代中的Eden区满了时,会触发Minor GC。在Minor GC过程中,垃圾回收器会清理Eden区和一个Survivor区中不再被引用的对象,并将存活的对象复制到另一个Survivor区。Minor GC的目标是尽量快速地清理短生命周期对象,减少内存碎片。

  • Full GC(完全垃圾回收):Full GC,也称为Major GC(主要垃圾回收),涉及整个堆内存,包括新生代和老年代。Full GC通常发生在老年代空间不足、永久代/元空间满了或显式调用System.gc()方法时。Full GC的目标是清理整个堆内存中的垃圾对象,从而回收更多的内存。

7 Java 特性

  • 面向对象编程(OOP)
  • 跨平台
  • 自动内存管理
  • 多线程支持

8 为什么 Java 是单继承

  • 避免菱形继承问题:多继承可能导致菱形继承问题,即一个类继承了两个具有共同祖先的类,从而在继承链中出现歧义。这可能导致方法和属性的二义性,使代码难以理解和维护。
  • 简化语言和编译器设计:单继承简化了类之间的继承关系,减少了编译器和运行时的复杂性。它使得类的层次结构更清晰,易于解析和管理。

9 单例模式

  • 单例模式是一种设计模式,用于确保一个类只能创建一个实例,并提供全局访问该实例的方式。
  • 在单例模式中,类的构造函数是私有的,从而阻止直接通过构造函数创建多个实例。而通过一个静态方法或属性,类提供了一种获取单一实例的机制。
    • 懒汉式单例模式是在实际需要的时候才创建实例。
    • 饿汉式单例模式是一种在类加载时就创建实例的方式。
  • 懒汉式单例实现
public class Singleton {
    private static Singleton instance;
    
    private Singleton() {
        // 私有构造函数,防止外部创建实例
    }
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
-------------------------------------------------------------------------------------
/*需要注意的是,懒汉式的单例模式在多线程环境下可能引发线程安全问题。
如果多个线程同时调用 getInstance() 方法,可能会导致创建多个实例。
可以通过添加同步锁(synchronization)或使用双重检查锁定(double-checked locking)等方式来解决这个问题。*/
//第一种解决多线程问题的解决办法。
public class Singleton {
    private static Singleton instance;

    private Singleton() {
        // 私有构造函数,防止外部创建实例
    }
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
--------------------------------------------------------------------------------------
/*需要注意的是,在 Java 5 及以后,可以使用 volatile 关键字来确保变量的可见性,从而进一步增加懒汉式单例模式的线程安全性。*/
//第二种解决多线程的办法。
public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {
        // 私有构造函数,防止外部创建实例
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

  • 饿汉式单例模式实现
public class Singleton {
    // 在类加载时就创建单例对象
    private static Singleton instance = new Singleton();
    
    // 私有构造方法,防止外部实例化
    private Singleton() {
    }
    
    // 提供一个公共方法来获取单例对象
    public static Singleton getInstance() {
        return instance;
    }
    
    // 其他方法和属性
    // ...
}

10 JVM的内存溢出情况分析(堆和栈)

  • 堆内存溢出原因
    堆内存是用来存储对象实例的区域,包括新生代和老年代,堆内存溢出通常发生在以下情况下:

    • 对象创建过多
    • 对象保留不释放
  • 栈内存溢出原因
    栈内存用于存储方法的调用栈、局部变量和方法参数等。栈内存溢出通常发生在以下情况下:

    • 方法调用层次过深:递归调用或者方法嵌套层次过深,导致栈内存不断增长,最终超出了栈的容量。
    • 局部变量过多:某个方法中声明了过多的局部变量,导致栈内存被占用完毕。
  • 解决内存溢出问题

    • 堆内存溢出:检查代码中的对象创建和释放,确保不再需要的对象能够被及时回收。使用内存分析工具来识别内存泄漏的情况。
    • 栈内存溢出:优化递归调用和方法嵌套,确保调用层次不会过深。合理使用局部变量,避免在方法中声明过多的局部变量。
  • 虚拟机内存设置(调参)

    • java -Xmx1024m -Xms512m YourMainClass

11 JVM运行时数据区

  • 方法区(Method Area)或元空间(Metaspace):方法区在 Java 8 以前称为永久代,现在称为元空间。方法区用来存储类的元数据、静态变量、常量池、方法代码等。元空间在堆外分配内存,由操作系统管理。方法区或元空间的大小可以通过命令行参数进行设置。

  • 堆内存(Heap Memory):堆内存用来存储对象实例,包括新生代和老年代。新生代又分为 Eden 区和两个 Survivor 区,主要用于存放新创建的对象。老年代用于存放生命周期较长的对象。堆内存管理由垃圾回收器负责,通过回收不再使用的对象来保持堆内存的可用性。

  • 栈内存(Stack Memory):栈内存用于存储方法调用栈、局部变量和方法参数等。每个线程在运行时都有一个独立的栈,用来跟踪方法的调用和返回。栈内存的大小在创建线程时指定,每个方法调用会在栈上分配一个栈帧。

    • 程序计数器(Program Counter):每个线程都有一个程序计数器,用于指示当前执行的字节码指令。在线程切换时,程序计数器的值会被保存和恢复。

    • 本地方法栈(Native Method Stack):本地方法栈用于支持本地方法的调用,本地方法是使用其他语言编写的,如C、C++等。本地方法栈与 Java 方法栈类似,但是用于本地方法的调用。

12 栈帧

  • 局部变量区(Local Variables):局部变量区存储了方法中的局部变量,包括基本数据类型和对象引用。每个局部变量在栈帧中都有一个对应的槽位,它可以存储一个值。

  • 操作数栈(Operand Stack):操作数栈用来存储方法执行过程中的操作数。在方法执行过程中,运算符和操作数从操作数栈中取出,计算结果再次入栈。操作数栈在方法调用时创建,方法返回时销毁。

  • 动态链接(Dynamic Linking):动态链接用来指向方法的运行时常量池中的方法符号引用,以支持方法调用的动态分派。在编译期间,方法调用会被解析成符号引用,而在运行时通过动态链接将其转换为实际方法的直接引用。

  • 方法返回地址(Return Address):方法返回地址记录了方法调用完成后的返回地址,以便程序能够从被调用方法返回到正确的位置。

  • 实际返回值(Actual Return Value):在方法执行完成后,实际的返回值会被存储在栈帧中,以便被调用方法获取。

  • 帧数据区(Frame Data):帧数据区存储了方法的一些附加信息,如异常处理器表、局部变量表长度等。这些信息在方法调用和异常处理时会被使用。

13 构造函数的特点?(饿了么)

  • 与类同名。
  • 没有返回类型。
  • 自动调用。
  • 可重载。

14 Java的基本数据类型

  • 整数类型:
    • byte:8 位,有符号。取值范围:-128 到 127。
    • short:16 位,有符号。取值范围:-32768 到 32767。
    • int:32 位,有符号。取值范围:-2147483648 到 2147483647。
    • long:64 位,有符号。取值范围:-9223372036854775808 到 9223372036854775807。
  • 浮点数类型:
    • float:32 位,单精度浮点数。取值范围:约 -3.4e38 到 3.4e38,精度约为 7 位小数。
    • double:64 位,双精度浮点数。取值范围:约 -1.7e308 到 1.7e308,精度约为 15 位小数。
  • 字符类型:
    • char:16 位,表示一个 Unicode 字符。取值范围:‘\u0000’ 到 ‘\uffff’。
  • 布尔类型:
    • boolean:表示真或假的值。只有两个值:true 或 false。

15 Int和integer 的区别

  • 基本数据类型 vs 包装类:
    • int 是原始的数据类型,没有方法和属性。
    • Integer 是 int 的包装类,可以使用方法和属性。
  • Null 值处理
    • int 是基本数据类型,不能表示为 null。
    • Integer 是一个对象,可以为 null。
  • 性能差异
    • int 是基本数据类型,直接存储在栈中,因此操作效率较高。
    • Integer 是一个对象,存储在堆内存中,因此比基本数据类型 int 在操作时更耗费内存和时间。

16 Integer的equals()比较的是什么?

  • Java的集合有哪些?
    • List:有序的可重复集合,可以根据索引访问元素。常见实现类有 ArrayList、LinkedList 和 Vector。
    • Set:无序的不可重复集合,不允许存在重复元素。常见实现类有 HashSet、LinkedHashSet 和 TreeSet。
    • Queue:队列接口,支持在一端插入元素,在另一端删除元素。常见实现类有 LinkedList 和 PriorityQueue。
    • Deque:双端队列接口,可以在两端插入和删除元素。常见实现类有 ArrayDeque 和 LinkedList。
  • 映射(Map):
    • Map:键值对映射的集合,存储唯一的键和对应的值。常见实现类有 HashMap、LinkedHashMap、TreeMap 和 Hashtable。
  • 工具类:
    • Collections:提供了一系列静态方法,用于操作集合,如排序、查找、替换等。

17 List和set的区别是什么?

  • List:有序的可重复集合
  • Set:无序的不可重复集合

18 List为什么有序可以重复,set为什么不可以

  • List 和 Set 是根据不同的需求和用途设计的集合类型

19 Java的重载和重写的区别?

  • 重载是在同一个类中允许有多个同名方法,但参数不同,用于提供不同的方法签名。
  • 重写是子类重新定义与父类相同的方法,用于实现多态和子类的自定义实现。
  • 重载是在编译时静态决定的,根据传入的参数类型和个数来决定调用哪个方法。
  • 重写是在运行时动态决定的,根据对象的实际类型来调用对应的方法。

20 重写的范围为什么要大于被重写的?

  • 为了保证面向对象编程的多态性即子类能够向上转型。

21 Sql的左右连接的区别?

  • 左连接是以左表为基准,将左表中的每一行与右表中匹配的行连接在一起,无论右表中是否有匹配的行。
  • 右连接是以右表为基准,将右表中的每一行与左表中匹配的行连接在一起,无论左表中是否有匹配的行。

22 Sql的左右连接如果查到不存在的数据呢

  • 在左连接中,左表的所有行都会被保留,而右表中没有匹配的行将会以 NULL 值填充。
  • 在右连接中,右表的所有行都会被保留,而左表中没有匹配的行将会以 NULL 值填充。

23 装饰器是什么,多个装饰器的使用是怎么样的

  • 可以更灵活地组合对象的行为。

24 lombok的一个坑知不知道是什么【小红书】

  • Lombok 是一个 Java 库,旨在通过注解减少 Java 代码的样板代码。它可以自动生成构造函数、getter、setter、equals、hashCode 等方法,从而简化开发过程。然而,使用 Lombok 时可能会遇到一些潜在的问题,其中一个比较常见的“坑”是与 IDE 插件的兼容性问题。

25 jdk8 HashMap的扩容的机制【小红书】

  • 在JDK8中,HashMap 的初始容量为16,负载因子为0.75。也就是说,当HashMap中的元素数量超过12时,就会触发扩容操作。
    • 1.创建一个新的数组,容量为原数组的两倍。新数组的容量总是为2的幂次方,这样可以保证哈希函数的计算结果能够更均匀地分布在新的数组中。
    • 2.遍历原数组中的每个元素,将其重新计算在新数组中的位置,并将其存储在新数组中。
    • 3.将新数组设置为HashMap的内部数组,并更新相关的属性。

26 公平锁和非公平锁分别解释一下是什么【小红书】

  • 锁会按照线程的等待时间先后顺序分配给这些线程,即先等待的线程先获取锁。
  • 线程获取锁的顺序是无序的,不考虑线程等待的时间先后。

27 死锁【小红书】

什么是死锁?

  • 多个进程/线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于进程/线程被无限期地阻塞,因此程序不可能正常终止。

死锁的四个必要条件?

  • 互斥条件
  • 不可剥夺条件
  • 持有并请求条件
  • 循环等待条件

解决死锁的方法?

  • 预防,避免,检测,解除
  • 预防:
    • 静态分配策略:破坏请求并持有条件:一次性申请完所有资源。
    • 层次分配策略:破坏循环等待条件:一个进程得到某一次的一个资源后,它只能再申请较高一层的资源;当一个进程要释放某层的一个资源时,必须先释放所占用的较高层的资源。
  • 避免:
    • 银行家算法 通过先 试探 分配给该进程资源,然后通过 安全性算法 判断分配后系统是否处于安全状态。
  • 检测:
    • 如果进程-资源分配图中无环路,则此时系统没有发生死锁
    • 如果进程-资源分配图中有环路,且每个资源类仅有一个资源,则系统中已经发生了死锁。
    • 如果进程-资源分配图中有环路,且涉及到的资源类有多个资源,此时系统未必会发生死锁。如果能在进程-资源分配图中找出一个 既不阻塞又非独立的进程 ,该进程能够在有限的时间内归还占有的资源,则不会发生死锁,否则会发生死锁。
  • 解除:
    • 立即结束所有进程的执行,重新启动操作系统
    • 撤销涉及死锁的所有进程,解除死锁后继续运行
    • 逐个撤销涉及死锁的进程,回收其资源直至死锁解除
    • 抢占资源:把夺得的资源再分配给涉及死锁的进程直至死锁解除。

28 引用和指针区别

  • 引用是变量的别名,即引用和它所引用的变量是同一个东西,没有自己的内存地址(不可重新绑定)。
  • 指针是一个变量,它存储了一个变量的内存地址(可以重新指向)。

29 java序列化

  • Java 序列化是一种将对象转换为字节流,Java 提供了 java.io.Serializable 接口,通过实现这个接口,你可以使你的类变为可序列化的。要进行序列化和反序列化,你可以使用 ObjectOutputStream 和 ObjectInputStream 类。

30 栈内存和堆内存,哪个周期久(堆)

  • 栈内存的生命周期相对较短,主要用于方法的执行和局部变量的存储,而堆内存的生命周期相对较长,用于存储对象实例

31 红黑树和二叉平衡树的区别

  • 平衡二叉树是一类概念,而红黑树是平衡二叉树的一种具体实现。
  • 红黑树是一种自平衡二叉搜索树,通过颜色标记和旋转操作来保持平衡,而其他平衡二叉树(如 AVL 树)使用不同的平衡策略。
  • 红黑树相对于一些其他平衡二叉树来说,插入和删除操作的实现相对复杂,但在实际应用中,红黑树在综合性能上表现良好,被广泛应用于各种数据结构和算法中。

计算机网络

TCP三次握手和四次挥手(非常重要)?

  • 三次握手:一次请求,两次确认。
    • 客户端向服务端发送同步请求,SYN(SQE=x);
    • 服务端向客户端发送同步和确认请,ACK(SEQ=x+1),SYN(SEQ=y);(回传 SYN 则是为了建立并确认从服务端到客户端的通信。)
    • 客户端向服务端发送确认请求,ACK(SEQ=y+1);
  • 四次挥手:两次请求,两次确认。
    • 客户端向服务端发送终结请求:FIN(SEQ=x);
    • 服务端向客户端发送确认请求:ACK(SEQ=x+1);
    • (可能服务端还有部分数据正在传输。)
    • 服务端向客户端发送终结请求:FIN(SEQ=y);
    • 客户端向服务端发送确认请求:ACK(SEQ=y+1);

TCP如何保证传输的可靠性?(重要)

  • 基于数据块传输(数据块:报文段或段)
  • 对失序数据包重新排序以及去重(每个包一个序列号)
  • 校验和(首部和数据的检验和)
  • 超时重传(发送数据后,启动定时器)
  • 流量控制(TCP 的接收端只允许发送端发送接收端缓冲区能接纳的数据:滑动窗口
  • 拥塞控制(慢启动,拥塞避免,超时重传,快重传,快恢复)
    在这里插入图片描述

TCP可靠性传输-滑动窗口机制

  • 滑动窗口基本思想:如果客户端不急着接收ACK,可以利用等待ACK的空挡直接发送下一批数据,这样就可以做到短时间内发送多批数据。我们把需要发送的多批数据的集合看成窗口。
  • 理解滑动窗口的3个关键点:
    • 1)滑动窗口大小如何确定?
      • 滑动窗口大小如何确定?滑动窗口大小依据服务端接收缓冲区的大小确定。
      • 滑动窗口大小为0时,何时才能继续发送数据?
        • 策略一:发送方定期发送不携带数据的报文,接收方返回携带窗口大小的ACK,以此判断是否继续发送数据。
        • 策略二:接收方缓冲区一旦更新立马通知发送方。
    • 2)滑动窗口如何滑动?
      • 滑动窗口根据来自接收方的确认序列号确定左边界,根据接受方缓冲区大小+左边界确认右边界。
        • 例如:服务端收到了1001~2000的数据,那就回复确认序号2001+ACK,表明2001之前的数据已经全部收到,下一次从2001的字节位置开始发;
    • 2)滑动窗口数据传输过程中遇到的两个问题如何解决?
      • 客户端发送的报文丢了(快重传机制)
        • 服务端即便收到了3001~4000的数据,等了一段时间依然没有收到2001~3000的数据,说明2001~3000的报文丢了。此时发送的确认序号不是4001,而是2001,表明目前只收到了2001之前的数据。
        • 站在客户端的角度,客户端如果连续多次(一般是连续三次)收到了2001的确认应答,说明序号为2001之后的报文丢了,此时就会重传2001~3000的报文。服务端收到2001~3000的报文以后,由于之前已经收到了3001~4000 的报文(暂时存放在接收缓冲区),那么服务端相当于已经收到了全部的报文,此时服务端发送的确认序号是 4001,表明4001之前的报文已经全部收到,下一次可以从第4001个位置开始发。
        • 这种重发机制被称为“快重传”,与超时重传不同的是,超时重传是等待60s以后还没有收到ACK,此时客户端就会重新发送报文;而快重传更注重效率,只要收到多个相同的确认序号,就立马重传。
      • 服务端发送的ACK丢了(先收到了排序靠后的ACK)
        • 客户端没有收到2001~3000,但是收到了3001~4000的ACK。没关系,因为服务端必须是按序号回复的,如果服务端没有收到2001~3000的数据,根本不会发送3001~4000的ACK,因此可以认为 服务端已经收到了三批数据。此时滑动窗口的左边界向左移动至4001,同时根据对方接收缓冲区剩余空间大小移动右边界。
  • HTTP 和 HTTPS 区别-怎么加密?
  • 安全性:
    • HTTP:传输数据是明文的,不提供数据加密,容易被中间人窃听和篡改。
    • HTTPS:传输数据经过 SSL/TLS 加密,提供数据的机密性和完整性,防止中间人攻击。
  • 端口:
    • HTTP:默认端口为 80。
    • HTTPS:默认端口为 443。
  • 怎么加密:
      1. 首先由客户端向服务器端发送使用的协议的版本号、一个随机数和可以使用的加密方法。
      1. 服务器端收到后,确认加密的方法,也向客户端发送一个随机数和自己的数字证书。
      1. 客户端收到后,首先检查数字证书是否有效,如果有效,则再生成一个随机数,并使用证书中的公钥对随机数加密,然后发送给服务器端,并且还会提供一个前面所有内容的 hash 值供服务器端检验。
      1. 服务器端接收后,使用自己的私钥对数据解密,同时向客户端发送一个前面所有内容的 hash 值供客户端检验。
    • 注意:按照之前所约定的加密方法,使用这三个随机数生成一把秘钥,以后双方通信前,就使用这个秘钥对数据进行加密后再传输。

TCP、UDP的区别和特点

  • TCP
    • 可靠性:TCP 提供可靠的数据传输,通过确认、重传、拥塞控制等机制确保数据的准确传输。
    • 有序性:TCP 保持数据的有序性,确保数据按发送顺序到达。
    • 有连接:三次握手建立连接/四次挥手断开连接。
    • 面向字节流:TCP 将数据视为字节流进行传输,没有数据报大小的限制。
    • 适用场景:
      • 适用于要求数据传输可靠性、有序性的场景,如文件传输、网页浏览、电子邮件等。
  • UDP
    • 不可靠:UDP 不提供数据传输的可靠性,数据传输过程中可能丢失或重复。
    • 无序性:UDP 不保证数据的有序性,数据到达的顺序不固定。
    • 无连接:UDP 不需要建立连接,数据发送和接收无需握手。
    • 面向报文:UDP 将数据视为数据报进行传输,每个数据报大小有限。
    • 适用场景:
      • 适用于要求低延迟、实时性较高的应用,如在线游戏、实时视频流、语音通话等。

计网osi七层模型,每一层的协议

  • 应用层(Application Layer):
    • 提供应用程序间的通信服务,为用户提供各种网络服务。
    • 应用层的数据单位是消息(Message)。
    • 常见协议:HTTP、SMTP、FTP。
  • 表示层(Presentation Layer):
    • 处理数据的表示、加密和压缩,确保不同系统间数据的正确解释。
    • 负责数据格式转换、加密解密、数据压缩等。
  • 会话层(Session Layer):
    • 管理不同设备之间的会话,负责建立、维护和终止会话。
    • 提供会话控制、同步和对话管理等功能。
  • 传输层(Transport Layer):
    • 提供端到端的通信,为应用层提供可靠的数据传输和错误检测。
    • 传输层的数据单位是报文段(Segment)。
    • 常见协议:TCP、UDP。
  • 网络层(Network Layer):
    • 负责网络间的路由选择和数据包转发,实现逻辑地址(IP 地址)寻址。
    • 网络层的数据单位是数据包(Packet)。
    • 常见协议:IP、ICMP、OSPF。
  • 数据链路层(Data Link Layer):
    • 提供可靠的点对点数据传输,通过物理地址(MAC 地址)寻址。
    • 数据链路层的数据单位是帧(Frame)。
    • 常见协议:Ethernet、Wi-Fi、PPP。
  • 物理层(Physical Layer):
    • 负责传输比特流,处理物理接口和传输媒介。
    • 物理层的数据单位是比特。
    • 没有特定的协议,而是涉及物理信号、电压、频率等方面的规范。

为什么要分层

  • 分层结构是一种使计算机网络更加可控、可维护、可扩展的设计方式,有助于在复杂的网络通信环境中实现高效、可靠的数据传输。

http的报文分为哪些部分

  • HTTP 请求报文:
    • 请求行: 包含请求方法、请求的资源路径(URI)和协议版本。
    • 请求头部: 包含关于请求的附加信息,如客户端信息、认证、缓存控制等。
    • 空行: 用于分隔请求头和请求体。
    • 请求体: 可选,包含实际要发送给服务器的数据,例如 POST 请求中的表单数据。
  • HTTP 响应报文
    • 状态行: 包含协议版本、状态码和对应的状态消息。
    • 响应头部: 包含关于响应的附加信息,如服务器信息、内容类型、缓存控制等。
    • 空行: 用于分隔响应头和响应体。
    • 响应体: 包含服务器返回给客户端的实际数据,例如 HTML 内容、图像、文本等。

请求头的作用是什么

  • 帮助服务器根据客户端的需求进行处理。服务器可以根据这些头部来调整返回的响应内容、处理认证、实现缓存控制等各种功能,从而更好地满足客户端的请求。
    • User-Agent: 这个头部包含了发送请求的客户端的信息,如浏览器的名称、版本、操作系统等。服务器可以根据这个信息来为不同的客户端提供适当的内容,进行用户代理检测。

    • Authorization: 当使用身份认证时,这个头部包含了认证凭证,如基本认证的用户名和密码,或者令牌。服务器通过这个头部来验证客户端的身份。

    • Accept 和 Content-Type: Accept 头指定客户端能够接受的响应内容类型,而 Content-Type 头指定请求体的内容类型。这样服务器就知道如何提供适合的响应内容或解析请求体。

    • If-Modified-Since 和 If-None-Match: 这些头部用于缓存控制。客户端可以通过这些头部告诉服务器,只有在资源自从某个时间以来有所更改,或者资源的ETag值不匹配时,才需要返回最新的数据。

    • Range 和 If-Range: Range 头部用于指定客户端请求资源的某个范围,通常用于支持断点续传。If-Range 则配合 Range 头部,用于判断是否需要返回整个资源。

    • Origin: 当浏览器发起跨域请求时,这个头部包含了源(发起请求的页面的域名),服务器可以根据这个头部来判断是否允许跨域访问。

    • Referer: 这个头部包含了当前请求是从哪个页面链接过来的,可以帮助服务器分析来源页面,用于统计和分析等用途。

    • Cookie: 如果客户端有保存的 Cookie,这个头部会包含这些 Cookie 数据,服务器可以根据这些数据来识别用户和保持会话状态。

跨域请求

  • Origin 是一个 HTTP 请求头部,用于指示发起跨域请求的页面的源(域名)。在浏览器中,Origin 头部通常由浏览器自动添加,并包含了当前页面的源信息。取值一般是当前页面的域名、协议和端口号的组合。

    • Origin 头部的取值示例:

      • https://www.example.com
      • http://sub.example.com:8080
      • http://localhost:3000
    • 这些取值由协议(http 或 https)、域名、以及端口号(如果存在)组成。Origin 头部通常用于浏览器发起的跨域请求中,服务器可以根据 Origin 头部来判断是否允许特定来源的跨域请求。在 CORS(跨域资源共享)机制中,服务器可以通过检查 Origin 头部来确定是否允许响应跨域请求,并设置适当的响应头部以进行授权。

常见的状态码都有哪些

1xx - Informational (信息响应):
- 100 Continue: 请求已经收到,继续处理。
- 101 Switching Protocols: 切换协议,用于切换到不同的协议。

  • 2xx - Successful (成功响应):
    • 200 OK: 请求成功,返回资源。
    • 201 Created: 请求成功,资源已创建。
    • 204 No Content: 请求成功,但没有返回内容。
  • 3xx - Redirection (重定向):
    • 300 Multiple Choices: 多个资源可用,客户端选择其中一个。
    • 301 Moved Permanently: 资源永久重定向到新的 URL。
    • 302 Found: 资源临时重定向到新的 URL。
    • 304 Not Modified: 客户端缓存有效,资源没有变化。
    • 307 Temporary Redirect: 资源临时重定向到新的 URL(保持请求方法)。
  • 4xx - Client Error (客户端错误):
    • 400 Bad Request: 请求错误,服务器无法理解。
    • 401 Unauthorized: 请求未授权。
    • 403 Forbidden: 请求被服务器拒绝。
    • 404 Not Found: 请求的资源不存在。
    • 405 Method Not Allowed: 请求方法不允许。
    • 406 Not Acceptable: 请求的资源不满足客户端的条件。
    • 429 Too Many Requests: 请求过多,服务器拒绝。
  • 5xx - Server Error (服务器错误):
    • 500 Internal Server Error: 服务器内部错误。
    • 501 Not Implemented: 请求方法未实现。
    • 502 Bad Gateway: 网关错误。
    • 503 Service Unavailable: 服务不可用。
    • 504 Gateway Timeout: 网关超时。
    • 505 HTTP Version Not Supported: HTTP 版本不支持。

301状态码和302状态码有什么区别

  • 301 Moved Permanently(永久重定向)
  • 302 Found(临时重定向)
    • 登陆重定向: 当用户未登录时,访问需要登录才能访问的页面,可以使用 302 临时重定向到登录页面。用户登录后,再跳转回原始请求的页面。
    • 临时维护页面: 当网站正在进行临时维护或更新时,可以将所有请求重定向到一个临时的维护页面,以便提醒用户网站暂时不可用。

什么叫重定向,实现原理是什么,浏览器经历过什么

  • 重定向是指在客户端请求一个 URL 的时候,服务器返回一个指示,要求客户端重新请求另一个 URL。重定向的主要目的是在不改变请求的方法的情况下,将客户端导向到另一个资源或地址,可能是因为资源已经移动、需要登录、临时维护等原因。

  • 实现重定向的原理是,服务器在返回响应时,在 HTTP 头部中添加特定的状态码(如 301 或 302)以及重定向目标的新 URL。浏览器接收到这个响应后,会根据状态码来进行不同的处理:

    • 如果状态码是 301 或 302,浏览器会从响应头中获取新的 URL,并重新发起一个新的 GET 请求到该 URL。
    • 浏览器将在新请求中包含原来的请求头部,但不会包含请求体(POST 请求除外)。
      这个过程会导致浏览器重新加载并显示新的页面,而用户会感觉到浏览器自动切换到了新的页面。
  • 总结起来,重定向就是在客户端请求时,服务器返回一个指示,要求客户端重新请求另一个 URL。浏览器会根据状态码和响应头部的指示重新加载新的页面。重定向可以用于多种情况,包括移动资源、跳转到登录页、临时维护等。

404和500状态码解释下。

  • 当客户端请求一个 URL 时,服务器会检查是否存在对应的资源。如果服务器无法找到请求的资源,就会返回 404 状态码。
  • 当服务器在处理请求时遇到了内部错误,如代码 bug、服务器配置问题、数据库连接问题等,就会返回 500 状态码。

http底层基于什么协议。

  • 除了 TCP,HTTP 还可以在其他传输层协议上运行,如 SCTP(Stream Control Transmission Protocol)等,但在实际应用中,TCP 是最常用的传输层协议。

四次挥手 - 为什么要四次

  • 全双工通信: TCP连接是全双工的,双方可以同时发送和接收数据。因此,在关闭连接时,需要分别关闭客户端到服务器和服务器到客户端的两个方向。
  • 确保数据完整性: 在关闭连接之前,双方可能还有未传输完的数据。因此,第一次挥手是通知对方已经不会再发送数据,但仍会接收数据。

SYN和ACK属于什么类型,统一的叫什么

  • SYN 和 ACK 是 TCP 首部中的标志位,用于在 TCP 连接的建立和维护过程中进行通信。它们统一被称为 TCP 标志(TCP Flags)或 TCP 控制位(TCP Control Flags)。

ACK和ack有什么区别

  • ACK是标志位,ack只是普通文本。

四次挥手服务器发送给客户端的两次挥手,时间间隔是多少

  • 2MSL

除了get和post还有其他的method吗?他们有什么区别

  • GET: 用于从服务器获取资源。
  • POST: 用于向服务器提交数据,主要用于创建新资源。
  • PUT: 用于向服务器上传一个资源或更新已有资源。
  • DELETE: 用于请求服务器删除指定的资源。
  • PATCH: 用于对资源进行部分更新。
  • HEAD: 类似于 GET,但不返回实际的数据,只返回响应头信息。
  • OPTIONS: 用于获取服务器支持的 HTTP 方法列表,以及服务器的配置信息。常用于跨域请求的预检(preflight)。
  • CONNECT: 用于建立与目标资源的双向通信隧道,常用于代理服务器等场景。
  • TRACE: 用于向服务器获取一个经过代理服务器的请求/响应的传输路径。主要用于调试和诊断。

get和post的区别

  • 数据位置:
    • GET:数据通过 URL 的查询参数传递,附加在 URL 后面。
    • POST:数据放在请求的请求体中,不会在 URL 上显示,适合传输大量数据。
  • 幂等性:
    • GET:应该是幂等的,即多次请求相同的 URL,不应该产生不同的影响。
    • POST:不一定是幂等的,多次请求可能会对服务器状态产生影响。
  • 用途:
    • GET:用于获取数据,不应该对服务器产生影响。
    • POST:用于提交数据,可能会对服务器产生影响。

cookie 和session 的区别?

  • Cookie: Cookie 是在客户端(浏览器)存储的小型文本文件,服务器通过设置响应头将 Cookie 发送给客户端,然后客户端将其存储在本地。每次浏览器发起请求时,会将相应的 Cookie 自动附加到请求头中发送给服务器。用于存储少量的用户信息、偏好设置等。
  • Session: Session 是在服务器端存储的对象,通常存储在服务器的内存中或者存储在数据库等持久化存储中。服务器会为每个会话分配一个唯一的 Session ID,客户端通过 Cookie 将该 ID 发送到服务器来恢复会话状态。可以存储用户登录信息、购物车内容、权限等。

TCP三次握手,为什么不两次握手

  • 第三次握手确认双方能够接收数据: 在第二次握手时,客户端可以确认服务器能够接收到它发送的连接请求。但是,服务器并不知道客户端是否已经准备好接收数据。
  • 防止已失效的连接请求建立: 假设只有两次握手,客户端发送连接请求到服务器,但由于某些原因该请求在网络中滞留,而客户端认为请求已经被接受。稍后,客户端又发送了一个新的连接请求,服务器接受后建立连接。如果这时滞留的请求突然到达服务器,就会误导服务器认为客户端要建立连接,从而导致连接错误。通过第三次握手,服务器可以避免接受已经失效的连接请求。

socket通信

  • Socket(套接字)允许不同设备上的程序通过网络进行数据交换。
  • Socket(套接字)位于传输层(Transport Layer)。
  • Socket 通信的基本步骤包括创建 Socket、绑定地址和端口、建立连接(仅适用于 TCP)、数据传输、关闭连接等。
  • 服务端代码
import java.io.*;
import java.net.*;

public class Server {
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(12345); // 监听端口 12345
            System.out.println("Server listening on port 12345...");
            Socket clientSocket = serverSocket.accept(); // 等待客户端连接
            System.out.println("Client connected: " + clientSocket.getInetAddress());
            BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            String message = in.readLine(); // 读取客户端发送的消息
            System.out.println("Received from client: " + message);
            PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
            out.println("Hello from server!"); // 向客户端发送消息
            clientSocket.close();
            serverSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 客户端代码
import java.io.*;
import java.net.*;

public class Client {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("localhost", 12345); // 连接服务器,指定服务器地址和端口
            System.out.println("Connected to server");
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            out.println("Hello from client!"); // 向服务器发送消息
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String message = in.readLine(); // 读取服务器返回的消息
            System.out.println("Received from server: " + message);
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

数据结构

给出先序遍历和中序遍历,写出对应的后序遍历。

  • 在先序遍历中,第一个元素为根节点。
  • 在中序遍历中,找到根节点的位置,左侧为左子树的中序遍历,右侧为右子树的中序遍历。
  • 根据左子树的中序遍历长度,可以在先序遍历中找到左子树的先序遍历和右子树的先序遍历。
  • 递归地构造左子树和右子树,然后根据后序遍历的定义,先遍历左子树,再遍历右子树,最后输出根节点的值。
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int val) {
        this.val = val;
    }
}

public class PreInPostTraversal {
    public static TreeNode buildTree(int[] preorder, int[] inorder) {
        return buildTreeHelper(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1);
    }

    private static TreeNode buildTreeHelper(int[] preorder, int preStart, int preEnd,
                                            int[] inorder, int inStart, int inEnd) {
        if (preStart > preEnd || inStart > inEnd) {
            return null;
        }

        int rootVal = preorder[preStart];
        int rootIndex = 0;
        for (int i = inStart; i <= inEnd; i++) {
            if (inorder[i] == rootVal) {
                rootIndex = i;
                break;
            }
        }

        int leftSubtreeSize = rootIndex - inStart;

        TreeNode root = new TreeNode(rootVal);
        root.left = buildTreeHelper(preorder, preStart + 1, preStart + leftSubtreeSize,
                                    inorder, inStart, rootIndex - 1);
        root.right = buildTreeHelper(preorder, preStart + leftSubtreeSize + 1, preEnd,
                                     inorder, rootIndex + 1, inEnd);

        return root;
    }

    public static void postorderTraversal(TreeNode root) {
        if (root != null) {
            postorderTraversal(root.left);
            postorderTraversal(root.right);
            System.out.print(root.val + " ");
        }
    }

    public static void main(String[] args) {
        int[] preorder = {1, 2, 4, 5, 3, 6, 7};
        int[] inorder = {4, 2, 5, 1, 6, 3, 7};

        TreeNode root = buildTree(preorder, inorder);
        postorderTraversal(root);  // 输出: 4 5 2 6 7 3 1
    }
}

如何用栈来模式队列【小红书】

  • 入队操作: 当需要入队一个元素时,直接将元素压入第一个栈(称为入栈)。
  • 出队操作: 当需要出队一个元素时,首先检查第二个栈(出栈)是否为空。如果不为空,直接从第二个栈弹出一个元素作为出队元素;如果为空,则将第一个栈的所有元素依次弹出并压入第二个栈,然后从第二个栈弹出一个元素作为出队元素。
import java.util.Stack;

class MyQueue {
    private Stack<Integer> stack1; // 入栈
    private Stack<Integer> stack2; // 出栈

    public MyQueue() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }

    public void enqueue(int item) {
        stack1.push(item);
    }

    public int dequeue() {
        if (isEmpty()) {
            throw new IllegalStateException("Queue is empty");
        }

        if (stack2.isEmpty()) {
            while (!stack1.isEmpty()) {
                stack2.push(stack1.pop());
            }
        }

        return stack2.pop();
    }

    public boolean isEmpty() {
        return stack1.isEmpty() && stack2.isEmpty();
    }
}

public class Main {
    public static void main(String[] args) {
        MyQueue queue = new MyQueue();
        queue.enqueue(1);
        queue.enqueue(2);
        queue.enqueue(3);

        System.out.println(queue.dequeue()); // 输出:1
        System.out.println(queue.dequeue()); // 输出:2

        queue.enqueue(4);
        System.out.println(queue.dequeue()); // 输出:3
        System.out.println(queue.dequeue()); // 输出:4
    }
}

二叉查找树性质及查找过程【小红书】

  • 性质
    • 每个节点都有一个键值,且不重复。
    • 左子树中的所有节点的键值小于根节点的键值。
    • 右子树中的所有节点的键值大于根节点的键值。
    • 左右子树也都是二叉查找树。
  • 查找过程如下:
    • 从根节点开始,比较要查找的值和当前节点的键值。
    • 如果要查找的值等于当前节点的键值,查找成功,返回当前节点。
    • 如果要查找的值小于当前节点的键值,继续在左子树中查找。
    • 如果要查找的值大于当前节点的键值,继续在右子树中查找。
    • 重复以上步骤,直到找到相应的节点或者达到叶子节点(查找失败)。

二叉树中序遍历是什么,递归怎么实现

  • 二叉树的中序遍历是一种遍历方法,按照"左子树 -> 根节点 -> 右子树"的顺序访问二叉树的节点。
  • 递归实现二叉树的中序遍历的基本思路是:从根节点开始,先递归遍历左子树,然后访问根节点,最后递归遍历右子树。以下是一个使用 Java 语言实现的示例代码:
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int val) {
        this.val = val;
    }
}

public class InorderTraversal {
    public void inorder(TreeNode root) {
        if (root != null) {
            inorder(root.left);   // 递归遍历左子树
            System.out.print(root.val + " "); // 访问根节点
            inorder(root.right);  // 递归遍历右子树
        }
    }

    public static void main(String[] args) {
        InorderTraversal traversal = new InorderTraversal();

        TreeNode root = new TreeNode(10);
        root.left = new TreeNode(5);
        root.right = new TreeNode(15);
        root.left.left = new TreeNode(3);
        root.left.right = new TreeNode(7);
        root.right.left = new TreeNode(12);
        root.right.right = new TreeNode(18);

        System.out.println("Inorder traversal:");
        traversal.inorder(root); // 输出:3 5 7 10 12 15 18
    }
}

操作系统

进程和线程的区别?【美团】

  • 进程:操作系统资源分配的最小单位。
  • 线程:CPU进行运算调度的基本单位。
  • 线程是进程内运算/运行单位==车间于生产线。

一个谷歌页面对应几个进程【美团】

  • 浏览器进程(Browser Process): 这是谷歌浏览器的主进程,负责管理所有其他进程。它控制着浏览器的用户界面,以及处理标签页的创建、销毁、导航等操作。
  • 插件进程(Plugin Process): 对于使用插件的页面,每个插件通常运行在独立的插件进程中,这有助于提高浏览器的稳定性。
  • 网络进程(Network Process): 负责网络请求,这使得网络操作可以在单独的进程中进行,不会阻塞页面渲染。
  • GPU 进程(GPU Process): 这个进程负责处理与图形相关的任务,如硬件加速、绘制页面内容等。
  • 渲染进程(Renderer Process): 每个标签页通常都有一个独立的渲染进程,用于加载、渲染和交互页面内容。这种隔离可以提高安全性和稳定性,因为即使一个页面崩溃,不会影响其他页面。

操作系统功能

  • 进程管理: 操作系统负责管理进程的创建、调度、切换和终止。它确保多个进程可以在系统中并发执行,合理分配CPU时间。
  • 内存管理: 操作系统管理计算机内存的分配和回收,确保不同程序能够共享和访问内存。它也负责虚拟内存的管理,将物理内存与磁盘空间结合起来,从而扩展可用内存。
  • 文件系统管理: 操作系统管理存储设备上的文件,包括文件的创建、读取、写入、删除等操作。它还提供了文件权限控制、目录管理等功能。
  • 设备管理: 操作系统管理计算机的各种硬件设备,如输入输出设备、磁盘驱动器等。它负责分配设备资源、处理设备中断等。

进程通信方式

  • 管道/匿名管道(Pipes):父子进程间通信。
  • 命名/有名管道(Named Pipes):以磁盘文件的方式存在,可以实现本机任意两个进程通信。
  • 信号(Signal) :用于通知接收进程某个事件已经发生。
  • 消息队列(Message Queuing):消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺点。
  • 信号量(Semaphores):信号量是一个计数器,用于多进程对共享数据的访问。
  • 共享内存(Shared memory) :内存屏障。
  • 套接字(Sockets):客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元。

线程和进程的区别,什么时候用

  • 使用进程适合于需要在不同的独立环境中运行多个任务的场景,每个进程之间有独立的内存空间和资源。这种情况下,进程提供了更好的隔离和安全性。
  • 使用线程适合于需要在同一进程内共享资源、共享数据的场景,且任务之间需要频繁地进行通信和同步。线程可以更高效地实现并发操作。

进程之间切换

  • 保存当前进程的状态: 当操作系统决定要切换到另一个进程时,首先需要保存当前进程的状态。这包括了当前进程的寄存器值、程序计数器(PC)、堆栈指针等信息,以便在将来恢复时能够继续执行。
  • 切换内存映射: 不同进程的内存映射是独立的,所以在切换到另一个进程时,操作系统需要更新内存映射表,以便新进程能够正确访问内存。
  • 加载新进程的状态: 操作系统从内存中获取要切换到的新进程的状态信息,包括寄存器值、程序计数器、堆栈指针等。
  • 恢复新进程的状态: 操作系统将新进程的状态信息恢复到 CPU 寄存器和内存中,以便新进程可以继续执行。
  • 更新调度信息: 进程切换时,操作系统还需要更新进程调度信息,以便正确选择下一个要执行的进程。
  • 开始执行新进程: 一旦新进程的状态被加载和恢复,操作系统将 CPU 的控制权转移到新进程,从而开始执行新进程的代码。

反转列表你有多少种方案

    1. 迭代法: 使用三个指针分别指向当前节点、前一个节点和后一个节点,然后不断地更新指针的指向,完成反转。
    1. 递归法: 递归地将链表分为头节点和剩余部分,然后将剩余部分反转,最后将头节点放到反转后的部分的末尾。
    1. 头插法: 创建一个新的链表,不断地将原链表的节点插入到新链表的头部,实现反转。
    1. : 使用栈来存储链表节点,然后按照栈的顺序弹出节点,构建反转后的链表。
    1. 修改指针方向: 对于每个节点,将它的下一个节点指向前一个节点,直到遍历完整个链表。
    1. 递归方式: 使用递归的方式,在每一步中交换当前节点和下一个节点的指针,然后递归处理下一个节点。

Linux[进程、日志查看、端口查看、目录&文件操作]

1 linux 查端口

  • netstat -tuln
  • ss -tuln
    • 这个命令会显示所有处于监听状态的 TCP 和 UDP 端口。其中,-t 表示显示 TCP 端口,-u 表示显示 UDP 端口,-l 表示仅显示监听状态,-n 表示以数字形式显示端口号。

2 Linux常用命令

  • 1.文件和目录操作:
    • ls: 列出目录中的文件和子目录。
    • cd: 切换当前工作目录。
    • pwd: 显示当前工作目录的路径。
    • mkdir: 创建新目录。
    • rm: 删除文件或目录。
    • cp: 复制文件或目录。
    • mv: 移动文件或目录,也可用于重命名。
    • touch: 创建空文件或更新文件的时间戳。
    • chmod: 修改文件权限。
    • chown: 修改文件所有者和所属组。
    1. 文本处理命令:
    • cat: 显示文件内容。
    • grep: 在文件中搜索匹配的文本。
    • sed: 流式文本编辑器,用于查找和替换文本。
    • awk: 强大的文本处理工具,可用于处理和格式化文本。
    • head: 显示文件的前几行。
    • tail: 显示文件的后几行。
    • sort: 对文本进行排序。
    1. 系统信息和管理命令:
    • ps: 显示当前运行的进程。
    • top: 动态显示系统资源使用情况。
    • df: 显示磁盘空间使用情况。
    • du: 显示文件和目录的磁盘使用情况。
    • free: 显示系统内存使用情况。
    • uname: 显示系统信息。
    • reboot: 重新启动系统。
    • shutdown: 关闭系统。
    1. 网络命令:
    • ping: 测试网络连通性。
    • ifconfig 或 ip: 显示和配置网络接口信息。
    • netstat: 显示网络连接和端口信息。
    • nslookup 或 dig: 域名解析工具。
    • wget 或 curl: 下载文件或资源。
      1. 压缩和解压缩命令:
    • tar: 创建和提取 tar 归档文件。
    • gzip 或 gunzip: 压缩和解压缩文件。
    • zip 或 unzip: 创建和提取 zip 压缩文件

3 有没有用过管道组合命令来使用

  • 在 Linux 中,管道(Pipeline)允许你将一个命令的输出作为另一个命令的输入,以此方式可以将多个命令组合在一起来完成复杂的任务。
    • ls -l | grep "pattern" | sort这个命令将列出当前目录中的文件和子目录,然后使用 grep 筛选包含指定模式的行,最后使用 sort 对结果进行排序。
    • cat file.txt | wc -lcat file.txt | wc -w这两个命令分别计算文件的行数和字数。
    • cat file.txt | grep "pattern" | sed 's/old/new/g'这个命令首先使用 grep 查找包含指定模式的行,然后使用 sed 在这些行中查找并替换旧字符串为新字符串。
    • ps aux | grep "process_name"这个命令获取系统上的所有进程,并使用 grep 查找包含指定进程名的行。
    • ls -l | grep "pattern" | awk '{print $2}'这个命令将列出当前目录中的文件和子目录,然后使用 grep 筛选包含指定模式的行,最后使用 awk 提取并打印第二个字段。

4 有没有在linux上查过日志,怎么查的

  • cat/more/less/tail
  • more 是一个简单的分页查看器,它允许你按页查看文本文件的内容。在使用 more 查看文件时,你可以使用空格键向下翻页,按 Enter 键向下滚动一行,按 q 键退出查看模式。由于 more 只支持向前滚动,无法向后滚动查看之前的内容,因此它的交互性较为有限。
  • less 也是一个分页查看器,但相对于 more 更加功能丰富。less 允许向上和向下滚动查看文件内容,使用箭头键、Page Up、Page Down 等键进行导航,具有更好的交互性。你还可以使用搜索功能,在 less 中输入 / 后跟要搜索的关键字,然后按 Enter 键进行搜索。按 n 键可以在搜索结果中跳转到下一个匹配项。

5 Cd ~是什么意思

  • 切换到当前用户的主目录。也可以直接用cd。

6 查询某一个的进程的id怎么写?

  • ps aux | grep process_name

7 查询的结果的里面哪一个是他的id?

  • 第二位就是PID

8 如何查看进程端口被占用

  • netstat/ss -tuln | grep 8080
    • 显示占用进程信息: 如果该端口被占用,命令的输出将会显示与该端口相关的进程信息,包括进程的 PID、进程名以及其他相关信息。
    • 无输出: 如果命令的输出为空,表示该端口目前没有被任何进程占用。

9 如何通过一个端口号去查找对应的进程ID

  • netstat/ss -tuln | grep PORT_NUMBER

10 port already in used是怎么解决的

  • 查找占用端口的进程: 使用前面提到的 lsof、netstat 或 ss 命令来查找占用特定端口的进程,并获取该进程的进程 ID。然后可以考虑终止占用端口的进程,或者将你的应用程序配置到其他可用的端口。

11 查看a.log文件最新的100条日志怎么写命令

  • tail -n 100 a.log

12 linux grep用法

  • grep "pattern" filename将 “pattern” 替换为你要搜索的字符串,filename 替换为你要搜索的文件名。这个命令将会在文件中查找包含指定字符串的行。

13 linux GPU怎么看

  • 查看 NVIDIA GPU 使用情况:nvidia-smi,nvidia-smi -i GPU_INDEX

如何kill所有叫python的进程

  • 要终止所有名称为 “python” 的进程,你可以使用 pkill 命令。示例用法:pkill python
  • 只想终止特定用户的 Python 进程,可以使用 -u 参数:pkill -u username python

Linux长连接和短链接

  • 长连接(持久连接):适用于需要频繁通信或实时通信的场景,如聊天应用、实时数据传输等。
  • 短连(非持久连接):适用于单次或偶尔通信的场景,如发送请求获取资源。
  • 长连接通常用于实时通信和推送场景,如 WebSocket、HTTP/1.1 Keep-Alive、MQTT 等。选择适合你需求的通信协议。
    • HTTP/1.1 Keep-Alive ,允许在单个 TCP 连接上发送和接收多个 HTTP 请求和响应。
    • WebSocket,Web 应用程序,实时通信和实时推送。
    • MQTT ,轻量级的消息传输协议,通常用于物联网(IoT)等场景
  • 短连接通常用于传统的 HTTP 请求和响应、SMTP、POP3(从远程服务器收取电子邮件的协议。) 等,这些协议在每次通信后会断开连接。选择适合你需求的通信协议。

Spring

bean的初始化

Spring的一些底层原理有没有了解过

  • Spring 是一个功能强大的框架,它提供了多种功能和模块,用于简化企业级应用程序的开发和管理。以下是 Spring 框架的一些底层原理和关键概念:

    • IoC(Inversion of Control)控制反转: IoC 是 Spring 框架的核心概念之一。它通过 IoC 容器来管理和维护对象的生命周期和依赖关系。在 Spring 中,你不需要自己创建对象,而是将对象的创建和管理交给 Spring 容器来处理。

    • Bean 容器和生命周期: Spring IoC 容器是用于管理 Bean(对象)的容器。容器负责创建、初始化、装配和销毁 Bean。Bean 的生命周期由容器控制,可以通过配置和接口实现自定义初始化和销毁过程。

    • 依赖注入(DI): DI 是 IoC 的一种实现方式,它是指通过将依赖关系从代码中抽离出来,并在外部进行配置和注入。Spring 使用 DI 来实现解耦和可维护性,使得对象之间的关系更加灵活和可配置。

    • AOP(Aspect-Oriented Programming): Spring 支持 AOP,允许开发人员将横切关注点(如日志、事务管理等)与业务逻辑分离。底层原理涉及动态代理、字节码操作等技术,通过在方法调用前后插入切面逻辑来实现。

    • 代理机制: Spring 使用动态代理来实现 AOP。通过 JDK 动态代理和 CGLIB 动态代理,Spring 在运行时生成代理对象,从而实现切面逻辑的插入。

    • Bean 的作用域: Spring 定义了多种 Bean 的作用域,如单例、原型、会话、请求等。不同的作用域决定了 Bean 的创建和销毁时机。

    • Bean 的自动装配: Spring 支持自动装配,可以根据类型或名称自动将依赖注入到 Bean 中。这样可以减少手动配置的工作量。

    • SPI(Service Provider Interface): Spring 使用 SPI 实现了很多扩展点,使得开发人员可以通过扩展接口来定制和拓展框架的功能。

    • 事件驱动编程: Spring 的事件驱动机制允许对象发送和监听事件。事件可以在应用内部传递,从而实现松耦合的组件通信。

    • Spring 模块: Spring 框架按照功能模块进行了拆分,如 Spring Core、Spring AOP、Spring Data、Spring Web 等。这些模块之间相互协作,实现了不同领域的功能。

    • 总之,Spring 框架的底层原理涉及到控制反转、依赖注入、AOP、动态代理等多种技术,它们共同为开发人员提供了一个灵活、可扩展的开发框架,使得企业级应用程序的开发变得更加高效和可维护。

Spring的Aspect注解是什么,什么时候会失效?

  • 在 Spring 框架中,@Aspect 注解用于声明一个类是一个切面(Aspect)。切面是用于定义横切关注点(如日志、事务管理等)的逻辑,通过 @Aspect 注解和其他相关注解来标识切面类,从而在运行时将切面逻辑织入到目标方法中。

  • 切面类中通常使用以下几个关键注解来定义切面逻辑:

    • @Before:在目标方法之前执行。
    • @After:在目标方法之后执行(无论是否抛出异常)。
    • @AfterReturning:在目标方法正常返回后执行。
    • @AfterThrowing:在目标方法抛出异常后执行。
    • @Around:包围目标方法,可以在方法之前和之后执行自定义逻辑。
  • 切面使用 AspectJ 表达式来选择连接点(切点),这些连接点是在目标方法执行过程中触发切面逻辑的点。

  • 切面的失效可能是由于以下情况引起的:

    • 配置不正确: 如果切面的配置不正确,比如 AspectJ 表达式错误,或者切面类没有被正确扫描到,都可能导致切面无法生效。

    • 切点匹配问题: 切点定义的不准确或选择的切点没有匹配到任何连接点,可能导致切面无法触发。

    • 代理问题: 如果 Spring 使用 JDK 动态代理来生成代理对象,并且目标类没有实现任何接口,那么切面可能无法被应用。这是因为 JDK 动态代理要求目标类实现接口,否则 Spring 会选择使用 CGLIB 代理。

    • 异常处理问题: 如果切面逻辑中出现异常,并且没有正确处理,可能导致切面失效,影响到目标方法的正常执行。

    • 运行时条件: 有时切面的执行可能会受到运行时条件的影响,如条件判断不满足时不执行切面逻辑。

    • 优先级问题: 如果多个切面同时应用在同一个连接点上,切面的执行顺序可能会影响到最终的效果。

RESTController和Controller的区别是什么

  • @Controller 注解用于标识一个类是 Spring MVC 中的控制器。它通常用于传统的 Web 应用程序,即基于视图的应用
  • @RestController 注解也用于标识一个类是 Spring MVC 的控制器,但它专门用于创建 RESTful Web 服务,即面向 API 的应用。
  • 传统的 Web 应用程序前后端交互:控制器生成模型数据=>控制器选择视图=>视图根据模型数据渲染生成 HTML、CSS 和 JavaScript =>浏览器收到响应后,解析 HTML、CSS 和 JavaScript,并将页面呈现给用户。
  • RESTful Web 服务:面向接口,将静态页面资源和动态后端模型数据获取的请求接口分开。前端VUE等框架通过Ajax发送请求,并负责接收数据,渲染生成HTML、CSS 和 JavaScript =>浏览器解析 HTML、CSS 和 JavaScript,并将页面呈现给用户。

IOC和AOP

  • IoC 是一种设计原则,它将对象的创建、组装和管理的控制权从应用程序代码中反转给了框架(或容器)。

    • Spring 框架是一个典型的 IoC 容器,它通过以下方式实现 IoC:

    • 配置元数据: 在 Spring 中,你可以使用 XML 配置文件、Java 注解或 Java 代码来描述对象之间的依赖关系和配置信息。这些配置元数据告诉 Spring 容器如何创建和组装对象。

    • 依赖注入(Dependency Injection,DI): Spring 使用依赖注入将对象的依赖关系从代码中分离出来。通过构造函数、属性或方法参数,Spring 将对象所需的其他对象(依赖)注入到它们中。

    • 对象的生命周期管理: Spring 容器管理对象的生命周期,确保对象在需要时被创建、初始化、使用和销毁。你可以通过配置来控制对象的作用范围(singleton、prototype 等)。

    • 解耦和模块化: IoC 使得应用程序的不同组件之间解耦,每个组件只需关注自己的任务。这种模块化使代码更易于维护和测试。

    • 灵活的配置: 通过修改配置,你可以轻松地更改对象之间的依赖关系,而无需修改代码。

  • AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在将应用程序的横切关注点(cross-cutting concerns)从核心业务逻辑中分离出来,使得这些关注点能够被模块化、重用和集中管理。这种分离能够提高代码的可维护性、可读性和扩展性。

    • 在 AOP 中,关注点是跨越不同模块和层的概念,如日志、事务管理、安全性等。这些关注点通常涉及多个类和方法,它们横贯于整个应用程序,不容易通过传统的面向对象编程方法进行管理。

    • AOP 的核心概念包括:

      • 切面(Aspect): 切面是一个模块化的单元,用于封装特定的关注点。一个切面由通知和切点组成。通知定义了在何时何地执行代码,而切点定义了在何处执行代码。

      • 通知(Advice): 通知是切面的实际操作,它定义了在何时何地执行特定的行为。在 AOP 中,通知可以是以下几种类型:

        • @Before:在目标方法执行之前执行。
        • @After:在目标方法执行之后执行,无论是否发生异常。
        • @AfterReturning:在目标方法正常返回后执行。
        • @AfterThrowing:在目标方法抛出异常后执行。
        • @Around:包围目标方法,可以在方法之前和之后执行自定义逻辑。
      • 切点(Pointcut): 切点定义了在哪里应该应用通知。它通过表达式或其他方式来匹配特定的方法、类或对象。

      • 连接点(Join Point): 连接点是在应用程序执行过程中能够插入切面的点,通常是方法的执行。连接点在 AOP 中被视为可以插入切面的“时机”。

      • 织入(Weaving): 织入是将切面应用到目标对象中的过程。它可以在编译时、类加载时或运行时进行。Spring 使用代理模式和字节码增强来实现织入。

    • 在 Spring 中,AOP 提供了强大的功能,允许开发人员将横切关注点从业务逻辑中抽离出来,提高代码的模块化程度。常见的应用包括日志记录、性能监控、事务管理等。Spring AOP 可以与 IoC 容器集成,实现依赖注入和切面逻辑的统一管理。

Spring有哪些模块

  • Spring Core Container: 这是 Spring 框架的核心模块,提供了 IoC(控制反转)和 DI(依赖注入)功能。它包括了 BeanFactory 和 ApplicationContext,用于管理对象的创建、依赖关系和生命周期。

  • Spring AOP: 这个模块支持面向切面编程,允许开发人员将横切关注点(如日志、事务)从业务逻辑中分离出来,并在运行时将其织入到代码中。

  • Spring Data Access/Integration: 这个模块提供了对数据访问和集成的支持,包括 JDBC、ORM(对象关系映射)等。它还包括了事务管理和数据源的功能。

  • Spring Web: 这个模块支持 Web 应用开发,包括 Web MVC 框架、Web 过滤器、RESTful Web 服务支持等。

  • Spring Security: 这是用于身份验证和授权的模块,提供了强大的安全性功能,可用于保护应用程序中的资源和数据。

  • Spring Messaging: 这个模块提供了消息传递的功能,包括消息发送和接收、异步处理等。

  • Spring Test: 这个模块支持对 Spring 应用程序进行单元测试和集成测试,提供了针对 Spring 组件的测试工具。

  • Spring Boot: 尽管 Spring Boot 不是一个独立的模块,但它是一个重要的 Spring 子项目。Spring Boot 简化了 Spring 应用程序的配置和部署,使得快速创建独立的、生产级别的 Spring 应用变得更加容易。

  • 除了上述列出的模块外,Spring 还有一些其他的模块,用于特定的用途,如 Spring Cloud 用于构建分布式系统、Spring Data 用于数据访问等。每个模块都提供了特定的功能,使开发人员能够根据应用需求选择合适的模块来构建和扩展应用。

spring的注释有哪些分别怎么用

  • Spring 框架提供了多个注解,用于在应用程序中进行配置、依赖注入、AOP 等方面的操作。以下是一些常见的 Spring 注解以及它们的使用方式:

  • @Component: 标记一个类为 Spring 的组件,让 Spring 管理这个类的实例。通常用于标记普通的 POJO 类。

  • @Repository: 标记一个类为数据访问层的组件,通常与持久化技术一起使用,例如与 Spring 的 JdbcTemplate 结合使用。

  • @Service: 标记一个类为业务逻辑层的组件,通常用于标记服务类。

  • @Controller: 标记一个类为控制器层的组件,通常用于标记控制器类,处理用户请求。

  • @RestController: 组合了 @Controller 和 @ResponseBody,用于创建 RESTful Web 服务的控制器。

  • @Autowired: 自动注入依赖关系,可以用于构造函数、属性、方法等位置。

  • @Qualifier: 当存在多个候选 Bean 可以注入时,与 @Autowired 一起使用,用于指定要注入的 Bean。

  • @Value: 用于从配置文件中获取属性值,注入到类的字段或方法参数中。

  • @Configuration: 标记一个类为配置类,通常与 @Bean 注解一起使用,用于定义 Bean 的创建。

  • @Bean: 在配置类中使用,声明一个方法为创建 Bean 的方法,Spring 容器会将方法返回的对象注册为 Bean。

  • @Scope: 定义 Bean 的作用范围,如单例、原型等。

  • @PostConstruct: 在 Bean 创建后立即执行的方法上使用,用于初始化操作。

  • @PreDestroy: 在 Bean 销毁前执行的方法上使用,用于清理资源等操作。

  • @Transactional: 标记一个方法为事务性方法,用于控制事务的边界。

  • @Aspect: 标记一个类为切面类,用于定义切面逻辑。

  • @Pointcut: 定义一个切入点,用于指定在何处应用切面逻辑。

  • 这只是 Spring 注解的一小部分,实际上 Spring 框架提供了更多的注解,每个注解都有其特定的用途和使用方式。不同的注解可以组合使用,以满足不同的业务需求。在使用 Spring 注解时,需要深入了解每个注解的含义和用法,以便正确地应用到应用程序中。

spring中应用到的设计模式是哪些?

  • Spring 框架在其设计和实现中广泛应用了多种设计模式,以达到松耦合、可扩展和易于维护的软件架构。以下是一些在 Spring 中应用到的常见设计模式:

  • 工厂模式(Factory Pattern): Spring 使用工厂模式来创建和管理对象的生命周期。ApplicationContext 充当了一个工厂,负责创建和管理 Bean 对象。

  • 单例模式(Singleton Pattern): Spring 中的默认作用域是单例(singleton),即每个 Bean 定义只会创建一个单例实例。

  • 原型模式(Prototype Pattern): Spring 也支持原型作用域,即每次请求都会创建一个新的实例。

  • 代理模式(Proxy Pattern): Spring AOP(面向切面编程)使用代理模式实现横切关注点的分离。动态代理可以在方法执行前后插入切面逻辑。

  • 观察者模式(Observer Pattern): Spring 中的事件机制使用了观察者模式,通过发布-订阅机制实现组件之间的通信。

  • 模板方法模式(Template Method Pattern): Spring 提供了 JdbcTemplate 等模板类,简化了数据库操作,而具体的实现可以在模板方法中覆盖。

  • 策略模式(Strategy Pattern): Spring 在配置中使用策略模式来根据不同的需求选择不同的实现。

  • 装饰器模式(Decorator Pattern): Spring 的 AOP 切面可以看作是对被装饰对象的增强。

  • 适配器模式(Adapter Pattern): Spring 提供了适配器模式来实现各种接口,从而允许框架与其他技术进行集成。

  • 依赖注入和控制反转(IoC): 这不是一个传统的设计模式,但是是 Spring 框架的核心思想。它实现了对象之间的解耦,通过配置和注解来控制对象的创建和依赖注入。

  • 这些只是在 Spring 中应用到的一些常见设计模式。Spring 框架的核心思想是将这些设计模式融合在一起,使开发人员能够构建松耦合、可扩展和易于测试的应用程序。

spring boot有哪些功能模块、操作数据库的模块是哪个?

  • Spring Boot 是一个用于简化 Spring 应用程序开发的框架,它集成了许多常用的功能模块,以便开发人员可以更快速地构建和部署应用程序。以下是 Spring Boot 中一些常见的功能模块:

  • 自动配置(Auto-Configuration): Spring Boot 根据 classpath 上的依赖自动配置 Spring 应用程序,无需手动配置大量的配置文件。

  • 起步依赖(Starter Dependencies): 提供了预配置的依赖项,可以轻松添加到项目中,例如 spring-boot-starter-web 用于构建 Web 应用。

  • 嵌入式 Web 服务器: Spring Boot 集成了嵌入式的 Tomcat、Jetty 或 Undertow 服务器,可以轻松地创建和运行 Web 应用。

  • Actuator: 提供了监控和管理 Spring Boot 应用的端点,如健康检查、性能指标、环境信息等。

  • 外部化配置: 支持在不同环境中使用不同的配置文件,例如 application.properties 或 application.yml。

  • 日志记录: 集成了常见的日志框架,如 Logback、Log4j2,可以通过配置进行日志记录。

  • 安全性: 提供了基本的安全性功能,如登录认证、角色权限等。

  • 数据库访问: Spring Boot 集成了多个数据库访问模块,其中常用的是 Spring Data JPA 和 Spring JDBC。

  • RESTful Web 服务: 支持构建 RESTful Web 服务,可以使用 @RestController 注解来创建控制器。

  • 缓存管理: 提供了对缓存的支持,如集成了 Ehcache、Redis 等。

  • 消息队列: 集成了消息队列,如 RabbitMQ,用于实现异步消息传递。

  • 任务调度: 支持基于时间或事件触发的任务调度,集成了 Spring 的任务调度框架。

  • 关于操作数据库的模块,Spring Boot 集成了多个数据库访问模块,其中主要的模块是 Spring Data JPA 和 Spring JDBC。Spring Data JPA 是一个用于简化数据库访问的模块,它基于 JPA(Java Persistence API)标准,并提供了许多便利的功能。Spring JDBC 则是基于传统的 JDBC 技术,可以通过简单的模板类来进行数据库操作。你可以根据具体需求选择适合的数据库访问模块来操作数据库。

Mybatis

SpringBoot

Redis

Redis缓存穿透击穿。

redis实际在实习项目的作用

Redis数据结构与底层数据结构

缓存写回机制(持久化机制)

缓存宕机了怎么办

  • 当 Redis 缓存宕机了,系统的一部分数据无法从缓存中获取,这可能会影响系统的性能和可用性。以下是处理 Redis 缓存宕机的一些常见方法:

    • 优雅降级: 在系统设计时,考虑将缓存设计为可选的组件,当缓存不可用时,系统可以从后端存储中获取数据,尽管可能会降低一些性能。

    • 缓存数据预热: 在系统启动或缓存重启后,可以预先将常用的热点数据加载到缓存中,以减轻缓存冷启动时的性能影响。

    • 使用备用缓存: 如果有多个缓存节点或缓存集群,可以切换到备用缓存节点,以保持缓存的可用性。使用多个缓存层级,如本地缓存和远程缓存,也可以减轻宕机的影响。

    • 监控和报警: 设置监控系统,实时监测 Redis 的健康状态。一旦检测到宕机或异常,及时触发报警,以便运维人员能够采取措施。

    • 快速恢复: 如果 Redis 宕机是由于临时问题引起的,可以尝试快速恢复 Redis 服务。如果 Redis 配置了持久化(如 AOF 持久化),可以通过恢复数据文件来重新启动 Redis。

    • 临时方案: 如果宕机的时间较长,可以考虑暂时采用临时方案,如将数据存储在数据库中,直到 Redis 恢复正常。

    • 缓存降级策略: 当 Redis 宕机时,可以根据业务需求考虑一些缓存降级策略,如降低某些操作的频率或范围,以减轻后端存储的负担。

  • 需要根据具体的业务需求和系统架构,选择适合的应对策略。总之,处理 Redis 缓存宕机需要权衡数据的一致性、性能和可用性等方面的需求。

缓存更新(一致性)

缓存过期 key 如何处理

  • 在 Redis 中,可以通过设置缓存键(Key)的过期时间来控制缓存数据的有效期。当缓存键过期后,相关数据会被自动删除,此时如果有新的请求来访问该缓存键,会触发缓存未命中,从而从后端存储重新获取数据。

Redis缓存穿透、缓存击穿、缓存雪崩及解决办法

  • 缓存穿透:
    • 定义:缓存穿透是指查询一个不存在于缓存和数据库中的数据。当大量的请求同时访问不存在的数据时,这些请求会穿透缓存,直接访问数据库,造成数据库压力。
    • 解决办法:
      • 使用布隆过滤器:在缓存层使用布隆过滤器,用于快速判断一个数据是否存在于缓存中,从而避免访问数据库。
      • 缓存空值:将不存在的数据也存入缓存,并设置一个较短的过期时间,以避免频繁访问不存在的数据。
  • 缓存击穿:
    • 定义:缓存击穿是指一个热点数据突然过期,导致大量请求同时访问数据库,从而造成数据库压力。
    • 解决办法:
      • 使用互斥锁(Mutex Lock):在缓存失效时,只允许一个请求进入数据库查询数据,其他请求等待,查询完毕后更新缓存。
      • 针对热点数据设置永不过期:对于热点数据,可以设置永不过期,或者过期时间较长,以减少突然过期带来的影响。
  • 缓存雪崩:
    • 定义:缓存雪崩是指缓存中大量的数据同时过期,导致大量的请求都直接访问数据库,从而引发数据库崩溃。
    • 解决办法:
      • 随机过期时间:在设置缓存过期时间时,引入一定的随机性,避免大量数据同时过期。
      • 分布式缓存:使用多个缓存节点或分布式缓存,将数据分散到不同节点上,以减少缓存雪崩的风险。
      • 数据预热:在系统启动或缓存刷新时,预先加载部分数据到缓存中,以保持缓存的热度。

Redis数据一致性

什么时候用redis

  • 缓存: Redis最常见的用途之一是作为缓存。将经常访问的数据存储在Redis中,可以提高系统的读取性能,减少对数据库的负载。适用于读多写少的业务场景。
  • 会话存储: 将用户会话数据存储在Redis中,可以支持分布式环境下的会话共享,并且能够快速存取会话数据。
  • 计数器和统计: Redis支持原子操作,适合用于实现计数器、访问频率限制、热门排行榜等功能。
  • 消息队列: Redis的发布-订阅机制和列表数据结构可以用于实现简单的消息队列,支持消息的发布和订阅。
  • 实时排行榜: 通过有序集合数据结构,可以实现实时排行榜功能,如实时热门文章排行等。
  • 地理位置服务: Redis支持地理位置数据存储和查询,适合实现附近的人、地点搜索等功能。
  • 分布式锁: Redis的单线程特性和原子操作可以用于实现分布式锁,保障分布式环境下的数据一致性。
  • 缓存穿透解决: 使用Redis的布隆过滤器可以有效防止缓存穿透问题。
  • 数据预热: 在系统启动时,可以预先将部分数据加载到Redis中,以提高系统的响应速度。
//Redis分布式锁
public class RedisDistributedLock {

    private final String LOCK_KEY = "my-lock-key";
    private final long LOCK_EXPIRE = 30000; // 锁的过期时间,单位毫秒
    private final long WAIT_TIMEOUT = 5000; // 等待获取锁的超时时间,单位毫秒
    private final String LOCK_VALUE = UUID.randomUUID().toString(); // 锁的唯一标识,用于区分不同的锁

    private Jedis jedis; // Redis客户端,需要预先初始化

    public boolean acquireLock() {
        long startTime = System.currentTimeMillis();
        try {
            while (System.currentTimeMillis() - startTime < WAIT_TIMEOUT) {
                // 尝试获取锁
                String result = jedis.set(LOCK_KEY, LOCK_VALUE, "NX", "PX", LOCK_EXPIRE);
                if ("OK".equals(result)) {
                    return true; // 获取锁成功
                }
                Thread.sleep(100); // 等待一段时间后重试
            }
        } catch (Exception e) {
            // 处理异常
        }
        return false; // 获取锁失败
    }
    public void releaseLock() {
        try {
            // 释放锁
            String lockValue = jedis.get(LOCK_KEY);
            if (lockValue != null && LOCK_VALUE.equals(lockValue)) {
                jedis.del(LOCK_KEY);
            }
        } catch (Exception e) {
            // 处理异常
        }
    }
}

redis的基本数据类型

  • 字符串(String): 最基本的数据类型,存储一个字符串值。可以进行字符串连接、截取、替换等操作。也可以用于存储序列化后的数据,如JSON、XML。

  • 哈希(Hash): 类似于一个关联数组,可以存储多个键值对。适合存储对象的属性和值,可以快速获取单个字段的值,也适用于存储嵌套的数据结构。

  • 列表(List): 有序的字符串列表,可以存储多个值,允许重复元素。支持从列表的两端进行元素的插入和弹出操作,可以用于实现队列、栈等数据结构。

  • 集合(Set): 无序的字符串集合,不允许重复元素。支持集合间的交集、并集、差集等操作,适合实现标签系统、好友关系等功能。

  • 有序集合(Sorted Set): 类似于集合,但每个元素关联一个分数,用于进行排序。支持按分数范围获取元素,适用于实现排行榜、范围查询等功能。

  • 位图(Bitmap): 用于存储位操作的数据结构,支持对位进行设置、清除、统计等操作,适用于位图存储和运算。

  • HyperLogLog: 用于进行基数估计的数据结构,可以快速估计一个集合中的元素个数,适用于独立用户数统计、数据统计等场景。

  • 地理位置(Geospatial): 存储地理坐标和名称的数据结构,支持根据坐标范围查询附近的位置,适用于地理位置服务。

项目中使用的各种数据库的使用场景、选型考量、以及数据一致性的实现?

  • 持久化数据库选用Mysql,作为项目的底库。
  • 考虑高性能和高并发,选用Redis作为缓存。
  • 数据一致性主要是:先删除缓存,再更新数据库,最后再删缓存。

本地缓存的过期时间的设置?

  • 使用EXPIRE命令设置过期时间:
SET myKey "Hello, Redis"
EXPIRE myKey 60
  • 使用SETEX命令设置带有过期时间的键值对:
SETEX myKey 60 "Hello, Redis"
  • 使用SET命令设置带有过期时间的键值对:
SET myKey "Hello, Redis" EX 60

本地缓存中存的数据是什么?

  • 主要是菜品信息和员工信息
  • 使用的是Hash数据结构。

Redis 使用中会涉及到磁盘的开销吗

  • 持久化: Redis支持持久化机制,将内存中的数据写入磁盘,以便在系统重启后能够恢复数据。这涉及将数据序列化并写入磁盘文件,这就需要涉及到磁盘的读写操作。有两种主要的持久化方式:快照持久化(RDB)和追加文件持久化(AOF)。

  • AOF日志: 在AOF持久化模式下,Redis会将每个写操作记录到一个追加的文件中,这个文件会不断增长。当AOF文件过大时,Redis会执行AOF文件重写,将一些冗余的写操作进行合并,从而减少AOF文件的大小。

  • 内存淘汰: 当内存不足时,Redis会执行内存淘汰操作,即从缓存中移除一些数据以释放内存。这可能涉及将一些数据从内存写回到磁盘,以便在需要时能够恢复。

  • 数据重载: 当Redis启动时,如果开启了持久化功能,它会从磁盘加载数据到内存中。这也会涉及到磁盘读取操作。

Redis 持久化的方式,以及你自己的具体使用?

  • 找到 redis.conf 文件的位置:Redis的配置文件通常位于Redis的安装目录下,或者在启动Redis时可以使用 -c 参数指定配置文件的位置。默认情况下,redis.conf 文件位于 Redis 安装目录的根目录。

  • 打开 redis.conf 文件:使用文本编辑器(如Vi、Nano、Notepad等)打开 redis.conf 文件。

  • 搜索和修改相关配置项:在 redis.conf 文件中,你可以找到和持久化相关的配置项。以下是一些与持久化相关的常用配置项和说明:

    • save 配置项:这是一个列表,表示自动触发快照持久化的条件。默认情况下,Redis配置了三个条件:save 900 1、save 300 10、save 60 10000,分别表示在900秒内有1个改动、在300秒内有10个改动、在60秒内有10000个改动时触发快照持久化。你可以根据需要修改这些值。

    • dir 配置项:表示持久化文件的存储路径。默认情况下,持久化文件保存在Redis的工作目录下。你可以将它修改为你希望的路径。

    • appendonly 配置项:如果你想启用AOF持久化,将这个配置项的值设置为 yes,表示将每个写操作追加到AOF文件中。

    • appendfilename 配置项:用于设置AOF文件的文件名,默认为 appendonly.aof。

    • appendfsync 配置项:用于设置AOF文件的同步策略。可以选择的值包括 always、everysec 和 no,分别表示每次写操作、每秒写操作、不进行写操作时进行同步。

    • 其他与持久化相关的配置项:在 redis.conf 文件中还有其他与持久化相关的配置项,你可以根据需求进行修改。

  • 保存文件并重启Redis:修改完配置文件后,保存文件并重新启动Redis,使配置生效。你可以使用以下命令重启Redis:

redis-server /path/to/redis.conf

布隆过滤器的实现原理? 若数据库中对应键值null, 那在布隆过滤器对应的底层bitmap 中置0还是1?

  • 布隆过滤器(Bloom Filter)是一种空间效率很高的概率型数据结构,用于判断一个元素是否存在于一个集合中。它的实现原理基于位运算和多个哈希函数。

  • 实现原理:

    • 初始化:创建一个长度为m的位数组,初始化所有位为0。
    • 添加元素:将要添加的元素经过多个哈希函数计算得到多个哈希值,然后将这些哈希值对应的位设置为1。
    • 查询元素:对于要查询的元素,同样经过多个哈希函数计算得到多个哈希值,检查对应的位是否都为1。如果有任何一位为0,表示元素不在集合中;如果所有位都为1,表示元素可能在集合中。
  • 由于布隆过滤器的特性,有一定的误判率(false positive),即某个元素被判断存在于集合中,但实际上并不存在。误判率与位数组长度m、哈希函数个数以及要添加的元素个数等因素有关。

  • 对应键值为null:

    • 布隆过滤器本质上是一种位数组,其中每个位代表一个键的存在与否。当数据库中的键值为null时,布隆过滤器对应的底层位数组的操作方式与其他键值是一样的。布隆过滤器没有特殊处理null值,对于任何键,都是通过多个哈希函数计算得到多个位置,然后将这些位置对应的位设置为1。

    • 值得注意的是,由于布隆过滤器使用多个哈希函数来计算位的位置,即使键的值为null,不同的哈希函数也会得到不同的哈希值,因此会影响到位数组中的多个位,而不是仅仅一个位。

    • 因此,在布隆过滤器中,null值对应的位会被设置为1,就像其他键的位一样,以表示该键可能在集合中。

数据库

数据库四大特性。

  • 原子性(Atomicity):
    原子性是指数据库事务的操作被视为一个不可分割的最小单元,要么全部执行成功,要么全部回滚到事务开始前的状态。如果在事务执行过程中发生错误,所有的操作都会被撤销,不会有部分操作生效的情况。

  • 一致性(Consistency):
    一致性要求在事务执行前后,数据库的状态必须保持一致。这意味着事务必须遵循预定义的规则,以确保数据的完整性和一致性。如果事务操作违反了数据库的一致性规则,事务将被回滚,数据库状态回到事务开始前的状态。

  • 隔离性(Isolation):
    隔离性是指并发执行的多个事务之间必须互相隔离,保证每个事务在看待数据时都像是在独立操作数据库。这可以防止并发事务之间产生相互干扰或冲突,保证数据的准确性。

  • 持久性(Durability):
    持久性要求一旦事务提交,其对数据库的修改将永久保存,即使在数据库崩溃或重启后也能恢复。这通常涉及将事务日志写入磁盘等操作,以确保数据不会因系统故障而丢失。

事务四大特性

介绍数据库索引【快手】

  • 键(Key): 索引基于一个或多个列的值来构建,这些列称为索引的键。例如,如果在姓名列上创建索引,姓名将成为键。

  • 叶子节点(Leaf Node): 索引通常使用树状数据结构(如B树、B+树)来组织数据。在树中,叶子节点存储了实际数据的引用,用于加速查找。

  • 树状结构: 索引的底层数据结构通常是树,其中包括根节点、内部节点和叶子节点。B树和B+树是常用的索引结构,用于支持高效的范围查询和查找操作。

  • 常见的索引类型:

    • 单列索引(Single Column Index): 基于单个列的值创建的索引。适用于单列的查询和排序操作。

    • 复合索引(Composite Index): 基于多个列的值创建的索引。适用于多列组合查询,可以提高多个列条件的查询性能。

    • 唯一索引(Unique Index): 索引列的值必须唯一,用于确保表中没有重复的数据。

    • 主键索引(Primary Key Index): 主键是唯一标识表中每个记录的列,主键索引用于加速主键查询。

    • 全文索引(Full-Text Index): 用于对文本数据进行全文搜索的索引,支持关键字搜索和模糊匹配。

    • 空间索引(Spatial Index): 用于空间数据类型(如地理位置信息)的索引,支持空间范围查询。

什么样的字段是不适合加索引的

索引的分类

哪些数据库用的是B+树做索引

  • MySQL、PostgreSQL、Oracle Database、Microsoft SQL Server、SQLite

使用B树做索引的数据库有哪些

  • MongoDB: MongoDB是一种面向文档的NoSQL数据库,其默认的索引结构也是基于B树的。MongoDB使用B树来支持单字段的查询和范围查询。

  • LevelDB: LevelDB是Google开发的一种键值存储引擎,它也使用B树作为索引结构。LevelDB适用于需要高性能键值存储的场景。

  • Couchbase: Couchbase是一种分布式NoSQL数据库,它的存储引擎使用B树作为索引结构,以支持高性能的查询和索引操作。

B树和B+树的特点和区别

  • 存储数据: B树的节点中既存储关键字,也存储数据;而B+树的数据仅存储在叶子节点中。
  • 节点结构: B树的非叶子节点和叶子节点结构相同;B+树的非叶子节点仅存储索引,叶子节点存储实际数据。
  • 叶子节点链接: B+树的叶子节点通过链表链接,便于范围查询操作。
  • 节点分裂: B树的节点分裂后可能需要上移关键字,非叶子节点和叶子节点都可能分裂;B+树的分裂仅在叶子节点上进行,非叶子节点不分裂。
  • 范围查询: B+树在范围查询上通常比B树更高效,因为B+树的叶子节点有序,有利于范围查询。

MySQL索引,及设计原则

  • MySQL索引的类型:
    • B树索引: MySQL的默认索引类型,使用B+树作为底层数据结构。支持普通索引、唯一索引和主键索引。
    • 全文索引: 用于全文搜索,支持关键字搜索和模糊匹配。
    • 空间索引: 用于空间数据类型(如地理位置信息)的索引,支持空间范围查询。
  • MySQL索引的设计原则:
    • 选择合适的字段: 对于经常用于查询和筛选的字段,考虑加索引。一般来说,主键、外键以及经常用于WHERE、JOIN和ORDER BY的字段是候选索引字段。

    • 避免过多的索引: 索引虽然能提高查询性能,但过多的索引会增加维护开销和占用额外的存储空间。根据实际需求,仅创建必要的索引。

    • 使用复合索引: 对于多个查询条件组合,使用复合索引可以减少索引的数量。但不要创建过长的复合索引,避免影响性能。

    • 避免过长的索引: 索引字段的长度过长会增加索引的存储和维护成本。对于字符串字段,最好只选择字段前缀或哈希值作为索引。

    • 避免在列上做运算: 如果在查询条件中使用了函数或表达式,将会导致MySQL无法使用索引。尽量避免在列上做运算。

    • 主键和唯一索引: 每个表应该有一个主键,主键默认会创建唯一索引。对于唯一性要求高的字段,考虑创建唯一索引。

    • 避免频繁更新的字段: 更新操作会导致索引的维护,频繁更新的字段不适合加索引。

    • 注意索引和查询优化器: 索引不仅仅是加速查询,还会影响查询优化器的执行计划选择。因此,要结合查询模式和查询计划来设计索引。

    • 定期优化索引: 随着数据的增加和变更,索引的性能可能会下降。定期检查和优化索引是维护数据库性能的一部分。

数据库引擎有哪些【小红书】

  • InnoDB: InnoDB是MySQL数据库的一种事务性存储引擎,支持ACID事务,具有行级锁定和外键约束等特性。它适用于需要高并发和数据完整性的应用。

  • MyISAM: MyISAM是MySQL的另一种存储引擎,适用于读写比较少的应用,不支持事务和外键,但对于只读或只写的场景具有较好的性能。

  • Oracle Database Engine: Oracle数据库引擎是Oracle数据库系统的核心,支持事务、多版本并发控制(MVCC)、高可用性和大规模数据处理等特性。

  • Microsoft SQL Server Engine: SQL Server数据库引擎是Microsoft SQL Server的核心,支持事务、并发控制、分布式数据库和报表等功能。

  • PostgreSQL: PostgreSQL支持多种存储引擎,其中最常用的是使用B+树作为底层数据结构的引擎。它支持事务、MVCC、复杂查询和扩展性。

  • SQLite: SQLite是一种嵌入式数据库引擎,适用于轻量级应用,不需要单独的服务器进程,所有数据存储在单个文件中。

  • MongoDB: MongoDB使用B树作为索引结构的NoSQL数据库引擎,适用于文档型数据存储和查询。

  • Cassandra: Cassandra是分布式NoSQL数据库引擎,支持横向扩展、高可用性和灵活的数据模型。

  • Redis: Redis是内存数据库引擎,用于高速读写操作,支持多种数据结构如字符串、哈希、列表、集合和有序集合。

  • Elasticsearch: Elasticsearch是一种分布式搜索和分析引擎,用于全文搜索和实时数据分析。

5.2 InnoDB的底层数据结构【小红书】

  • 聚集索引(Clustered Index): InnoDB表中的主键索引被称为聚集索引。它定义了数据的物理存储顺序,并且表的数据行按照聚集索引的顺序存储。因此,主键的选择对于InnoDB表的性能和数据存储布局具有重要影响。

  • 辅助索引(Secondary Index): 除了主键索引外,InnoDB还支持辅助索引,也称为非聚集索引。辅助索引用于加速根据非主键列的查询。每个辅助索引的叶子节点存储了索引列的值和对应的主键值。

  • B+树结构: InnoDB的索引使用B+树(Balanced B-Tree)结构。B+树是一种平衡树结构,具有良好的查询性能和范围查询优势。B+树的叶子节点包含实际的数据记录。

  • 页(Page): InnoDB将数据和索引存储在固定大小的页中,通常为16KB。每个页可以存储多个数据行,同时也可以存储索引键和元数据。

  • 事务日志(Transaction Log): InnoDB引擎支持事务,事务的持久性和恢复通过事务日志实现。事务日志记录了对数据库的修改操作,以保证事务的持久性和数据一致性。

  • MVCC(Multi-Version Concurrency Control): InnoDB使用MVCC机制来支持并发控制。每个事务在读取数据时会看到一个时间点的数据版本,这可以防止脏读和不可重复读。

  • 页分裂和合并: 为了保持B+树的平衡性,InnoDB会进行页的分裂和合并操作。分裂操作用于插入新数据,合并操作用于删除数据或索引。

  • 锁: InnoDB支持行级锁定,这允许多个事务在同一表上并发地进行读写操作,提高了并发性能。

简单介绍下其他的数据库【小红书】

  • PostgreSQL: PostgreSQL是一款开源的关系型数据库管理系统(RDBMS),具有强大的扩展性和标准支持,支持SQL、事务、视图、触发器等功能。它还支持JSON和复杂数据类型,并具有高级的查询优化和扩展性特性。PostgreSQL适用于需要高级功能和自定义扩展的应用。

  • Oracle Database: Oracle Database是由Oracle公司开发的一款商业级关系型数据库管理系统,支持高性能事务处理、大规模数据处理和复杂查询。它具有ACID事务、分布式数据库、高可用性、并发控制等功能。Oracle Database适用于大型企业和复杂业务场景。

  • Microsoft SQL Server: Microsoft SQL Server是Microsoft推出的关系型数据库管理系统,适用于Windows环境。它支持ACID事务、分布式数据库、并发控制和报表等功能。SQL Server还集成了BI(商业智能)和分析服务。适用于Windows生态系统中的企业应用。

  • SQLite: SQLite是一款嵌入式数据库管理系统,将整个数据库存储在单个文件中。它轻量且易于集成,适用于轻量级应用、移动应用和嵌入式系统。SQLite不需要独立的数据库服务器进程。

有一个student表,列为id,name,class,查出每个class的学生数量。为什么用count(*),用count(1),count(id)行不行?

  • 在这种情况下,你可以使用 GROUP BY 子句结合聚合函数来查询每个班级的学生数量。具体来说,你可以使用 COUNT(*) 来计算每个班级的学生数量,但是使用 COUNT(1) 或 COUNT(id) 也可以正常工作。

  • 这三种写法的效果是一样的,它们都会返回每个班级的学生数量。这是因为聚合函数在计算时只关注行的存在与否,而不关心具体的列值。

  • COUNT(*):这将计算每个班级中的所有行数,因为它不考虑任何特定列的值,只要存在行就计数。

  • COUNT(1):这也是计算每个班级中的所有行数,因为它使用一个常量值1作为计数依据,同样不关心具体的列值。

  • COUNT(id):这也会计算每个班级中的所有行数,虽然它使用了 id 列的名称,但是在计算时仍然只关注行的存在与否,不考虑具体的 id 列的值。

  • 因此,无论你选择使用哪种形式,都可以得到每个班级的学生数量。通常情况下,习惯上会使用 COUNT(*),因为它更简洁,并且不涉及具体的列名。

小红书的历史记录你觉得是存在服务器端还是客户端?【小红书】

  • 小红书的历史记录通常会在服务器端进行管理。这是因为历史记录是与用户账户关联的重要信息,需要在多个设备上同步,并保持一致性和持久性。如果历史记录仅存在于客户端,用户在不同设备上访问时将无法看到一致的历史记录。

考虑一个场景,如果在写入数据库时,缓存还没来得及更新,这个时候刚好来了一个查redis缓存的请求,怎么才能保证读取的数据是刚写入数据库的数据,而不是缓存中的旧数据【小红书】

mysql主从同步 (不了解)

  • MySQL主从同步是一种数据库复制技术,允许将一个MySQL数据库服务器的数据同步到另一个MySQL服务器,从而实现数据的备份、读写分离、负载均衡等目的。主从同步通过将主服务器上的数据更改操作(如INSERT、UPDATE、DELETE)传递给从服务器,使得从服务器的数据保持与主服务器一致。

  • 以下是MySQL主从同步的基本工作原理和配置步骤:

  • 工作原理:

    • 主服务器(Master)上的二进制日志(binlog)记录了所有数据更改操作。
    • 从服务器(Slave)连接到主服务器,并请求从binlog读取数据更改记录。
    • 从服务器将读取到的binlog记录应用到自己的数据库,从而实现数据同步。
  • 配置步骤:

  • 在主服务器上配置:

  • 开启二进制日志:在主服务器的配置文件(my.cnf)中设置 log_bin 选项为启用二进制日志记录。
    配置复制账户:创建一个用于从服务器连接的复制账户,并授予 REPLICATION SLAVE 权限。
    在从服务器上配置:

  • 配置复制账户:与主服务器上创建的复制账户相同,但权限可以是只读的。
    配置主服务器信息:设置 master_host(主服务器地址)、master_port(端口)、master_user(复制账户用户名)、master_password(复制账户密码)等参数。
    启动从服务器复制进程:执行 CHANGE MASTER TO 命令,指定主服务器的连接信息,并执行 START SLAVE 命令启动复制进程。
    监控复制状态:

  • 使用 SHOW SLAVE STATUS 命令查看从服务器的复制状态,确认是否正常运行。
    主从同步可以实现一些重要的用途,如:

  • 数据备份和恢复: 从服务器可以作为主服务器的备份,用于数据恢复。

    • 读写分离: 从服务器可以用于处理读操作,减轻主服务器的负载。
    • 负载均衡: 将读操作分散到多个从服务器上,提高整体性能。
    • 高可用性: 当主服务器故障时,可以将从服务器提升为新的主服务器,实现高可用性。
  • 需要注意的是,主从同步并不是实时同步,而是异步的。因此,在数据更新后,可能会有一定的延迟才能在从服务器上看到更新。

主键是唯一索引吗

  • 是的,主键(Primary Key)在数据库表中是一种唯一索引。

如果你的数据是上亿量级的,如果有人做了select * 操作你会怎么办?

  • 引入查询限制: 如果应用允许,可以引入查询限制,限制一次查询返回的行数,从而避免大量数据传输和内存消耗。
  • 监控和警报: 设置数据库性能监控和警报系统,以便在查询负载过高时及时发出警报,以便采取措施应对。
  • 查询缓存和优化: 如果查询频率较高,可以考虑使用缓存技术,缓存查询结果,以减轻数据库的负担。同时,通过数据库查询优化工具,确保查询语句能够高效执行。
  • 数据分区和分片: 如果数据库支持,可以考虑使用数据分区或分片技术,将大表分割成更小的部分,以提高查询性能。
  • 性能测试和优化: 针对大量数据的场景,进行性能测试和优化,寻找瓶颈和优化点,确保系统能够处理大数据量的查询请求。

给一个100行一样的数据,怎么样删去五十行(脑子抽了答了一个distinct,后来面试官提醒了limit,还嘲笑我有没有用过)

  • 删除前50行数据:
DELETE FROM data_table LIMIT 50;
  • 删除后50行数据:
DELETE FROM data_table ORDER BY id DESC LIMIT 50;
  • 删除中间50行数据:
DELETE FROM data_table WHERE id BETWEEN 26 AND 75;
DELETE FROM data_table ORDER BY id LIMIT 50 OFFSET 25;#从第26行开始删除50行数据

针对用id查名字做sql优化,只能针对这个过程

  • 索引优化: 确保 id 列上有索引,以加速查询操作。索引可以显著减少数据检索时间,特别是在数据量较大时。
  • 只查询所需列: 当你只需要查询名字时,不要使用 SELECT *,而是明确指定需要的列,避免不必要的数据传输和内存消耗。

mysql如何把表A导入表B

  • 使用 INSERT INTO 语句:
    • 如果你只需要将表 A 的数据插入到表 B 中,可以使用 INSERT INTO 语句。这适用于两个表的结构相同或相似的情况。
INSERT INTO table_b SELECT * FROM table_a;
  • 使用 mysqldump 命令:
    • 如果你想复制整个表 A(包括数据和结构)到表 B,可以使用 mysqldump 命令生成一个 SQL 脚本,然后执行脚本来导入数据。
mysqldump -u username -p database_name table_a > dump.sql
-- 打开生成的 dump.sql 文件
-- 将所有 "CREATE TABLE table_a" 替换为 "CREATE TABLE table_b"
-- 将所有 "INSERT INTO table_a" 替换为 "INSERT INTO table_b"
mysql -u username -p database_name < dump.sql

测试理论基础

点击按钮上传文件的测试用例。

  • 界面:上传按钮清晰可见
  • 功能:
    • 文件选择窗口正确弹出,文件选择和上传流程流畅,上传进度显示正确,上传成功反馈。
    • 文件选择窗口正确弹出,不满足文件类型、大小(零字节文件、非常大的文件),正确异常处理&错误反馈。
  • 性能:大文件上传,并发文件上传,正确处理网络异常。

玩家id查询输入框怎么测

  • 界面:输入框可见性和位置。
  • 功能:输入满足需求的id->正确显示,输入不满足需求的id - 错误id,字符,边界id,过长id,空id->正确异常处理&错误提示。
  • 性能:查询速度->检查数据库索引&缓存。并发查询10000->响应时间。
  • 易用性:id提示,模糊查询。
SHOW INDEX FROM table_name; # Mysql查看某张数据库表字段是否加索引

分页查询接口怎么测。

  • 正常分页查询: 首先,测试正常情况下的分页查询,确保接口按照指定的页码和每页数量返回正确的数据。
  • 边界情况测试: 测试边界情况,例如查询第一页、最后一页。
  • 错误参数测试: 测试传递错误的参数,如负数页码、负数每页数量、非法字符等,确保接口能够正确处理并返回合适的错误提示。
  • 超出范围测试: 测试查询超出数据范围的页码,确认接口返回合适的空数据或错误信息。
  • 数据一致性测试: 在连续多次查询中,确保接口返回的数据是一致的,没有数据丢失或重复。
  • 性能测试: 对于大量数据的情况,测试接口的响应时间,特别是在不同页码和每页数量下的响应时间。
  • 并发测试: 使用多个并发请求同时进行分页查询,测试接口的并发处理能力。

删除链表中的指定节点-怎么设计测试用例

  • 删除中间节点
  • 删除头节点
  • 删除尾节点
  • 删除唯一节点场景
  • 节点不存在场景
  • 节点值重复

测试认为是bug和研发不一致,怎么处理。

  • 一是需求不定,所以这个时候可以找来产品经理进行确认,需不需要改动,商量确定好后再看要不 要改。
  • 二是和技术主R和测试主R进行确认。如果最终bug被确定不改, 那么就要在测试报告里面记录一下,以便以后查阅。

如果测试没发现bug导致线上出现bug,如何面对这些问题。

  • ⾸先复现这个bug,第一时间反馈给开发人员,尽快找出Bug原因。
  • 然后拉群联合开发产品,评估bug的严重性和修复时长。
  • 如果修复时间比较长,bug影响比较大,立即版本回退,延期上线,排期修复bug。
  • 如果修复时间比较短,bug影响比较大,开发拉hotfix分支,并立即修改bug。

对某个接口功能如何进行测试,从哪些方面。

正常情况测试: 测试接口在正常情况下的功能。提供合法且符合预期的输入,验证接口是否返回正确的输出。

边界情况测试: 测试接口在输入参数处于边界值的情况下的功能。例如,对于数字输入,测试最小值、最大值、边界值等。

异常情况测试: 测试接口在异常情况下的功能,例如传递非法参数、缺少必要参数、非预期数据类型等。验证接口是否能够正确处理这些情况,返回适当的错误信息。

性能测试关注的点有哪些。

  • 响应时间: 测试系统在各种负载下的响应时间,即从请求发送到接收到响应所需的时间。这可以帮助评估用户体验。
  • 吞吐量: 测试系统在单位时间内可以处理的请求数量。高吞吐量表示系统可以支持更多的并发用-户。
  • 并发用户数: 测试系统在同时处理多少个并发用户时性能仍然稳定。这可以帮助确定系统的最大负载能力。
  • 资源利用率: 监测系统在负载下的资源使用情况,包括CPU、内存、网络带宽等,以便发现资源瓶颈。

购物车设计测试用例【快手】

  • 添加商品到购物车:
    确保可以成功添加商品到购物车。
    验证商品数量增加,购物车总价正确更新。
  • 删除购物车中的商品:
    测试从购物车中移除单个商品。
    测试清空购物车功能,确认购物车中没有商品。
  • 修改购物车中商品数量:
    测试修改商品数量,确保购物车总价正确更新。
    测试数量减少到零,商品是否自动从购物车中移除。
  • 商品信息显示:
    确保购物车中显示商品的名称、价格、数量等信息。
    验证购物车总价计算是否准确。
  • 结算流程:
    测试点击结算按钮,确认是否跳转到结算页面。
    确保结算页面显示购物车中的商品清单、总价等信息。
  • 登录状态和未登录状态:
    测试用户登录状态下添加商品,确认购物车中数据与用户关联。
    测试未登录状态下添加商品,验证是否正确使用临时标识。
  • 多商品添加:
    测试同时添加多个商品到购物车。
    验证多个商品的数量和总价是否正确。
  • 库存限制:
    测试添加数量超过库存的商品,验证是否有正确的错误提示。
    确保购物车中的数量不会超过库存限制。
  • 商品规格和属性:
    测试购买有不同规格和属性的商品,确认是否正确显示在购物车中。
  • 界面响应性:
    在添加、删除、修改数量等操作后,验证购物车界面是否能够及时响应更新。
  • 跨平台测试:
    在不同设备和浏览器上测试购物车功能的兼容性。
  • 优惠券和折扣:
    测试购物车中使用优惠券、折扣码等功能。

实习的工作,怎么展开测试活动的,如何设计测试用例、自动化测试用例;(测试流程)【快手】

  • 业务功能:需求评审-需求分析-用例设计-用例评审-新建测试计划(提测/集成测试/系统测试(灰度/全量)计划)- 单元测试(看看代码逻辑)。

  • 接口:

    • 关键问题:为了防止自动化测试数据被删除-BeforeTest,新增测试数据。
    • 数据构造:两种方式。
  • 给你一个输入框,你打算怎么测试。

  • 有效数据测试

  • 无效数据测试

  • 边界数据测试

  • SQL注入测试

  • 并发测试

如何测试登录界面。

  • 如何测试小度的语音转文本功能,列举7个【百度】
  • 声音质量测试:
    测试不同音频质量下的转录准确度,包括清晰度、噪音干扰等。
  • 多种语言测试:
    测试不同语言的语音转文本准确度,确保功能对多种语言的支持和识别准确性。
  • 方言测试:
    测试是否能准确识别方言,包括不同地区的方言和口音。
  • 速度测试:
    测试语音转文本的响应速度,确保在合理的时间内完成转录。
  • 长音频测试:
    测试长音频文件的转录准确性和性能,确保在长时间录音后能够正确识别。
  • 语音识别准确度测试:
    提供多个不同口音、语速的语音样本,验证转录结果的准确度。
  • 错误情况测试:
    测试输入不清晰的语音、背景噪音、口误等情况下的识别准确度,确认是否能够正确处理异常情况。

实习遇到的困难及怎么解决,冲突怎么解决

如果设计百度语音智能音箱会有哪些设计流程,会用到哪些技术

  • 需求分析: 确定音箱的功能和特性,包括语音识别、语音合成、智能助手功能等。

  • 硬件设计:
    设计音箱外观、尺寸、材质等。
    选择合适的处理器、音频芯片、麦克风、扬声器等硬件组件。
    设计电路板和连接方式。

  • 软件设计:
    开发音箱的操作系统,可能基于嵌入式Linux等。
    开发语音识别和语音合成的软件模块。
    开发智能助手功能,如闹钟、提醒、天气查询等。

  • 语音识别技术:
    使用自然语言处理(NLP)和机器学习技术,训练语音识别模型,用于将语音转换成文本。
    考虑多种语言和方言的支持。

  • 语音合成技术:
    使用文本转语音技术,将文本合成为自然的语音音频。
    考虑音色、语调、语速等参数的调整。

  • 人机交互设计:
    设计语音唤醒词,使用户可以通过喊出特定词语来唤醒音箱。
    设计交互界面,可以是触摸屏、按钮、LED灯等。
    设计用户与音箱的对话交流方式。

  • 网络连接:
    集成Wi-Fi、蓝牙等连接方式,实现与互联网和其他设备的通信。
    考虑安全性和隐私保护。

  • 固件和软件开发:
    开发音箱的固件,负责控制硬件和运行系统。
    开发用户界面、智能助手和其他应用软件。

  • 测试和验证:
    进行各种功能测试,包括语音识别准确度、响应速度、用户体验等。
    进行稳定性测试和安全性测试。

  • 生产和制造:
    将设计好的硬件和软件进行批量生产和制造。
    进行组装、测试和调试。

  • 发布和营销:
    推出音箱产品,进行市场宣传和推广。
    提供售后支持和升级。

自然语言处理需要处理些什么

分词和标记: 将文本拆分成词语、短语或符号,并为每个词语添加词性标记,以便后续处理。

语法分析: 分析句子的语法结构,包括词语之间的关系,如主谓宾关系、从句结构等。

词义消歧: 确定一个词语在上下文中的具体意义,以避免歧义。

命名实体识别: 识别文本中的命名实体,如人名、地名、组织机构等。

情感分析: 分析文本中的情感倾向,如正面、负面或中性情感。

文本分类和聚类: 将文本归类到不同的类别或进行相似性聚类。

信息抽取: 从文本中提取结构化信息,如关系、事件等。

机器翻译: 将文本从一种语言翻译成另一种语言。

问答系统: 回答用户提出的问题,从大量文本中找到最合适的答案。

对话系统: 实现与计算机的自然语言对话,能够理解上下文并进行有意义的回应。

语音识别: 将语音转化为文本形式,以便计算机处理。

语音合成: 将文本转化为自然流畅的语音。

自动摘要: 从大量文本中提取出主要信息,生成摘要。

语言生成: 根据给定的指导生成自然语言文本,如自动生成文章、评论等。

文本生成: 使用大量文本训练模型,使其能够自动生成类似的文本内容。

NLP涉及的技术包括机器学习、深度学习、统计方法等。它在很多领域都有应用,包括搜索引擎、社交媒体分析、智能助手、翻译服务、自动摘要等,为计算机赋予了处理和理解人类语言的能力。

针对“小度小度,打开窗帘”,会用到什么技术

  • 语音识别: 首先,系统需要将语音命令转化为文本。语音识别技术会将用户说的“小度小度,打开窗帘”识别为对应的文本。

  • 自然语言理解: 接着,自然语言理解技术会分析识别出的文本,理解用户的意图。在这个例子中,理解用户想要控制窗帘的动作。

  • 语音唤醒: 在识别到“小度小度”的部分时,系统可能使用了语音唤醒技术,以便在用户说出关键词时激活系统。

  • 智能助手: 当理解用户意图后,系统会调用智能助手技术,解释命令的含义,如“打开窗帘”。

  • 执行控制: 系统通过与窗帘控制设备连接,发出控制指令,从而实际打开窗帘。这涉及到与物联网设备的通信。

  • 人机交互: 系统可能会回应用户,例如说“好的,窗帘已经打开”,以提供反馈。

如果断网情况,“小度小度,打开窗帘”又怎么实现

  • 本地控制: 一些智能设备可能支持本地控制,即在断网情况下,通过设备本身的局域网络连接,直接与智能设备进行通信。这可以实现基本的操作,例如打开或关闭窗帘。

  • 离线模式: 某些智能助手或语音控制系统可以在断网情况下切换到离线模式。在离线模式下,它们可能可以执行一些基本的预设命令,如控制家居设备。

  • 缓存策略: 一些系统在正常联网状态下会将部分常用的指令和数据缓存在本地,以备断网时使用。这样,当断网后,仍然可以使用已缓存的指令来控制设备。

  • 本地语音识别: 在断网情况下,设备可能使用本地语音识别引擎来处理语音指令,而不依赖于云端服务。这样,设备可以在断网时实现语音控制

针对无线耳机,设计一些测试用例

针对无线耳机的测试用例可以涵盖多个方面,包括连接性、音频质量、操作功能等。以下是一些可能的测试用例示例:

连接性测试:

测试耳机与设备的连接稳定性,包括蓝牙连接和无线连接(如AirPods)。
测试连接断开后的自动重新连接功能。
音频质量测试:

测试耳机的音频质量,包括音量、音调、清晰度等。
测试在不同环境中的音频效果,如室内、室外、嘈杂环境等。
延迟测试:

测试音频播放的延迟,确保与视频同步。
电池寿命测试:

测试耳机的电池寿命,包括通话时间和播放时间。
充电测试:

测试耳机的充电功能,包括充电速度和充电完整度。
测试充电盒的充电功能和电池寿命。
操作功能测试:

测试触摸或按键操作,如播放、暂停、切换歌曲、接听电话等功能。
测试语音助手功能,如调用智能助手(Siri、Google Assistant)。
耳感检测测试:

测试耳机是否能够通过感应器检测是否被佩戴,以自动播放/暂停音乐。
耳机定位测试:

测试耳机是否正确识别左右耳,确保音频通道正确。
防水防汗测试:

测试耳机是否具备防水和防汗功能,以适应户外运动等环境。
兼容性测试:

测试耳机与不同类型的设备(手机、平板、电脑等)的兼容性。
固件升级测试:

测试耳机是否支持固件升级,确保系统和功能的更新。
佩戴舒适性测试:

测试佩戴耳机的舒适性和稳定性,长时间佩戴时是否会感到不适。
控制APP测试:

测试配套控制APP的功能,如均衡器调整、查找耳机等。

无线耳机连接情况,如何测连接正常率(大量连接,测试失败率),使用什么测试流程,什么方法

测量无线耳机连接的正常率和大量连接下的测试失败率,可以使用一系列测试流程和方法来进行。以下是可能的测试方法和流程:

测试流程:

环境准备: 创建一个符合真实使用场景的测试环境,包括多个测试设备、无线信号干扰等。

测试用例设计: 设计连接和断开连接的测试用例,包括正常连接、异常连接、同时连接多个设备等。

连接正常率测试:

对每个测试设备逐个进行连接测试,确保单个连接的正常率。
记录连接成功和失败的情况,计算连接正常率。
大量连接测试:

针对同一时间连接多个设备的情况,逐个尝试连接。
逐步增加连接设备的数量,观察测试结果。
测试失败率测试:

在大量连接测试中,记录连接失败的情况,包括连接尝试超时、连接中断等。
统计和分析:

对连接正常率和大量连接测试失败率进行统计和分析。
分析失败情况的原因,如信号干扰、设备限制等。
测试方法:

手动测试: 首先可以使用手动方式进行测试,逐个连接设备并记录连接情况。这可以帮助发现基本的连接问题。

自动化测试: 针对大量连接情况,使用自动化测试框架进行测试,可以节省时间和人力,并确保测试的一致性。

脚本模拟: 使用脚本模拟同时连接多个设备的情况,通过控制连接和断开来模拟不同的连接情况。

无线干扰模拟: 在测试环境中引入无线干扰信号,以模拟实际使用中可能遇到的情况,测试连接的稳定性。

压力测试工具: 使用压力测试工具模拟大量连接情况,观察系统在高负载下的连接性能。

数据分析: 收集连接测试结果的数据,使用统计和数据分析工具来分析连接成功率和失败率的趋势和原因。

通过综合使用手动测试、自动化测试、脚本模拟等方法,可以全面地测试无线耳机的连接正常率和在大量连接情况下的测试失败率,以便发现和解决潜在的连接问题。

白盒测试方法

有没有实际部署过这种持续集成的流水线,它是怎么工作的

微信拼手气抢红包有金额和红包数,说一下抢红包的测试点,并自己设计几个测试用例

测试点:

金额计算正确性:

确保红包总金额等于红包金额乘以红包个数。
验证红包金额是否按照规则进行分配。
抢红包逻辑:

确保每个用户只能抢一次。
验证用户抢到的红包金额是否在范围内。
并发性和竞态条件:

多个用户同时抢红包,确保系统能够正确处理并发情况。
测试在高并发情况下是否会出现竞态条件和数据不一致问题。
金额精度:

测试红包金额的小数精度是否正确,避免精度丢失问题。
红包数和金额限制:

测试在不同的红包金额和红包个数情况下,系统是否能正常工作。
测试用例示例:

正常抢红包:

输入:红包总金额为100元,红包个数为10个。
预期结果:每个用户抢到的金额应在0元到100元之间,且总和为100元。
红包金额为0:

输入:红包总金额为0元,红包个数为10个。
预期结果:用户不能抢到任何金额。
只有一个用户抢红包:

输入:红包总金额为100元,红包个数为1个。
预期结果:该用户应抢到全部金额100元。
多用户并发抢红包:

输入:红包总金额为100元,红包个数为10个,同时有多个用户并发抢红包。
预期结果:所有用户抢到的金额总和应等于100元,且每个用户抢到的金额符合要求。
红包金额小数精度:

输入:红包总金额为0.1元,红包个数为10个。
预期结果:各用户抢到的金额应为0.01元,总和为0.1元。
红包个数大于金额:

输入:红包总金额为10元,红包个数为20个。
预期结果:部分用户抢到的金额为0元,部分用户抢到的金额大于0元。
抢红包后金额验证:

输入:用户抢到红包后,查看余额是否正确减少。
重复抢红包:

输入:同一用户多次尝试抢同一个红包。
预期结果:用户只能抢到一次红包,后续尝试不会有金额。
红包过期:

输入:红包设置有效期,用户在有效期外尝试抢红包。
预期结果:用户不能抢到任何金额。

实习遇到过什么困难?遇到困难该如何解决

  • 权限测试:
    • 不同角色BD权限不同。
  • 繁琐数据构造:
    • postman

页面或者网站打开特别慢的原因

  • 网络问题:
    慢速或不稳定的网络连接会导致页面加载时间延长。网络延迟、丢包等问题可能影响页面的下载速度。
  • 服务器性能不足:
    服务器资源(CPU、内存、磁盘)不足或负载过高会导致请求响应变慢,影响页面加载。
  • 大量或未优化的内容:
    大量的高分辨率图片、视频、JavaScript、CSS等文件会增加页面加载时间。
    未经优化的代码和资源可能导致浏览器需要更多时间来解析和渲染页面。
  • 第三方资源加载:
    如果页面依赖大量第三方资源(例如广告、社交媒体插件等),这些资源加载时间较长可能影响页面加载速度。
  • 慢速CDN:
    若使用的内容分发网络(CDN)提供商服务器响应慢或存在问题,会影响静态资源的加载速度。
    浏览器问题:
    旧版本的浏览器可能无法高效地处理现代网站的技术,导致页面加载缓慢。
    浏览器插件或扩展可能影响页面的加载速度。
  • 脚本执行阻塞:
    JavaScript脚本可能阻塞页面的加载,特别是在顶部位置放置的脚本会阻止其他资源的加载。
  • 缓存问题:
    缓存设置不当可能导致浏览器每次都重新下载资源,增加加载时间。
  • 数据库查询性能:
    如果网站依赖数据库,慢速的数据库查询可能影响页面加载时间。
  • 地理位置:
    用户和服务器之间的地理距离也可能导致页面加载速度不同。

设置哪些关键的指标测试音箱

  • 声音质量和音频性能:
    音质:测试音箱的音质,包括音量、音调、音质的清晰度等。
    噪音:测试音箱的静音状态和播放音频时是否存在杂音或干扰声。
    音频失真:检测高音、低音是否出现失真情况,例如爆音、嘶嘶声等。
  • 语音识别和交互:
    语音识别准确性:测试音箱的语音识别功能,确保可以准确理解用户的语音指令。
    多语言支持:验证音箱是否能够支持多种语言的语音识别。
    交互体验:测试音箱对于用户指令的回应速度、自然度和交互体验。
  • 连接性能:
    蓝牙连接:测试音箱通过蓝牙连接其他设备的稳定性和连接速度。
    Wi-Fi连接:测试音箱连接Wi-Fi网络的速度和稳定性,确保能够正常访问互联网。
  • 智能助手和功能:
    响应速度:测试音箱执行各种智能助手功能(如语音搜索、天气查询等)的响应速度。
    功能覆盖:测试音箱是否能够正常执行各种功能,如提醒、日历管理、音乐播放等。
  • 硬件性能:
    响应时间:测试音箱在不同场景下的响应时间,包括唤醒时间和执行指令时间。
    触控和按钮:如果音箱具有物理按钮或触控区域,测试其灵敏度和稳定性。
  • 固件升级和稳定性:
    固件升级:测试音箱的固件升级流程,确保用户可以轻松升级到最新版本。
    稳定性:长时间测试音箱的稳定性,避免崩溃、重启或死机等问题。
  • 能耗:
    电池续航:如果是无线音箱,测试电池续航时间。
    待机功耗:测试音箱在待机状态下的能耗情况。
  • 安全和隐私:
    隐私保护:测试音箱是否严格保护用户隐私,不会录制或存储敏感信息。
    语音指令保护:验证是否有机制防止未经授权的语音指令触发。
  • 网络功能:
    互联网访问:测试音箱能否正常访问互联网资源,如播放音乐、获取新闻等。

如何拿到准确的响应时间

  • 使用性能测试工具: 使用专业的性能测试工具,如JMeter
  • 使用浏览器开发者工具: 现代的浏览器开发者工具提供了网络监测功能,可以查看每个请求的响应时间。

微信点赞测试用例

  • 点赞成功场景:
    输入:用户A发表的帖子,用户B登录微信,点击点赞按钮。
    预期结果:用户B的点赞数增加,点赞状态变为已点赞。
  • 重复点赞场景:
    输入:用户A发表的帖子,用户B登录微信,点击点赞按钮,然后再次点击。
    预期结果:用户B的点赞数不会增加,点赞状态保持已点赞。
  • 取消点赞场景:
    输入:用户A发表的帖子,用户B已点赞,再次点击取消点赞按钮。
    预期结果:用户B的点赞数减少,点赞状态变为未点赞。
  • 点赞状态同步场景:
    输入:用户A发表的帖子,用户B登录微信,用户C登录网页版微信,用户B点赞,用户C查看帖子。
    预期结果:用户C能看到帖子的点赞状态同步,即显示已点赞。
  • 帖子删除点赞状态处理:
    输入:用户A发表的帖子,用户B登录微信,点赞帖子后,用户A删除了帖子。
    预期结果:用户B的点赞状态被移除,点赞数减少。
  • 并发点赞场景:
    输入:用户A发表的帖子,同时有多个用户登录微信,对帖子进行并发点赞操作。
    预期结果:系统应能正确处理并发点赞请求,不出现点赞数错误或冲突。
  • 点赞通知场景:
    输入:用户A发表的帖子,用户B登录微信,点赞帖子后,用户A收到点赞通知。
    预期结果:用户A收到正确的点赞通知,通知内容包含点赞人的信息。
  • 点赞记录场景:
    输入:用户A发表的帖子,用户B、C、D登录微信,分别点赞帖子。
    预期结果:用户A能够查看到所有点赞人的记录和信息。
  • 点赞按钮状态场景:
    输入:用户A发表的帖子,用户B登录微信,查看帖子,点赞按钮状态显示。
    预期结果:点赞按钮显示未点赞状态,用户B可以点击点赞。
  • 点赞数更新频率场景:
    输入:用户A发表的帖子,用户B登录微信,多次点击点赞按钮。
    预期结果:点赞数应及时更新,避免延迟。

了解过哪些测试工具(Selenium)

语音发送的测试用例

  • 语音发送成功场景:
    输入:用户A点击语音发送按钮,录制一段语音,选择发送给用户B。
    预期结果:用户B收到语音消息,能够正常播放收到的语音。
  • 语音录制时长限制:
    输入:用户A点击语音发送按钮,录制一段超过允许时长的语音。
    预期结果:系统应该限制语音录制时长,防止录制超时。
  • 录制中断恢复:
    输入:用户A正在录制语音,突然退出或中断录制过程。
    预期结果:系统应该保存已录制的部分语音,用户可以继续录制。
  • 语音播放功能:
    输入:用户A收到语音消息,点击播放按钮。
    预期结果:语音能够正常播放,声音清晰,没有杂音。
  • 语音发送中断处理:
    输入:用户A发送语音消息给用户B,发送过程中网络中断。
    预期结果:系统应该能够在网络恢复后继续发送未完成的语音消息。
  • 多人聊天语音发送:
    输入:用户A、B、C在同一个聊天会话中,用户A发送语音消息。
    预期结果:用户B和C都能够收到并播放用户A发送的语音消息。
  • 语音消息删除:
    输入:用户A发送语音消息给用户B,用户B收到后删除消息。
    预期结果:语音消息被成功删除,不再在用户B的聊天记录中显示。
  • 语音消息通知:
    输入:用户A发送语音消息给用户B,用户B不在线。
    预期结果:用户B上线后能够收到未读的语音消息通知。
  • 语音消息保存:
    输入:用户A发送语音消息给用户B,用户B收到后退出应用。
    预期结果:用户B再次进入应用时,能够看到之前收到的语音消息。
  • 不同网络环境测试:
    输入:用户A在不同网络环境(3G、4G、Wi-Fi)下发送语音消息。
    预期结果:系统应该能够适应不同网络环境,确保语音发送稳定。

测试当前面试界面,给出测试用例。

  • 面试取消功能测试:
    输入:用户已预约面试时间,进入面试界面,选择取消面试。
    预期结果:成功取消面试,系统更新预约状态并显示相关信息。
  • 界面兼容性测试:
    输入:使用不同浏览器、不同设备(PC、移动设备)访问面试界面。
    预期结果:界面在不同浏览器和设备上均能正常显示和交互。
  • 界面加载性能测试:
    输入:多个用户同时访问面试界面。
    预期结果:界面在高并发情况下能够保持正常加载和响应。
  • 异常情况处理测试:
    输入:用户进入面试界面时网络中断或系统崩溃。
    预期结果:系统应该有适当的错误处理机制,如友好的错误提示或自动恢复。

如果在 Postman 中模拟需要用户名密码鉴权后又需要进行短信验证的场景,可以按照以下步骤进行处理:

  • 使用用户名和密码鉴权:
    • 在 Postman 中,打开你要测试的请求。
    • 在请求的 Headers 或 Authorization 部分,添加适当的用户名和密码。可以使用基本认证(Basic Auth)或其他适合你的鉴权方式。
  • 获取短信验证码:
    • 手动从你的真实流程中获取短信验证码,并记下这个验证码。这个步骤需要在实际流程中完成,Postman 本身不能触发短信验证码的发送。
  • 使用环境变量:
    • 在 Postman 的环境管理器中,创建一个新的环境。
    • 在环境中设置一个变量,比如 verificationCode,将之前获取到的短信验证码作为值。
  • 修改请求:
    • 在你要测试的请求中,可以在请求的请求体中添加一个参数,比如 verificationCode,然后使用 {{verificationCode}} 来引用环境变量中保存的验证码。
  • 执行请求:
    • 确保你的请求在鉴权部分使用了用户名和密码,同时请求体中包含了短信验证码参数。
    • 执行请求,Postman 会自动将环境变量中的验证码作为请求参数,模拟了用户名密码鉴权后需要短信验证的场景。
  • 这种方法通过结合用户名密码鉴权和环境变量中的短信验证码来模拟了你所描述的场景。请注意,这只是一种模拟,实际应用中,你需要确保 API 在真实环境中正确处理了用户名密码和短信验证码的验证过程。

当你拥有已知的 Excel 文件链接,并希望在 Postman 中使用后置脚本来保存这个文件时,可以使用以下步骤:

  • 创建集合和请求:
    • 打开 Postman,创建一个新的集合,用于保存下载 Excel 文件的请求和操作。
    • 在集合中创建一个新的请求,用于获取 Excel 文件。
  • 设置请求参数:
    • 在请求中,设置请求方法为 GET,并在 URL 栏中输入 Excel 文件的链接。
  • 执行请求:
    • 执行请求,Postman 将会发送 GET 请求到指定的 Excel 文件链接。
  • 编写后置脚本:
    • 在请求的 “Tests” 部分,使用 JavaScript 编写后置脚本,以保存文件到本地。
const fs = require('fs');
// 迭代次数(可以根据你的需求进行设置)
const iteration = pm.variables.get('iteration');
// 根据迭代次数生成不同的文件名
const saveFilePath = `/path/to/save/excel_${iteration}.xlsx`;
// 将响应数据保存到文件
fs.writeFileSync(saveFilePath, pm.response.text(), 'binary');
console.log('Excel file saved:', saveFilePath);

算法

  • 手写数组轮转算法
import java.util.*;
public class Solution {
    public int[] solve (int n, int m, int[] nums) {
        //添加一个int temp ,循环M次。
        int temp;

        for(int i = 0;i<m;i++){
            temp = nums[n-1];
            for(int j=n-1;j>0;j--){
                nums[j]=nums[j-1];
            }
            nums[0]=temp;
        }
        return nums;
    }
}
  • 手撕 字符串转数字(主要是考察考虑的情况是否详尽,不能用integer.value)
public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String str = scanner.nextLine();
        double result = 0;
        boolean flag = true;
        double decimal = 0.1;
        if(str.charAt(0)!='-'){
            for(int i=0;i<str.length();i++){
                if(str.charAt(i)!='.' ){
                    if(flag!=false){
                        result = result*10 + (str.charAt(i)-'0');
                    }
                    else {
                        result = result + (str.charAt(i)-'0')*decimal;
                        decimal*=0.1;
                    }
                }
                else {
                    flag = false;
                }
            }
        }
        else {
            for(int i=1;i<str.length();i++){
                if(str.charAt(i)!='.' ){
                    if(flag!=false){
                        result = result*10 - (str.charAt(i)-'0');
                    }
                    else {
                        result = result - (str.charAt(i)-'0')*decimal;
                        decimal*=0.1;
                    }
                }
                else {
                    flag = false;
                }
            }
        }
        System.out.println(result);
    }
}
  • 最熟的排序说两种
  • 手撕斐波那契
import java.util.*;
public class Solution {
   public int Fibonacci (int n) {
       if(n==1||n==2)return 1;
       else return Fibonacci(n-1)+Fibonacci(n-2);
   }
}
  • 手撕代码,删除链表中的指定节点-怎么优化到O(1)。
public void removeNode(Node<T> node){
    if(node==null){
        return;
    }
    if(node.next!=null){
        node.value=node.next.value;//当前节点node,下一个节点value赋值为当前节点
        node.next=node.next.next;//删除node的下一个节点
        }//O(1)
    //如果为尾节点的话
    removeTail();//O(n)
    }
  • 找字符串中的回文串。
  • 如果有一个大量的整型数据集,找出前100个最大的。
  • 最长连续子序列
  • 设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可输入: arr = [1,3,5,7,2,4,6,8], k = 4 ,输出: [1,2,3,4] 能用几种方法就几种。
  • 排序题,大概是对25.22.11.20,25.22.12.10这种数排序。【快手】
  • BM83 字符串变形【快手】
  • 两个json字符串比较【快手】
  • 三数之和【快手】
  • 去掉字符串的前后空格【oppo】
  • 字符串分割【oppo】
  • 字符串替换某个字符【oppo】
  • target=9 输出[2,3,4],[4,5]【快手】
  • 盛最多水的容器
  • 用两个栈实现队列–》简述方法
  • 第K大的值的索引位置。例如[1,4,2,5,6],第2大的值出现的索引位置,应该是3 - 设计测试用例
  • 手撕合并有序链表
  • 手撕使用线程池创建10个线程打印“ABC”【百度】
  • 加个机制,线程池执行完,主线程才执行【百度】
  • 手撕:一个字符串中提取数字,进行排序快排,分析时间复杂度,空间复杂度【百度】
  • 手撕字符串匹配,及KMP实现方式,分析时间复杂度,空间复杂度
  • 手撕两数之和,分析时间复杂度,空间复杂度
  • 开始手撕,从一个文件里面检索出“xxx”字符串出现的次数,按照一行一行读取(需要考虑大数据量情况,当时没注意)- 手撕的代码进行测试,如何测试【百度】
  • 做题 判断这个字符串是否是回文字符串”zbccbz”【小红书】
  • 手撕:判断回文、最长公共前缀。
  • 手撕:树的的前序遍历;
  • 中序遍历二叉树,不用递归
  • 手撕一个题,统计一篇文章中每个单词出现的次数。
  • 归并排序
  • 手撕二分搜索
  • 字符串的所有子串
  • 字符串全排列
  • 买卖股票最佳时机Ⅲ
  • 快排,冒泡
  • 最长无重复字符字串
  • 最长回文字子串
import java.util.*;


public class Solution {
    public int getLongestPalindrome (String str) {
        int flag = 0;
        for(int len=1;len<=str.length();len++){
            for(int i=0;i<=str.length()-len;i++){
                if(isHui(str,i,len)){
                    if(len>flag){
                        flag=len;
                    }
                }
            }
        }
        return flag;
    }
    private static boolean isHui(String str,int i,int len){
        String subStr = str.substring(i,i+len);
        int start = 0;
        int end = subStr.length()-1;
        while(true){
            if(subStr.charAt(start)!=subStr.charAt(end)){
            return false;
        }
        start++;
        end--;
        if(start>=end)break;
        }
        return true;
    }
}
  • 链表删除倒数第n个节点 自己写测试用例,输入输出,链表也要自己建。
  • 排序算法,各个排序算法怎么实现,复杂度怎么样。
  • leetcode搜索二维矩阵,二分查找
  • 一个文件追加到另一个文件,文件重命名,文件查找字符串
  • 直接手撕三道,n*m能画出正方形的个数,两个子节点的最矮公共子树,登台阶问题
  • 代码:两个栈实现一个队列

SQL

  • sql 大于平均分的学生。
  • Mysql索引。
  • 关联查询。
  • 统计学生成绩不合格的课程数。
  • mysql 查找表中重复的邮箱
  • 一个字符串,不使用库函数,统计字符串长度 - 对上面的统计字符串长度代码进行测试
  • 三张表 学生表(学生id、名字) 学生分数表(学生id、科目id、以及科目分数) 学生科目表(科目id、科目名称)查询学生a的数学成绩。
  • SQL索引是什么,索引失效
  • 乐观锁、悲观锁是什么,什么时候用、如何实现(应该是代码层面的,这个如何实现没答上来)
  • 查出tb_score表中数学成绩排在前10的学生的姓名和成绩
  • sql 删除重复电子邮箱
  • sql语句 两个表联表查询,一个是员工信息(包含员工分数),一个是部门信息,求平均分数超过八十的的部门名称和平均分数
  • sql学生表,得到排名前三的学生
  • 说一下聚合函数的关键字
  • mysql修改所有成绩>60变90

一道手撕MySQL 给定两个表,查询当前班级分数排名前5的学生名字。

智力考察

一个飞机上有100个人,只有一个人阳了,如何用最少的试纸检测出谁阳了。

  • 对半测试,最多7次

有八个小球,其中有一个小球比另外七个都重(其他的七个一样重),一个天平,如何两次找出那个偏重的小球。

  • 第一次比较: 将这八个小球分成三组:A、B、C。每组各放三个小球,剩下两个小球不放。然后使用天平比较 A 组和 B 组的重量。
    • 如果天平平衡,说明偏重的小球不在 A 组和 B 组中,而在 C 组中。
    • 如果天平倾斜,说明偏重的小球在 A 组和 B 组中的其中一个。
  • 第二次比较: 假设天平倾斜,即 A 组比 B 组重。
    • 将 A 组的三个小球分成三组:A1、A2、A3。将 B 组的三个小球中随机选两个小球分别放在 A1 组和 A2 组中,留下一个小球不放(记作 X 小球)。
    • 将剩下的两个小球(不在 A 组和 B 组中的小球)放在天平的两边比较。
      • 如果天平平衡,说明 X 小球为偏重的小球。
      • 如果天平倾斜,说明 A1 组中的小球为偏重的小球。

有不限量的水,和5L、6L的容器,怎么取3L水?

  • 5L转满倒入6L,此时6L还有1L为null
  • 再转满5L,倒入6L,直到装满倒掉,接收余下4L。
  • 再转满5L,倒入6L,直到装满倒掉。5L杯子里面有3L水。

智力题:一个真话村一个假话村,分叉路口通行,一个村民一个问题,怎么去真话村?

  • 选择一个村民: 选择任意一个村民,不论是在真话村还是假话村。

  • 询问问题: 问选中的村民一个问题:“你是真话村的居民吗?”

    • 如果该村民回答 “是”,那么他无论在真话村还是假话村,都会说真话。这意味着你选中了真话村的居民,所以可以继续前往真话村。
    • 如果该村民回答 “不是”,那么无论他在哪个村庄,都会说假话。这意味着你选中了假话村的居民,所以可以通过排除法,走另一条路前往真话村。

6.超多数,一亿个量级的那种,找到最大的10个

  • 建立一个最小堆: 初始化一个包含前10个元素的最小堆。遍历一亿个数,将前10个数加入堆中,保持堆的大小为10。这样堆顶就是这10个数中最小的数。

  • 遍历剩余数据: 遍历剩下的数(从第11个开始),对于每个数,如果它大于堆顶的数,就将堆顶的数替换为当前数,并进行堆的维护操作。

  • 获取最大的10个数: 最终堆中会保存最大的10个数,它们就是你要找的最大的10个数。

7.糖盐问题,提及相等的糖和盐,往盐里放勺糖,再从盐里取勺盐放回去,哪个含其他的东西多

  • 无论你是将糖放入盐瓶还是将盐放入糖盒,最终两个容器里的糖和盐的比例仍然是相等的。因此,最终哪个容器含有其他的东西多是没有差异的,它们仍然是相等的。这是因为每次操作都是等概率地取糖或盐,保持了两个容器中糖和盐的比例不变。

智力题一:经典蜡烛问题:有两根等长的蜡烛,每根可以燃烧一小时。你没有手表,火也不稳定。如何用这两根蜡烛来测量出 45 分钟的时间?

  • 一根点一头,另一根两头都点,那么当两头都点的那根烧完就要用半个小时,这时剩下的那半根如果也两头一起点的话,到它烧完就正好15分钟

智力题二:沙漏问题:你有两个相同的沙漏,一个可以计时 7 分钟,另一个可以计时 4 分钟。如何用这两个沙漏来计时 9 分钟?

  • 首先,同时让四分钟和7分钟的两个沙漏开始计时,四分钟后,那个四分钟的沙漏会漏完,我们再次把四分钟的沙漏倒过来,再过三分钟,7分钟的沙漏也漏完了,我们把它也倒过来,当四分钟的沙漏第二次漏完时,这时正好总共过去8分钟,七分钟的沙漏第二次计时正好过去1分钟,于是再次把七分钟的沙漏倒过来,当它漏完之后,正好9分钟!

勾子函数实践

  • 注册对应事件的监听器,一般系统事件管理器通过Map来接收,格式为<eventName,Listener>。
  • 源码中事件发生后,会在注册表中(Map)获取对应事件的所有监听器,然后遍历执行。
import java.util.HashMap;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;

// 定义事件监听器接口
interface EventListener {
    void onEvent(String eventData);
}

// 事件管理器类
class EventManager {
    private Map<String, List<EventListener>> subscribers = new HashMap<>();

    // 订阅事件
    public void subscribe(String eventType, EventListener listener) {
        subscribers.computeIfAbsent(eventType, k -> new ArrayList<>()).add(listener);
    }

    // 发布事件
    public void publish(String eventType, String eventData) {
        List<EventListener> listeners = subscribers.get(eventType);
        if (listeners != null) {
            for (EventListener listener : listeners) {
                listener.onEvent(eventData);
            }
        }
    }
}

// 具体事件监听器
class MyEventListener implements EventListener {
    private String name;

    public MyEventListener(String name) {
        this.name = name;
    }

    @Override
    public void onEvent(String eventData) {
        System.out.println(name + " received event: " + eventData);
    }
}

public class HookFunctionExample {
    public static void main(String[] args) {
        EventManager eventManager = new EventManager();

        // 注册钩子函数(事件监听器)
        eventManager.subscribe("event_type1", new MyEventListener("Listener 1"));
        eventManager.subscribe("event_type2", new MyEventListener("Listener 2"));
        eventManager.subscribe("event_type1", new MyEventListener("Listener 3"));

        // 模拟事件触发,执行钩子函数
        eventManager.publish("event_type1", "Hello from event_type1");
        eventManager.publish("event_type2", "Hello from event_type2");
    }
}

AOP思想实践

  • 当谈到AOP的具体应用,一个常见的示例是在方法调用前后记录日志。下面是一个使用Java和Spring框架的简单例子,演示如何使用AOP来实现日志记录的横切关注点:
  • 日志切面类:
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.MyService.*(..))")
    public void beforeAdvice() {
        System.out.println("Before method execution: Logging started...");
    }

    @After("execution(* com.example.MyService.*(..))")
    public void afterAdvice() {
        System.out.println("After method execution: Logging completed.");
    }
}

业务服务类:

import org.springframework.stereotype.Service;

@Service
public class MyService {

    public void doSomething() {
        System.out.println("Doing something...");
    }
}

主应用程序:

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(AppConfig.class);

        MyService myService = context.getBean(MyService.class);
        myService.doSomething();

        context.close();
    }
}

配置类:

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan(basePackages = "com.example")
@EnableAspectJAutoProxy
public class AppConfig {
}
  • 在这个示例中,我们创建了一个名为 MyService 的业务服务类,其中包含一个简单的方法 doSomething()。然后,我们使用AOP创建了一个名为 LoggingAspect 的切面类,通过在切面类中定义 @Before 和 @After 注解,分别在方法调用前和方法调用后执行日志记录操作。
    在主应用程序中,我们使用Spring的 AnnotationConfigApplicationContext 来加载配置类并获取 MyService 的实例。然后调用 doSomething() 方法,触发切面中定义的日志记录。

使用切点的例子

业务服务类:

import org.springframework.stereotype.Service;

@Service
public class MyService {

    public void doSomething() {
        System.out.println("Doing something...");
    }

    public void doAnotherThing() {
        System.out.println("Doing another thing...");
    }
}

切面类:

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    @Pointcut("execution(* com.example.MyService.*(..))")
    public void myServiceMethods() {}

    @After("myServiceMethods()")
    public void afterAdvice() {
        System.out.println("After method execution: Logging completed.");
    }
}
  • 在这个示例中,@Pointcut 注解用于定义切入点,将切入点表达式 execution(* com.example.MyService.*(…)) 与 myServiceMethods() 方法相关联。这个方法本身没有实际逻辑,只是作为一个切入点的标识。
  • 切入点方法可以被多个切面共享。

AOP有哪些注解

  • @Aspect: 用于定义切面类,将普通的Java类标识为切面。
  • @Before: 在目标方法执行之前执行切面逻辑。
  • @After: 在目标方法执行之后执行切面逻辑,无论方法是否出现异常。
  • @AfterReturning: 在目标方法正常返回后执行切面逻辑。
  • @AfterThrowing: 在目标方法抛出异常后执行切面逻辑。
  • @Around: 在目标方法前后执行切面逻辑,可以控制是否执行目标方法以及如何处理返回值。
  • @Pointcut: 定义切入点,用于声明哪些方法会被织入切面逻辑。
  • @Order: 控制切面的执行顺序,值越小越先执行。
  • @EnableAspectJAutoProxy: 启用自动代理,使AOP注解生效。

AOP底层原理

  • 主要通过代理来实现功能增强,当程序加载切面类的时候,Spring会通过动态代理生成对应的代理类,并替换掉原始的被代理类,因此,进行对象依赖注入的时候,注入的是代理类。

  • 代理生成方式: Spring 使用不同的方式来创建代理对象,这些方式对应了不同的代理类型。主要有 JDK 动态代理和 CGLIB 动态代理两种方式。

    • JDK 动态代理: 当目标对象实现了至少一个接口时,Spring 使用 JDK 动态代理。在这种情况下,代理对象实现了目标对象实现的接口,并且可以强制类型转换为该接口类型。

    • CGLIB 动态代理: 当目标对象没有实现任何接口时,Spring 使用 CGLIB 动态代理。在这种情况下,Spring 会生成一个继承自目标对象的子类,并在子类中插入切面逻辑。

BUG的生命周期

  • QA新建BUG-》QA指派BUG-》RD确认BUG-》RD解决BUG-》QA验证BUG-》QA关闭BUG

后端进行业务逻辑处理时往往会忽略的问题

  • 边界情况:处理常规情况通常是相对容易的,但往往忽略了边界情况,如输入为空、超出范围或未定义的情况。这些情况可能导致未处理的异常或意外的行为。

  • 错误处理:不足的错误处理可能导致不明确的错误消息或不稳定的应用程序状态。应该仔细考虑错误情况,并为其提供适当的处理和反馈。

  • 资源管理:资源管理涉及到文件句柄和其他资源的正确释放。未正确管理资源可能导致资源泄漏,最终导致性能下降或崩溃。

  • 并发问题:并发问题包括数据竞争、死锁和资源争夺等,这些问题在多个线程或用户同时访问后端逻辑时可能发生。它们可能导致应用程序的不稳定性。

  • 不安全的输入处理:未正确处理输入数据可能导致安全漏洞,如SQL注入。开发人员应该使用输入验证和过滤来减轻这些风险。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值