Skip to content

腾讯 后台开发 一面凉经

来源:https://www.nowcoder.com/discuss/599637537040232448

1、算法题:最大重复子串(LeetCode.1668)

问题描述

给你一个字符串 sequence ,如果字符串 word 连续重复 k 次形成的字符串是 sequence 的一个子字符串,那么单词 word重复值为 k 。单词 word最****大重复值 是单词 wordsequence 中最大的重复值。如果 word 不是 sequence 的子串,那么重复值 k0

给你一个字符串 sequenceword ,请你返回 最大重复值 k

详细思路

  1. 初始化 k=1,最大重复值 maxRepeat=0。
  2. 使用循环,不断增加 k 的值。
  3. 检查 word 的 k 倍串是否是 sequence 的子串,如果是,更新 maxRepeat 的值为当前 k 的值。
  4. 如果 word 的 k 倍串不再是 sequence 的子串,结束循环,返回 maxRepeat。

Java代码

class Solution {
    public int maxRepeating(String sequence, String word) {
        int k = 1; // 初始化 k=1
        int maxRepeat = 0; // 最大重复值初始化为0
        while (sequence.contains(word.repeat(k))) { // 循环直到 word 的 k 倍串不再是 sequence 的子串
            maxRepeat = k; // 更新最大重复值为当前 k 的值
            k++; // 增加 k 的值
        }
        return maxRepeat; // 返回最大重复值
    }
}

2、java的反射机制?

Java的反射机制指的是在运行时动态获取类的信息并操作类的属性、方法、构造方法等。通过反射,可以在运行时获取类的各种信息,如类名、父类、实现的接口、字段、方法等,并可以在运行时动态创建对象、调用方法、访问或修改属性等。

  1. Class类:Java反射机制的核心类是Class类,它表示一个类的信息,包括类的名称、字段、方法等。通过Class类的静态方法forName()可以根据类名获取Class对象,通过getClass()方法可以获取对象的Class对象。
  2. 获取Class对象:有三种方式获取Class对象:
    • 调用对象的getClass()方法。
    • 使用类名.class语法。
    • 使用Class.forName()方法。
  3. 获取类的信息:通过Class对象可以获取类的信息,如类名、包名、父类、接口、字段、方法等。
  4. 创建对象:通过Class对象可以动态创建类的实例,使用newInstance()方法可以调用类的无参构造方法创建对象。
  5. 访问和修改字段:通过Field类可以访问和修改类的字段,可以使用get()和set()方法获取和设置字段的值。
  6. 调用方法:通过Method类可以调用类的方法,可以使用invoke()方法调用方法。
  7. 操作构造方法:通过Constructor类可以操作类的构造方法,可以使用newInstance()方法创建对象。
  8. 动态代理:反射机制可以实现动态代理,动态代理是一种在运行时动态创建代理对象的技术。

应用场景:

  • 设计模式中的工厂模式、代理模式等。
  • Java框架中如Spring、Hibernate等。
  • 序列化和反序列化。
  • 动态加载类和资源等。

反射机制虽然提供了灵活性,但也存在一些缺点,如性能开销较大、安全性问题、代码可读性较差等。

3、java的类加载机制?

Java的类加载机制是Java虚拟机(JVM)在运行时将类加载到内存中并对其进行初始化的过程。

类加载机制主要分为三个阶段:加载、链接和初始化。

  1. 加载:加载是指将类的.class文件加载到内存中。类加载器负责将.class文件加载到内存,并生成对应的Class对象。类加载器根据类的全限定名在类路径中查找对应的.class文件,然后读取该文件的内容,并创建Class对象。
  2. 链接:链接是指将类的二进制数据合并到JVM的运行时环境中。链接阶段包括三个步骤:验证、准备和解析。
    • 验证:验证类的格式、语义等,确保类文件符合JVM规范。
    • 准备:为类的静态变量分配内存,并初始化为默认值。
    • 解析:将类、接口、字段和方法的符号引用解析为直接引用。
  3. 初始化:初始化是类加载的最后阶段,是类加载过程中的最后一步。在初始化阶段,JVM会执行类的初始化方法,包括静态字段赋值、静态代码块执行等。类初始化时机包括:创建类的实例、访问类的静态变量、调用类的静态方法、初始化子类等。

特点:

  • 委托机制:类加载器采用双亲委托机制,即当一个类加载器收到类加载请求时,先委托给父类加载器加载,如果父类加载器无法加载,则由子类加载器加载。
  • 可见性:父类加载器加载的类对子类加载器是可见的,但是子类加载器加载的类对父类加载器是不可见的,实现了类加载器的隔离性。
  • 单一性:对于每一个类,只会被加载一次,不会重复加载。

4、Spring的AOP?

Spring的AOP(面向切面编程)是一种编程范式,用于将横切关注点(如日志、事务、安全等)与核心业务逻辑分离,以提高代码的模块化、可重用性和可维护性。在Spring中,AOP通过代理机制实现,在运行时动态地将横切逻辑织入到目标对象的方法中。

  1. 切面(Aspect):切面是横切关注点的模块化。在Spring中,切面通常是一个类,其中包含了要在多个类中重复使用的横切逻辑(如日志记录、事务管理等)。
  2. 连接点(Join Point):连接点是在应用程序执行过程中可以插入切面的点。在Spring中,连接点通常表示方法的执行。
  3. 通知(Advice):通知是切面在特定连接点执行的动作。Spring定义了五种类型的通知:前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)和环绕通知(Around)。
  4. 切点(Pointcut):切点是一个表达式,用于确定在何处应该应用通知。通常使用切点表达式指定目标方法的执行。
  5. 引入(Introduction):引入允许向现有类添加新方法或属性。Spring AOP不支持引入。
  6. 目标对象(Target Object):目标对象是一个类,其中定义了切面所要横切的方法。
  7. AOP代理(AOP Proxy):在Spring中,AOP代理是指Spring通过代理模式创建的对象,用于将横切逻辑织入到目标对象的方法中。

Spring AOP的工作原理是通过代理机制实现的。当应用程序使用Bean时,Spring AOP会自动创建代理对象,代理对象会拦截对目标对象的方法调用,并在调用前后执行相应的通知逻辑,从而实现横切关注点的功能。

5、java中HashMap遍历删除的方法?

  1. 使用Iterator迭代器:使用Iterator迭代器遍历HashMap,并在遍历过程中使用Iterator的remove()方法删除元素,这是安全的方法。
HashMap<Integer, String> map = new HashMap<>();
// 添加元素到map中
Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<Integer, String> entry = iterator.next();
    // 根据条件删除元素
    if (condition) {
        iterator.remove(); // 使用Iterator的remove方法删除元素
    }
}
  1. 使用forEach遍历:在Java 8及以上版本中,可以使用forEach方法遍历HashMap,并使用Iterator的remove()方法删除元素。
HashMap<Integer, String> map = new HashMap<>();
// 添加元素到map中
map.entrySet().removeIf(entry -> condition); // 使用removeIf方法删除元素

但是需要注意的是,在以上两种方法中,删除元素时要使用Iterator的remove()方法而不是直接调用HashMap的remove方法,否则会抛出ConcurrentModificationException异常。

6、TCP和UDP?

  1. TCP(传输控制协议):
    • 可靠性:TCP是一种面向连接的协议,提供可靠的数据传输。它通过数据重传、校验和、确认和超时等机制来确保数据的可靠性,保证数据按顺序到达且不丢失。
    • 连接性:TCP使用三次握手建立连接和四次挥手关闭连接,确保通信双方能够建立可靠的通信信道。
    • 效率:由于提供可靠性和连接性,TCP的数据传输效率相对较低。它需要维护连接状态、保证数据的可靠性,这些额外的操作会增加网络开销和延迟。
  2. UDP(用户数据报协议):
    • 可靠性:UDP是一种无连接的协议,不提供可靠性保证。它只是简单地将数据包发送到目的地,不保证数据的顺序和可靠性。
    • 连接性:UDP不需要建立连接,因此不需要三次握手和四次挥手的过程,通信双方可以直接发送数据包。
    • 效率:UDP的数据传输效率较高,因为它不需要维护连接状态和保证数据的可靠性。它适用于实时性要求高、对数据丢失要求不严格的应用场景,如音视频传输、实时游戏等。

7、UDP的应用场景?

  1. 实时性要求高的应用:UDP适用于对实时性要求较高的应用场景,如在线游戏、实时视频会议、语音通话等。在这些应用中,快速传输数据比可靠性更为重要。
  2. 音视频传输:UDP常用于音频和视频传输领域,如IP电话、流媒体等。由于UDP传输速度快且实时性好,适合这些对实时性要求高的应用。
  3. 广播和多播:UDP支持广播和多播功能,可以将数据一次性发送给多个接收者,适用于一些需要向多个客户端发送相同数据的场景。
  4. 简单的请求-响应通信:UDP适合一些简单的请求-响应通信,如DNS查询、SNMP等,因为这些应用对传输延迟较为敏感。
  5. 传感器数据传输:对于一些传感器数据传输的场景,如工业控制、物联网等,UDP可以提供高效的数据传输方式。

8、微信和QQ采用的协议?

微信和QQ采用的通信协议不仅包括UDP,还包括TCP,具体使用哪种协议取决于通信的功能和要求。通过合理选择和组合UDP和TCP,可以实现不同功能的通信需求,提高通信效率和用户体验。

9、怎样保证redis挂了,信息不丢失?

  1. 持久化:Redis提供了两种持久化方式,可以将内存中的数据保存到磁盘上,以防止数据丢失。
    • RDB(Redis Database Backup)持久化:定期将内存中的数据快照保存到磁盘上,适合用于备份。
    • AOF(Append Only File)持久化:将每条写操作日志追加到文件末尾,重启时重新执行日志,适合用于恢复。
  2. 主从复制:配置Redis主从复制,将主节点的数据复制到一个或多个从节点上,当主节点挂掉时,从节点可以接替主节点提供服务,避免数据丢失。
  3. 哨兵模式:使用Redis哨兵模式,可以监控Redis实例的状态,当主节点挂掉时,自动将一个从节点升级为主节点,确保服务的高可用性。
  4. 集群模式:配置Redis集群模式,将数据分布在多个节点上,当某个节点挂掉时,其他节点可以继续提供服务,保证数据的可靠性和高可用性。

10、多级缓存如何保证一致性?

多级缓存一致性是指在多级缓存系统中,各级缓存之间的数据保持一致,即当数据在某一级缓存中发生变化时,其他级缓存中的数据也能及时更新,保证数据的正确性和一致性。

  1. 缓存失效策略:当某一级缓存中的数据发生变化时,可以直接将其他级缓存中相关的数据标记为失效(Invalidation),下次访问时重新从数据库或上一级缓存中获取最新数据。这种策略适用于数据变更频率较低的场景。
  2. 缓存更新策略:当某一级缓存中的数据发生变化时,可以通过消息队列或事件通知的方式,将变更消息发送给其他级缓存,让其他级缓存及时更新对应的数据。这种策略适用于数据变更频率较高的场景。
  3. 缓存回写策略:当某一级缓存中的数据发生变化时,可以将变更的数据异步回写到其他级缓存中,保持各级缓存之间的数据一致性。这种策略适用于数据变更频率较低、但要求数据一致性较高的场景。
  4. 缓存更新通知策略:在更新某一级缓存中的数据时,同时向其他级缓存发送更新通知,让其他级缓存立即更新相应的数据。这种策略适用于对数据一致性要求较高的场景,但会增加系统的复杂度和开销。

11、count(1) count(*) count(字段) 比较?

  1. COUNT(1):它会统计查询结果集中的行数。这种写法的优势在于COUNT(1)是一个常量表达式,不会涉及具体的列值,因此在某些数据库中可能会比较高效。

    sql
    SELECT COUNT(1) FROM table_name;
  2. COUNT(*):同样会统计查询结果集中的行数。不同于COUNT(1)COUNT(*)会统计所有行,包括NULL值行。在某些数据库中,COUNT(*)可能会比COUNT(1)稍慢一些,因为它需要检查每一行的数据是否为NULL。例如:

    sql
    SELECT COUNT(*) FROM table_name;
  3. COUNT(字段)COUNT(字段)则会统计指定字段的非NULL值的行数,忽略NULL值。如果指定的字段是主键或者非NULL约束的列,使用COUNT(字段)可以更精确地统计行数。例如:

    sql
    SELECT COUNT(column_name) FROM table_name;

12、k8s和docker?

  1. Docker:
    • Docker是一个开源的容器化平台,用于开发、打包、运行应用程序的工具。它提供了一种将应用程序和其依赖项打包到一个可移植的容器中的方式,使应用程序可以在任何环境中以相同的方式运行。
    • Docker将应用程序打包成镜像(image),镜像包含了应用程序的代码、运行时环境、库和依赖项。这样可以保证应用程序在不同的环境中具有一致的运行结果。
    • Docker提供了一套命令行工具和API,用于管理和操作容器,包括创建、启动、停止、删除容器等操作。
  2. Kubernetes:
    • Kubernetes是一个开源的容器编排引擎,用于自动部署、扩展和管理容器化应用程序。它可以管理多个容器化应用程序,提供了强大的容器编排、自动化部署、服务发现和负载均衡等功能。
    • Kubernetes可以在多个主机上自动部署容器,并根据应用程序的需求进行动态扩展和缩减。它还提供了故障恢复、日志记录、监控等功能,帮助用户管理大规模容器化应用程序的运行。

Docker主要关注于容器的打包和运行,而Kubernetes则主要关注于容器的编排和管理。使用Docker可以将应用程序打包成容器镜像,并在本地或者云端运行,而使用Kubernetes可以对多个容器进行编排和管理,实现高可用、可扩展的容器化应用程序的部署和管理。

13、java中多线程有哪几种实现方式?

  1. 继承Thread类:定义一个类继承自Thread类,并重写run()方法来实现多线程的任务。然后创建该类的实例并调用start()方法启动线程。示例代码如下:

    class MyThread extends Thread {
        public void run() {
            // 线程执行的任务
        }
    }
    MyThread thread = new MyThread();
    thread.start();
  2. 实现Runnable接口:定义一个类实现Runnable接口,并实现run()方法来实现多线程的任务。然后创建该类的实例,并将其作为参数传递给Thread类的构造方法,并调用start()方法启动线程。示例代码如下:

    class MyRunnable implements Runnable {
        public void run() {
            // 线程执行的任务
        }
    }
    Thread thread = new Thread(new MyRunnable());
    thread.start();
  3. 使用匿名内部类:可以使用匿名内部类的方式来实现Runnable接口,简化代码。示例代码如下:

    Thread thread = new Thread(new Runnable() {
        public void run() {
            // 线程执行的任务
        }
    });
    thread.start();
  4. 使用Lambda表达式:Java 8引入了Lambda表达式,可以进一步简化代码。示例代码如下:

    Thread thread = new Thread(() -> {
        // 线程执行的任务
    });
    thread.start();
  5. 使用线程池:通过线程池来管理线程的创建和执行,可以有效地重用线程、控制线程数量和管理线程的生命周期。示例代码如下:

    ExecutorService executor = Executors.newFixedThreadPool(10);
    executor.submit(() -> {
        // 线程执行的任务
    });
    executor.shutdown();

14、生产环境如何使用多线程?

  1. 任务划分:将任务划分为适当的粒度,以便并行执行。任务划分过细会导致线程间频繁切换和资源竞争,影响性能;划分过粗会导致部分线程空闲,无法充分利用系统资源。
  2. 线程管理:合理管理线程的生命周期,避免线程泄露和过多线程的创建。可以使用线程池来管理线程,根据需求调整线程数量,避免频繁创建和销毁线程。
  3. 线程安全:确保多个线程访问共享资源时的安全性。可以通过加锁(如synchronized关键字、Lock接口)、使用线程安全的集合类(如ConcurrentHashMap、CopyOnWriteArrayList)等方式来保证线程安全。
  4. 异常处理:及时捕获和处理线程中的异常,避免因为一个线程的异常导致整个应用程序的崩溃。可以在run()方法中使用try-catch块来捕获异常,并记录日志或者进行适当的处理。
  5. 性能监控:监控多线程任务的执行情况和性能表现,及时发现并解决性能瓶颈和问题。可以使用监控工具、日志记录等方式进行监控。
  6. 资源管理:合理管理系统资源,包括内存、CPU、网络等资源。避免因为多线程导致资源耗尽或者资源竞争问题。
  7. 任务调度:根据任务的优先级和依赖关系进行合理的任务调度,确保任务能够按照预期的顺序和时间执行。

15、工程化部署服务的方案?

16、项目中的安全保证?

17、OAuth2.0?

OAuth 2.0是一个授权框架,用于授权第三方应用程序访问受保护资源,而无需将用户凭据暴露给第三方应用程序。OAuth 2.0的主要目的是为了解决用户在多个网站和移动应用程序之间共享资源的问题,同时确保用户的安全性和隐私性。

  1. 资源所有者(Resource Owner):拥有受保护资源的用户,可以是一个人或者一个应用程序。
  2. 客户端(Client):请求访问受保护资源的第三方应用程序,可以是Web应用、移动应用或者后端服务。
  3. 授权服务器(Authorization Server):负责验证资源所有者的身份,并颁发访问令牌(Access Token)给客户端。
  4. 资源服务器(Resource Server):存储受保护资源的服务器,负责接收和处理访问令牌,并根据访问令牌授权对资源的访问。

OAuth 2.0定义了多种授权方式(Grant Types):

  • 授权码模式(Authorization Code Grant):用于Web应用的授权,客户端在重定向URI中收到授权码,并通过授权码获取访问令牌。
  • 隐式授权模式(Implicit Grant):适用于Web应用,客户端通过重定向URI直接获取访问令牌。
  • 密码模式(Resource Owner Password Credentials Grant):资源所有者将用户名和密码直接提供给客户端,客户端使用这些凭据向授权服务器请求访问令牌。
  • 客户端凭据模式(Client Credentials Grant):适用于后端服务,客户端使用自己的凭据向授权服务器请求访问令牌。

18、微服务之间通信如何保证安全性?

  1. 使用安全通信协议:微服务之间的通信可以使用安全的通信协议,如HTTPS,以加密通信内容,防止被中间人攻击窃取信息。
  2. 身份验证:微服务之间通信时需要进行身份验证,确保通信双方的身份合法。可以使用基于令牌的身份验证机制,如OAuth 2.0,JWT等。
  3. 访问控制:通过访问控制机制,限制微服务之间的通信权限,确保只有授权的服务才能进行通信。
  4. 数据加密:对通信内容进行加密,确保数据在传输过程中的安全性,可以使用对称加密和非对称加密等方式。
  5. 防止重放攻击:使用合适的机制防止重放攻击,如使用时间戳或者随机数来防止请求被重复使用。
  6. 安全审计和监控:对微服务之间的通信进行安全审计和监控,及时发现异常行为和安全漏洞。
  7. 容器安全:如果使用容器技术部署微服务,需要确保容器本身的安全,包括镜像安全、容器运行时安全等。