0.记录:
A.手撕关注点:设计模式的具体实现+代码随想录+juc部分,特别是涉及到线程池的结构。
B.场景题:强化阶段需要补充,25年6月前保持八股稳定记忆和复习。
C.八股部分:
-
Mysql
-
elasticSearch
-
mongoDB
-
Redis
-
RabbitMq
-
Kafka
-
ZooKeeper
-
Netty
-
k8s and docker
1.某不知名小厂:
AOP(面向切面编程)的实现原理:
AOP 是一种编程范式,用于将横切关注点(cross-cutting concerns)从业务逻辑中分离出来。AOP 的实现原理主要依赖于代理模式。在 Java 中,AOP 通常通过以下两种方式实现:
-
静态代理:在编译时期,通过 AOP 框架生成代理类的字节码,直接编译成 class 文件。这种方式通常通过AspectJ等工具实现。
-
动态代理:在运行时期,通过反射机制动态创建目标对象的代理对象。Java 提供了
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口来实现动态代理。Spring AOP 就是基于动态代理实现的。以下是一些关于面向切面编程(AOP)的代码题目及其答案:
题目1:编写一个简单的AOP示例,实现对某个方法执行前后添加日志功能。
答案1:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Pointcut; @Aspect public class LoggingAspect { @Pointcut("execution(* com.example.service.*.*(..))") public void businessMethods() {} @Before("businessMethods()") public void logBefore() { System.out.println("方法执行前,记录日志..."); } @After("businessMethods()") public void logAfter() { System.out.println("方法执行后,记录日志..."); } }
题目2:编写一个AOP切面,实现对某个方法进行权限校验。
答案2:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; @Aspect public class PermissionAspect { @Pointcut("execution(* com.example.service.*.update*(..))") public void updateMethods() {} @Before("updateMethods()") public void checkPermission() { // 假设有一个方法用于校验权限 if (!hasPermission()) { throw new RuntimeException("没有权限执行该操作!"); } } private boolean hasPermission() { // 这里是模拟权限校验,实际项目中需要根据具体业务实现 return true; } }
题目3:编写一个AOP切面,实现对某个方法进行异常捕获并记录日志。
答案3:
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Pointcut; @Aspect public class ExceptionHandlingAspect { @Pointcut("execution(* com.example.service.*.*(..))") public void businessMethods() {} @AfterThrowing(pointcut = "businessMethods()", throwing = "e") public void logException(Throwable e) { System.out.println("捕获到异常:" + e.getMessage()); // 这里可以将异常信息记录到日志文件中 } }
这些题目和答案仅供参考,实际项目中可能需要根据具体需求进行调整。希望这些题目能帮助你更好地理解AOP。
-
SpringBoot 的装配过程和实现原理:
-
SpringBoot 的装配过程主要依赖于
SpringFactoriesLoader
类和@EnableAutoConfiguration
注解。以下是简要的装配过程:
-
启动类:SpringBoot 应用通常有一个带有
@SpringBootApplication
注解的启动类,这个注解包含了@EnableAutoConfiguration
。 -
自动配置:
@EnableAutoConfiguration
注解告诉 SpringBoot 启动时自动配置。SpringBoot 会读取classpath
下的META-INF/spring.factories
文件,查找并加载所有可用的配置类。 -
条件注解:自动配置类通常会使用条件注解(如
@ConditionalOnClass
、@ConditionalOnMissingBean
等)来确保只有在满足特定条件时才会应用配置。
-
关于其他开源经历,这需要根据个人经验来回答。
-
Java 里的强引用和弱引用:
-
强引用(Strong Reference):最常见的引用类型,如果一个对象具有强引用,那么垃圾回收器绝不会回收它。
-
弱引用(Weak Reference):指向一个对象,但并不足以保证对象的生命周期。如果一个对象只有弱引用,那么在垃圾回收器线程扫描时,不管当前内存是否足够,都会回收该对象。
其他两种引用类型:
-
软引用(Soft Reference):用于实现内存敏感的高速缓存。软引用指向的对象,当系统内存充足时,不会被回收;当系统内存不足时,会被回收。
-
虚引用(Phantom Reference):也称为幽灵引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。
HashMap 多线程并发问题:
HashMap 在多线程并发情况下可能出现以下问题:
-
数据覆盖:多个线程同时执行
put
操作,可能导致后一个线程的值覆盖前一个线程的值。 -
死循环:在 JDK 1.7 中,HashMap 的扩容操作可能引起链表成环,导致
get
操作出现死循环。 -
安全问题:在并发环境下,HashMap 的行为是不可预测的,可能导致数据丢失或脏读。
-
间隙锁(Gap Lock):
-
间隙锁是 InnoDB 中的一种锁机制,用于解决幻读问题。它锁定一个范围,但不包括记录本身。间隙锁可以防止其他事务在这个范围内插入新的记录,从而保证了事务的隔离性。
-
行级锁解决的问题:
-
行级锁解决了事务在操作数据时,锁定整个数据表所带来的性能问题。通过锁定需要操作的数据行,减少了锁定的范围,提高了并发性能。
-
可重复读的实现:
-
可重复读是事务隔离级别的一种,InnoDB 通过以下方式实现:
-
多版本并发控制(MVCC):为每行数据生成多个版本,每个事务看到的数据都是快照版本。
-
行级锁:事务操作数据时,对数据行加锁。
-
事务的隔离级别及其保证方式:
-
读未提交(Read Uncommitted):允许读取尚未提交的数据变更,通过共享锁实现。
-
读已提交(Read Committed):只允许读取已经提交的数据变更,通过行级锁实现。
-
可重复读(Repeatable Read):确保在事务内可以多次读取同样的数据结果,通过 MVCC 和行级锁实现。
-
串行化(Serializable):确保事务可以从数据库中检索到的数据,就好像其他事务不存在一样,通过锁定整个范围的数据实现。
间隙锁的底层实现:
间隙锁是 InnoDB 通过在索引记录之间的空间插入特殊锁对象来实现的。
为什么选择短链接项目,项目难点,遇到的问题:
选择短链接项目的原因可能包括:
-
节省字符:在短信、社交媒体等场景中,短链接可以节省字符空间。
-
便于分享:短链接更易于分享和记忆。
-
数据跟踪:通过短链接可以跟踪点击量、用户行为等。
2.科大讯飞
以下是对您提出的“八股”问题的回答:
Java优势:
-
跨平台性:Java 的口号是“一次编写,到处运行”,因为它依赖于 Java 虚拟机(JVM),可以在不同的操作系统上运行。
-
面向对象:Java 是一种纯粹的面向对象编程语言,提供了封装、继承和多态等特性。
-
丰富的API:Java 提供了丰富的标准类库,简化了开发过程。
-
安全性:Java 设计了安全机制,如类加载器、字节码校验器等,以防止恶意代码。
-
多线程支持:Java 内建了对多线程的支持,简化了并发编程。
-
内存管理:Java 有自动垃圾回收机制,减少了内存泄漏的风险。
-
Java常用集合:
-
List:ArrayList、LinkedList、Vector、Stack
-
Set:HashSet、LinkedHashSet、TreeSet
-
Map:HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
ArrayList和LinkedList区别和底层:
-
区别:
-
ArrayList 基于动态数组实现,LinkedList 基于双向链表实现。
-
ArrayList 随机访问效率高,LinkedList 插入和删除效率高。
-
-
底层:
Set底层:
-
ArrayList 使用一个 Object 数组来存储元素。
-
LinkedList 使用节点(Node)存储元素,每个节点包含数据和指向前一个和后一个节点的引用。
-
-
HashSet:基于 HashMap 实现,使用对象的 hashCode() 来存储值,确保唯一性。
-
TreeSet:基于红黑树实现,可以确保元素处于排序状态。
红黑树讲讲:
红黑树是一种自平衡的二叉查找树,它通过以下规则保持平衡:
-
每个节点非红即黑。
-
根节点是黑色。
-
所有叶子节点(NIL节点,树尾端的虚拟节点)都是黑色。
-
每个红色节点的两个子节点都是黑色(从每个叶子到根的所有路径上不会有两个连续的红色节点)。
-
从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
-
HashMap和TreeMap区别,底层实现:
-
区别:
-
HashMap 不保证顺序,TreeMap 根据 key 自然排序或者指定的 Comparator 进行排序。
-
-
底层实现:
ConcurrentHashMap底层实现:
-
HashMap 基于哈希表实现。
-
TreeMap 基于红黑树实现。
-
-
JDK 1.7:使用分段锁(Segment),每个 Segment 继承自 ReentrantLock,包含若干个桶(HashEntry)。
-
JDK 1.8:使用 Synchronized 和 CAS 操作,以及对部分桶进行加锁,提高了并发访问的性能。
JVM内存区域:
-
程序计数器
-
虚拟机栈
-
本地方法栈
-
堆
-
方法区(JDK 1.8 之后替换为元空间)
对象创建过程:
-
类加载检查:检查类是否已经被加载。
-
分配内存:为对象分配内存空间。
-
初始化零值:将分配的内存空间初始化为零值。
-
设置对象头:设置对象所属类信息、HashCode、GC分代年龄等。
-
执行初始化方法:执行对象的构造方法。
-
垃圾回收过程:
-
标记:标记出所有需要回收的对象。
-
清除:清除被标记的对象。
-
整理:整理内存碎片。
-
Full GC和Young GC区别:
-
Young GC:发生在新生代,速度快,回收频率高。
-
Full GC:发生在老年代,速度慢,回收频率低,会回收新生代和老年代。
Full GC详细流程:
-
标记-清除(Mark-Sweep)或标记-整理(Mark-Compact)。
-
标记整个堆中的存活对象。
-
清除或整理非存活对象占用的空间。
-
静态代码块存在哪里:
-
静态代码块存储在方法区中。
-
Final修饰的变量存在哪里:
-
Final 修饰的变量存储在堆(对象实例)、栈(基本类型或局部变量)或常量池(常量)中。
-
CAP理论:
-
CAP 理论指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)三者不可同时得到保证。
-
为什么要首先保证P(Partition tolerance):
-
在分布式系统中,网络分区是必然发生的,因此分区容错性(P)是必须首先保证的。然后,系统设计者需要在一致性和可用性之间做出权衡,选择CP(一致性+分区容错性)或AP(可用性+分区容错性)。
3.拼多多
== 和 equals方法区别,JAVA对象是值传递还是引用传递:
-
== 操作符:
-
对于基本数据类型,比较的是两个变量的值是否相等。
-
对于引用数据类型,比较的是两个引用是否指向同一个对象地址。
-
-
equals 方法:
Java 对象传递是引用传递,但传递的是引用的副本,即传递的是引用的值。因此,从效果上看,可以认为是值传递,但传递的是对象的引用。
-
是 Object 类中的一个方法,默认情况下,比较的是两个对象的地址是否相同,但通常会被重写来比较对象的内容是否相等。
-
-
String用==的情况,为什么java这么设计:
-
String 使用 == 的情况:当需要比较两个字符串对象的引用是否指向同一个对象时,可以使用 ==。
-
字符串常量池设计原因:
4.1 MYSQL索引怎么存储的:
MySQL 索引通常使用 B-Tree 数据结构进行存储,对于全文本索引则使用倒排索引。
4.2 select * from t where a = x and b = x ; a和b都建了索引,mysql会怎么查:
MySQL 优化器会根据索引的选择性、统计信息等因素来决定使用哪个索引。如果两个索引的选择性相同,MySQL 可能会使用其中一个索引,或者可能会使用索引合并(Index Merge)来同时使用两个索引。
-
节省内存:字符串常量池避免了相同字符串的重复创建,节省了内存空间。
-
提高效率:字符串是不可变对象,使用常量池可以快速比较字符串内容是否相等,不需要重新创建对象。
-
-
Spring在开发中有什么常用的特性,为什么这么用:
-
依赖注入(DI):简化了组件之间的依赖关系,使得代码更加模块化,易于测试和重用。
-
面向切面编程(AOP):允许开发者定义跨多个点的行为,如日志、事务、安全等,分离横切关注点。
-
声明式事务管理:通过注解或配置简化事务管理,使得业务代码不受事务管理代码的侵入。
-
自动配置:Spring Boot 提供自动配置,减少了手动配置的工作量,提高了开发效率。
-
Spring Beans:提供了统一的bean管理方式,包括生命周期管理、作用域管理等。
-
介绍一下GC,把gc的流程基本讲了一遍,问了有没有调参的经验(无):
-
垃圾回收(GC)是自动内存管理的一部分,它旨在回收不再使用的内存空间。流程包括标记、清除、整理等。
-
GC的根节点有哪些:说白了就是 全局的 局部的 本地方法里面声明的
-
虚拟机栈中引用的对象。
-
方法区中类静态属性引用的对象。
-
方法区中常量引用的对象。
-
本地方法栈中 JNI(即一般说的 Native 方法)引用的对象。
-
反转二叉树:
public TreeNode invertTree(TreeNode root) { if (root == null) return null; TreeNode temp = root.left; root.left = invertTree(root.right); root.right = invertTree(temp); return root; }
-
二进制加分 "101"+"10" = "111" 这种:
public String addBinary(String a, String b) { StringBuilder result = new StringBuilder(); int i = a.length() - 1, j = b.length() - 1, carry = 0; while (i >= 0 || j >= 0 || carry != 0) { int sum = carry; if (i >= 0) sum += a.charAt(i--) - '0'; if (j >= 0) sum += b.charAt(j--) - '0'; result.append(sum % 2); carry = sum / 2; } return result.reverse().toString(); }
4.百度健康
以下是对您面试问题的回答:
-
Java有哪些基本数据类型:
-
Java有8种基本数据类型:byte, short, int, long, float, double, char(16bit), boolean(内存1bit 数组32bit)。
-
接口和抽象类的区别:
-
接口只能包含抽象方法和默认方法(Java 8+),属性默认是public static final的;抽象类可以包含具体实现的方法和属性。
-
一个类可以实现多个接口,但只能继承一个抽象类。
-
接口主要用于定义公共的方法规范;抽象类可以包含具体实现,提供更灵活的抽象。
-
-
介绍JVM内存模型:
-
JVM内存模型主要包括程序计数器、虚拟机栈、本地方法栈、堆和方法区(或元空间)。
-
介绍虚拟机栈:
-
虚拟机栈是线程私有的,每个方法执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
-
栈溢出和堆溢出抛出什么类型的异常:
-
栈溢出抛出
StackOverflowError
或OutOfMemoryError
。 -
堆溢出抛出
OutOfMemoryError
。
-
-
介绍堆和栈的内存释放机制(垃圾回收)。启动堆的时候可以指定堆大小吗?有什么参数指定的?
-
栈的内存释放是自动的,方法执行完毕,对应的栈帧就会出栈,内存随之释放。
-
堆的内存释放是通过垃圾回收器来进行的。
-
可以通过JVM参数
-Xms
和-Xmx
来指定堆的初始大小和最大大小。
-
-
介绍类加载的过程:
-
包括加载、链接(验证、准备、解析)、初始化、使用、卸载。
-
介绍Spring的ioc和aop,项目中怎么使用aop的:
-
IOC(控制反转)是通过依赖注入实现对象之间的解耦。
-
AOP(面向切面编程)用于在不修改源代码的情况下,添加额外的功能。
-
项目中可以通过定义切面(Aspect)和通知(Advice)来使用AOP,例如日志记录、事务管理。
-
-
Bean的作用域有哪些,项目中常用什么作用域:
-
作用域:singleton(单例)、prototype(原型)、request、session、application、websocket。
-
常用:singleton。
-
-
依赖注入的方式:
-
构造器注入、setter注入、字段注入。
-
MySQL中char和varchar的区别:
-
char是固定长度的,varchar是可变长度的。
-
char在存储时会用空格填充至固定长度,varchar则不会。
-
-
MySQL有哪些索引类型:
-
B-Tree索引、哈希索引、全文索引、R-Tree索引。
-
-
常见的join类型。使用左连接的方式连接A/B两张表,若B中某数据行缺失,但在A中改行存在,最终结果能查出来这一行数据吗?
-
常见的join类型:内连接(INNER JOIN)、左连接(LEFT JOIN)、右连接(RIGHT JOIN)、全连接(FULL JOIN)。
-
能,左连接会返回左表(A表)的所有行,即使在右表(B表)中没有匹配的行。
-
-
聚簇索引和非聚簇索引有什么区别:
-
聚簇索引的叶节点包含了完整的数据行;非聚簇索引的叶节点包含指向数据行的指针。
-
-
什么情况下应该使用索引,什么情况不该使用索引。某字段只有十种数据值,应当对其使用索引吗?
-
应该使用索引的情况:数据量大、经常查询的字段。
-
不应该使用索引的情况:数据量小、更新频繁的字段。
-
如果字段只有十种数据值,但数据量大且查询频繁,可以考虑使用索引。
-
-
redis为什么快:
-
基于内存、单线程模型、优化的数据结构。
-
-
redis怎么在项目中使用的:
-
用于缓存、会话管理、消息队列等。
-
-
redis持久化怎么实现的:
-
RDB(快照持久化)、AOF(追加文件持久化)。
-
-
redis常见的数据结构。其中list是双向链表还是单向:
-
常见数据结构:字符串(strings)、列表(lists)、集合(sets)、有序集合(sorted sets)、哈希(hashes)。
-
List是双向链表。
-
-
HTTP状态码的401和403表示什么意思:
-
401:未授权,请求需要用户验证。
-
403:禁止,服务器理解请求但拒绝执行。
-
-
如何设计token?token如何鉴权?介绍项目中使用到的SA-Token框架:
-
Token设计应该包含唯一标识、发行者、过期时间等信息,通常使用JWT(JSON Web Tokens)格式。 - 鉴权通常通过在请求头中携带Token,服务器端验证Token的有效性。 - SA-Token是一个轻量级Java权限认证框架,提供了诸如登录认证、权限验证、Session会话、单点登录、OAuth2.0、微服务网关鉴权等功能。
-
5.科大讯飞一面
-
Redis数据结构和缓存实现:
-
数据结构:
-
字符串(Strings):Redis中最基本的数据结构,用于存储简单的字符串、整数或者浮点数。
-
列表(Lists):实现了双向链表,可以用来存储一系列字符串。
-
集合(Sets):无序集合,元素唯一,可以用来存储不重复的字符串。
-
有序集合(Sorted Sets):类似于集合,但每个元素都会关联一个分数(score),可以根据分数排序。
-
哈希(Hashes):键值对集合,适合存储对象。
-
位图(Bitmaps):以位为单位进行存储,适用于布尔值存储。
-
超日志(HyperLogLogs):用于估计集合的基数,占用空间非常小。
-
地理空间(Geospatial):存储地理位置信息,可以进行半径查询等操作。
-
-
缓存实现:
-
缓存通常用于存储热点数据,减少数据库的访问压力。
-
实现方式通常是将数据读取和写入操作先作用于缓存,然后同步或异步更新到数据库。
-
-
缓存三兄弟问题及解决方案:
-
缓存击穿:大量请求访问一个不存在或过期的缓存键。解决方案:使用锁或设置热点数据永不过期。
-
缓存雪崩:大量缓存同时过期。解决方案:设置不同的过期时间,使用缓存预热。
-
缓存穿透:查询不存在的数据。解决方案:使用布隆过滤器,返回空值并设置过期时间。
-
-
-
Redis分布式锁实现:
-
使用Redis的
SETNX
命令尝试设置一个key,如果设置成功,则表示获取到了锁。 -
设置锁的过期时间,防止客户端崩溃后锁无法释放。
-
释放锁时,使用Lua脚本确保只有锁的持有者才能释放锁,避免误释放。
-
可以使用Redis的Redlock算法来提高分布式锁的可靠性。
-
-
HashMap底层原理和扩容原理:
-
底层原理:
-
HashMap基于数组和链表(或红黑树)实现。
-
通过hash函数计算key的hashCode,然后映射到数组的某个位置。
-
如果多个key的hashCode相同,这些key会形成一个链表,称为hash冲突。
-
当链表长度超过一定阈值时,链表会转换成红黑树,以提高查询效率。
-
-
扩容原理:
-
当HashMap中的元素数量达到容量和负载因子(load factor)的乘积时,会进行扩容操作。
-
扩容操作会创建一个新的更大的数组,并将旧数组中的所有元素重新哈希到新数组中。
-
-
-
JVM垃圾回收算法和回收过程:
-
垃圾回收算法:
-
标记-清除(Mark-Sweep):标记出所有活动对象,然后清除未被标记的对象。
-
标记-整理(Mark-Compact):标记活动对象后,将所有活动对象移动到内存的一端,清理掉边界以外的内存。
-
复制(Copying):将内存分为两个半区,每次只使用一个半区,在垃圾回收时,将活动对象复制到另一个半区。
-
-
回收过程:
-
标记阶段:遍历所有可达对象,标记它们为活动状态。
-
清除或整理阶段:清除未被标记的对象,或者在标记后移动活动对象。
-
-
-
JVM内存机制和对象垃圾判断:
-
内存机制:
-
程序计数器:记录当前线程所执行的字节码行号。
-
虚拟机栈:线程私有的,存储局部变量表、操作数栈等。
-
本地方法栈:为虚拟机使用到的Native方法服务。
-
堆:所有线程共享的内存区域,用于存放对象实例。
-
方法区(或元空间):存储已被加载的类信息、常量、静态变量等。
-
-
对象垃圾判断:
-
使用可达性分析算法,从GC Roots(如虚拟机栈中的引用、静态变量、常量池中的引用等)开始,如果一个对象无法通过任何引用链到达,则被认为是垃圾。
-
-
-
支付幂等性:
6.新国都一面
-
幂等性指的是多次执行同一操作,结果一致,不会因为多次执行而产生副作用。
-
在支付系统中,可以通过以下方式实现幂等性:
-
唯一事务号:为每次支付请求生成一个唯一的事务号,处理时检查事务号是否已存在。
-
状态机:维护支付请求的状态,确保每个状态只被处理一次
-
幂等接口设计:支付接口设计时保证多次请求不会导致重复支付。
-
消息队列:将支付请求放入消息队列,消费端保证消息的幂等性。
-
幂等数据库操作:使用数据库的事务机制或乐观锁保证数据库操作的幂等性。
-
-
自我介绍:
-
请简要介绍您的个人背景、工作经历、技术专长以及为什么对这个职位感兴趣。
-
-
Java有哪些集合:
-
List:ArrayList、LinkedList、Vector、Stack
-
Set:HashSet、LinkedHashSet、TreeSet
-
Map:HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
-
Queue:PriorityQueue、ArrayDeque、LinkedList
-
Collection:包含所有集合类型
-
Deque:双端队列,如ArrayDeque、LinkedList
-
SortedSet:有序集合,如TreeSet
-
SortedMap:有序映射表,如TreeMap
-
-
ArrayList和LinkedList区别,数组为什么能随机访问,ArrayList怎么扩容的,原数组怎么办,GC流程是怎么样的:
-
区别:ArrayList基于动态数组实现,LinkedList基于双向链表实现。ArrayList随机访问效率高,LinkedList插入和删除效率高。
-
数组为什么能随机访问:因为ArrayList底层使用数组存储元素,数组支持随机访问。
-
ArrayList扩容:当ArrayList的容量达到当前容量和负载因子的乘积时,会进行扩容。扩容后,新数组的大小是旧数组的1.5倍。原数组不会立即被GC,因为旧数组中的元素可能还被引用。
-
GC流程:
-
标记阶段:标记出所有可达对象。
-
清除阶段:清除未被标记的对象。
-
整理阶段:移动活动对象以减少内存碎片。
-
-
-
SpringBoot自动配置:
-
SpringBoot通过
@EnableAutoConfiguration
注解启用自动配置。 -
它会读取
classpath
下的META-INF/spring.factories
文件,加载所有可用的配置类。 -
这些配置类会被SpringBoot的自动配置机制识别,并根据条件进行相应的配置。
-
-
Spring IoC:
-
IoC(Inversion of Control,控制反转)是Spring的核心理念之一。
-
通过依赖注入(DI),实现了将对象的创建和绑定交给Spring容器管理。
-
容器在运行时动态地将依赖关系注入到对象中,而不是由程序代码直接创建和绑定依赖关系。
-
-
Spring Bean生命周期,循环依赖:
-
生命周期:
-
实例化(Instantiation):当容器第一次使用Bean时,会创建一个新的Bean实例。
-
属性设置(Population):如果Bean需要依赖其他Bean,Spring容器会在实例化后,根据依赖注入规则,将这些依赖注入到Bean中。
-
初始化(Initialization):如果Bean实现了
InitializingBean
接口或使用@PostConstruct
注解,会在属性设置后执行初始化方法。 -
使用(Usage):Bean可以被Spring容器或应用程序使用。
-
销毁(Destruction):如果Bean实现了
DisposableBean
接口或使用@PreDestroy
注解,会在使用后被销毁。
-
-
循环依赖:如果两个或多个Bean之间存在循环依赖,Spring容器会通过三级缓存(singletonFactories、earlySingletonObjects、singletonObjects)来解决循环依赖问题。
-
-
SpringMVC工作流程:
-
接收请求:客户端发送HTTP请求到SpringMVC前端控制器(DispatcherServlet)。
-
解析请求:DispatcherServlet解析请求,提取请求路径和参数。
-
查找HandlerMapping:根据请求路径查找对应的HandlerMapping。
-
执行Handler:根据HandlerMapping找到对应的Handler(通常是Controller中的方法)。
-
处理返回值:Handler处理请求,返回ModelAndView。
-
渲染视图:DispatcherServlet将ModelAndView发送给视图解析器(ViewResolver)。
-
响应客户端:视图解析器找到对应的视图,渲染视图并返回给客户端。
-
-
JWT(JSON Web Tokens)如何工作:
-
三部分组成:
-
头部(Header):包含类型(Type)、算法(Algorithm)等信息。
-
载荷(Payload):包含用户信息、创建时间、过期时间等。
-
签名(Signature):使用HMAC算法
-
对称加密:使用相同的密钥进行加密和解密
-
密钥泄露怎么办:
-
如果密钥泄露,需要立即更换密钥,并通知所有使用该密钥的服务和客户端
-
对所有已签名的Token进行重新签名,以保证安全。
-
确保新密钥的安全存储,防止再次泄露
-
监控Token的使用情况,及时发现异常。
-
-
-
7.腾讯全栈一面
-
算法题:
高精度加法通常用于处理超出普通数据类型(如int, long)能表示范围的整数加法。可以使用vector
或string
来表示大整数,并实现加法。vector<int> add(vector<int>& a, vector<int>& b) { vector<int> result; int carry = 0; for (size_t i = 0; i < a.size() || i < b.size() || carry; ++i) { int sum = carry; if (i < a.size()) sum += a[i]; if (i < b.size()) sum += b[i]; result.push_back(sum % 10); // 取模得到当前位的值 carry = sum / 10; // 进位 } return result; }
注意:这里的
使用栈来匹配括号是一种常见的方法。对于每个左括号,将其推入栈中;对于每个右括号,检查栈顶元素是否是相应的左括号,如果是则弹出栈顶元素,否则括号不匹配。vector<int>
是按逆序存储数字的,即vector[0]
是数字的个位。public boolean isValid(String s) { Stack<Character> stack = new Stack<>(); for (char c : s.toCharArray()) { if (c == '(' || c == '{' || c == '[') { stack.push(c); } else { if (stack.isEmpty()) return false; char top = stack.pop(); if ((c == ')' && top != '(') || (c == '}' && top != '{') || (c == ']' && top != '[')) { return false; } } } return stack.isEmpty(); }
-
高精度加法:
-
括号匹配:
-
-
Java内容:
数组是连续的内存空间,因此它支持通过索引快速访问元素,但插入和删除操作需要移动大量元素。链表由节点组成,每个节点包含数据和指向下一个节点的指针,因此链表插入和删除操作效率较高,但不支持快速随机访问。
Java中的List接口有多种实现,包括
ArrayList
、LinkedList
、Vector
和Stack
。ArrayList
基于动态数组实现,LinkedList
基于双向链表实现,Vector
与ArrayList
类似但线程安全,Stack
是Vector
的一个子类,用于实现栈数据结构。Java集合框架包括
Set
、List
、Queue
和Map
等接口及其实现类。常见的实现类包括HashSet
、TreeSet
、ArrayList
、LinkedList
、PriorityQueue
、HashMap
、TreeMap
等。可以使用
TreeSet
,它基于红黑树实现,能够保证元素的有序性和唯一性。ConcurrentHashMap
是线程安全的Map实现。它通过分段锁(Segmentation)来降低锁的粒度,提高并发性能。每个Segment相当于一个小的HashMap,内部维护着一个独立的锁。扩容时,会创建一个新的数组,并将旧数组中的元素重新映射到新数组中。-
2.1 数组和链表的区别:
-
2.2 List的实现:
-
2.3 常见的集合类:
-
2.4 存储有序且不重复的数据:
-
2.5 线程安全的Map及实现原理、扩容机制:
-
2.6 Java的锁:
-
synchronized
:内置锁,可重入,通过monitor对象实现。 -
乐观锁:通常通过版本号实现,假设没有冲突,在更新数据前检查版本号是否变化。
-
悲观锁:如
ReentrantLock
,它是一个显式锁,提供比synchronized
更丰富的功能,如可中断的锁获取、尝试非阻塞地获取锁等。
-
-
-
Redis内容:
-
3.1 Redis的数据类型:
-
String:简单的键值对。
-
List:按照插入顺序排序的字符串列表。
-
Set:无序集合,元素唯一。
-
ZSet(Sorted Set):有序集合,每个元素都有一个分数,根据分数排序。
-
Hash:键值对的集合,适合表示对象。
-
-
3.2 分布式锁实现的原理和方案,程序崩了怎么办:
-
原理:使用Redis的
SETNX
命令(现在推荐使用SET
命令带NX
和PX
选项)来设置一个键,如果键不存在则设置成功,返回1,否则设置失败返回0。设置成功后,该键就代表获取了锁。为了防止程序崩溃导致锁无法释放,通常会为锁设置一个过期时间(使用PX
参数),这样即使程序崩溃,锁也会在过期后自动释放。
-
-
-
3.3 Zset设计用户行为限流:
可以使用Redis的ZSet来设计一个简单的限流系统。为每个用户维护一个ZSet,以时间戳作为分数,将用户的行为作为成员。当用户进行操作时,可以检查ZSet中最早的行为是否在时间窗口之外,如果是,则移除,然后添加新的行为。通过这种方式,可以限制用户在特定时间窗口内的行为次数。
-
3.4 命令查看Redis信息:
使用INFO
命令可以查看Redis服务器的各种信息和统计数据,如内存使用情况、客户端连接数、持久化状态等。
-
MySQL内容:
-
SQL题目:由于没有具体的题目,无法提供具体的SQL语句。但一般来说,
UPDATE
语句用于修改表中的数据,GROUP BY
语句用于对结果集进行分组。 -
索引有哪些、什么时候加索引、怎么加索引:
-
索引类型:主键索引、唯一索引、普通索引、全文索引、复合索引等。
-
何时加索引:在经常需要搜索、排序、分组的列上添加索引,可以提高查询效率。
-
如何加索引:使用
CREATE INDEX
语句来创建索引,例如:CREATE INDEX idx_column1 ON table_name (column1);
-
-
如何提高查询效率:
-
优化SQL语句,避免使用子查询和复杂的连接。
-
使用合适的索引。
-
减少数据检索量,如使用
LIMIT
限制返回结果的数量。 -
分析查询计划,使用
EXPLAIN
语句查看查询的执行计划。
-
-
MySQL的锁:
-
表锁:锁定整张表,适用于MyISAM存储引擎。
-
行锁:只锁定需要的行,适用于InnoDB存储引擎。
-
-
-
简历上的和其它:
创建一个Spring Boot Starter通常需要以下步骤:-
创建一个包含自动配置类的项目。
-
在
src/main/resources/META-INF/spring.factories
文件中指定自动配置类。 -
编写自动配置逻辑,使用
@Conditional
注解来根据条件自动配置Bean。
-
使用RocketMQ的幂等性插件。
-
在业务逻辑中实现幂等性,例如通过数据库的唯一约束或者使用Redis等缓存来记录已处理的消息ID。
-
标记(Marking):标记出所有活动的对象。
-
清除(Sweeping):清除未被标记的对象,释放内存。
-
整理(Compacting):移动所有活动的对象,以减少内存碎片。
-
分配(Allocation):为新对象分配内存。
-
核心线程数(Core Pool Size):线程池中始终存活的线程数。
-
最大线程数(Maximum Pool Size):线程池中允许的最大线程数。
-
线程空闲时间(Keep Alive Time):非核心线程空闲时的存活时间。
-
工作队列(Work Queue):用于存放等待执行的任务队列。
使用线程池时,可以通过
Executors
工厂类来创建不同类型的线程池,或者直接使用ThreadPoolExecutor
类进行更细致的配置。-
通信协议、TCP的好处:
-
可靠传输:通过序列号、确认应答、重传机制等确保数据的可靠传输。
-
流量控制:通过滑动窗口算法来控制发送方的发送速率,避免网络拥塞。
-
拥塞控制:通过慢启动、拥塞避免、快速重传和快速恢复等算法来避免网络拥塞。
-
-
如何自定义Starter:
-
RocketMQ如何避免重复消费:
-
GC垃圾回收的流程、原理:
-
线程池的参数有哪些,怎么用:
-
AOP是什么,有什么用,怎么用,口述一个记录日志的使用过程,如何设计一个AOP,设计模式:
-
AOP(Aspect-Oriented Programming):面向切面编程,是一种编程范式,用于将横切关注点(如日志、事务、安全)与业务逻辑分离。
-
用途:用于在不修改业务逻辑代码的情况下,增加额外的功能。
-
使用:在Spring框架中,可以通过定义切面(Aspect)、通知(Advice)、切点(Pointcut)来使用AOP。
-
日志记录使用
-
-
过程示例:
// 定义一个切面 @Aspect @Component public class LoggingAspect { // 定义切点 @Pointcut("execution(* com.example.service.*.*(..))") public void serviceLayerMethods() {} // 定义通知 @Before("serviceLayerMethods()") public void logMethodEntry(JoinPoint joinPoint) { // 获取方法签名 String methodName = joinPoint.getSignature().getName(); // 获取方法参数 Object[] args = joinPoint.getArgs(); // 记录日志 System.out.println("Entering method: " + methodName + " with arguments " + Arrays.toString(args)); } @AfterReturning(pointcut = "serviceLayerMethods()", returning = "result") public void logMethodExit(JoinPoint joinPoint, Object result) { // 获取方法签名 String methodName = joinPoint.getSignature().getName(); // 记录日志 System.out.println("Exiting method: " + methodName + " with result " + result); } }
在这个例子中,我们定义了一个切面LoggingAspect
,它包含一个切点serviceLayerMethods
,这个切点匹配com.example.service
包下的所有方法。我们定义了两个通知:logMethodEntry
在方法执行前记录日志,logMethodExit
在方法执行后记录日志。
如何设计一个AOP:
-
确定切面:识别出需要在哪些地方添加横切关注点。
-
定义切点:使用AspectJ表达式语言定义切点,以确定哪些方法将被拦截。
-
实现通知:根据需要实现@Before、@After、@AfterReturning、@AfterThrowing、@Around等通知。
-
绑定通知和切点:将通知与切点关联起来,确保在正确的时机执行通知逻辑。
-
设计模式:
-
AOP本身就是一种设计模式,它通常与以下设计模式结合使用:
-
代理模式(Proxy Pattern):通过代理对象来控制对原始对象的访问。
-
责任链模式(Chain of Responsibility Pattern):通过一系列处理者来处理请求,每个处理者都有机会处理请求。
-
策略模式(Strategy Pattern):定义了一系列算法,并将每个算法封装起来,使它们可以互相替换。
在实现AOP时,Spring框架使用了代理模式,通过代理对象来拦截方法调用,并执行通知逻辑。
以上是对AOP相关内容的详细解释。在实际应用中,AOP提供了一种非常灵活的方式来增加和维护横切关注点,从而使得业务逻辑代码更加简洁和聚焦。
8.moka一面
-
线程池的使用与原理:
-
实习业务中使用线程池:在实习中,我使用线程池来处理大量的异步任务,如数据处理、文件上传下载等,以提高系统的响应速度和吞吐量。
-
如何复用线程池:通过配置核心线程数和最大线程数,线程池可以在不销毁核心线程的情况下复用线程,从而减少线程创建和销毁的开销。
-
确保线程正常执行任务:通过实现
Runnable
或Callable
接口,并将任务提交给线程池执行。 -
线程池底层原理:线程池通过维护一个线程集合和一个任务队列(通常是阻塞队列)来工作。当任务提交时,线程池会根据当前线程数和任务队列的情况来决定是创建新线程还是复用已有线程。
-
为什么使用阻塞队列:阻塞队列可以有效地将任务的生产者和消费者分离,同时支持线程间的协作和资源的合理利用。
-
单线程优先级调节:在Java中,可以通过
Thread.setPriority()
方法调节线程的优先级,但实际效果依赖于操作系统的调度策略。 -
阻塞队列的作用:提供线程安全的任务队列,并支持阻塞操作,确保生产者和消费者之间的同步。
-
-
线程池相关问题:
-
任务超时解决:可以设置任务执行的超时时间,使用
Future
对象来取消或检查任务的状态。 -
CountDownLatch
操作:CountDownLatch
可以在任何地方调用countDown()
方法来减少计数,通常在线程完成任务后调用。它是局部的,可以在局部变量中使用。 -
红锁算法:Redis分布式锁的红锁算法通过多个Redis实例上的锁来实现分布式环境下的锁,确保在多个节点上锁的原子性。
-
-
Redis分布式锁与看门狗机制:
-
SETNX
原理:SETNX
(Set If Not Exists)是一个原子操作,如果键不存在,则设置键值对并返回1,否则返回0。 -
看门狗机制:用于自动续期锁的过期时间,防止任务未完成时锁过期。
-
-
订单操作一致性:保持订单操作一致性是为了确保数据的一致性和准确性,避免出现数据冲突和错误。
-
复用样式对象方案:可能使用的是享元模式(Flyweight Pattern),通过共享相同或相似的对象来减少内存使用。
-
架构设计问题:为了防止静态变量被修改,可以将其设置为私有并通过公共方法提供访问,或者使用枚举类型来定义常量。
-
MQ(消息队列)相关:
-
基础模型:生产者-消费者模型,生产者发送消息,消费者接收消息。
-
消息可靠性:通过消息确认机制、持久化存储、事务消息等手段保证消息的可靠性。
-
消息刷盘时机:通常在消息被确认消费后或在特定时间间隔后刷盘。
-
死信队列:用于处理无法正常消费的消息。
-
延迟队列:用于延迟处理消息。
-
-
Java数据类型与集合:
-
Java数据类型:基本数据类型(如int, float, double)和引用数据类型(如类、接口、数组)。
-
常见集合:
ArrayList
、LinkedList
、HashMap
、HashSet
等。 -
LinkedList
与ArrayList
效率:ArrayList
在随机访问上效率更高,LinkedList
在插入和删除操作上效率更高。
-
-
锁结构与JUC工具类:
-
锁结构:如
synchronized
、ReentrantLock
、ReadWriteLock
等。 -
JUC工具类:如
Semaphore
、CountDownLatch
、CyclicBarrier
、Exchanger
等。
-
-
线程安全性与锁:
9.TP-link云计算一面
-
线程安全性:当多个线程访问同一资源时,不会出现数据不一致或错误的情况。
-
线程安全问题防范:使用同步机制、锁、原子变量等。
-
synchronized
使用:通常锁住共享资源或方法。 -
锁失效场景:如锁定的对象发生变化。
-
ReentrantLock
底层数据结构与原理:基于AQS(AbstractQueuedSynchronizer)实现,通过一个状态变量来控制锁的获取和释放。 -
锁的深入讨论:
-
公平锁与非公平锁:
ReentrantLock
的公平锁和非公平锁通过构造函数中的参数来区分。公平锁保证等待时间最长的线程先获取锁,非公平锁则允许新来的线程抢占锁。 -
锁竞争优先级:在非公平锁中,新来的线程可能会抢占已经在等待的线程,而在公平锁中,则是先来先服务的原则。
-
其他锁:除了
ReentrantLock
,还有ReentrantReadWriteLock
、StampedLock
等。 -
信号量保证线程安全:
Semaphore
可以用来限制对某个资源的访问数量,从而在一定程度上保证线程安全。
-
-
MySQL的隔离级别与索引:
-
隔离级别:MySQL支持以下隔离级别:
-
READ UNCOMMITTED
-
READ COMMITTED
-
REPEATABLE READ(默认隔离级别)
-
SERIALIZABLE
-
-
索引创建条件:索引通常在以下情况下创建:
-
经常用于查询的列
-
经常用于排序或分组的列
-
经常用于连接的列
-
-
索引创建原则:选择区分度高的列,避免过度索引,考虑索引维护的成本等。
-
联合索引失效:如果查询条件不满足联合索引的最左前缀原则,则可能导致索引失效。
-
索引下推:索引下推(Index Condition Pushdown)是MySQL的一种优化技术,它将部分过滤条件下推到存储引擎层,减少数据访问量。
-
-
线程安全与锁的深入讨论:
-
线程锁住的对象变化:如果锁住的对象发生变化,可能会导致锁失效或出现线程安全问题。通常应该锁住不会变化的对象,或者使用不可变对象。
-
如果不是锁住对象实例:可以考虑锁住一个特定的锁对象,比如
Lock
实例,或者使用类锁(Class
对象锁)。 -
ReentrantLock
的底层数据结构与原理:ReentrantLock
底层依赖于AbstractQueuedSynchronizer
(AQS),它使用一个int类型的变量来表示同步状态,并通过队列来管理等待的线程。
-
-
JUC工具类与线程安全:
-
JUC工具类:Java并发工具类,如
Semaphore
、CountDownLatch
、CyclicBarrier
、Exchanger
等,提供了丰富的并发编程工具。 -
线程安全:当多个线程访问同一个对象时,如果不需要考虑线程间的同步问题,那么这个对象就是线程安全的。
-
-
synchronized
的深入讨论:-
synchronized
锁住的是什么:synchronized
可以锁住代码块或方法,实际上锁住的是对象监视器(monitor),对于同步方法,锁的是当前对象实例;对于静态同步方法,锁的是类的Class
对象。 -
锁住对象实例的变化:如果对象实例在锁住期间发生变化,可能会导致锁的粒度不正确或锁失效。
-
使用
ReentrantLock
:ReentrantLock
提供了比synchronized
更灵活的锁操作,可以显式地获取和释放锁,还可以实现公平锁等。 -
MySQL索引与性能优化:
-
索引失效的情况:除了不满足最左前缀原则外,以下情况也可能导致索引失效:
-
使用函数或计算表达式导致索引列无法直接使用。
-
在WHERE子句中使用不等于(
<>
)或IS NULL可能会导致索引失效。 -
使用LIKE操作符时,如果通配符不在字符串的开头,例如
LIKE '%value'
,可能会导致索引失效。
-
-
索引优化的原则:
-
选择合适的索引类型,如BTREE或HASH。
-
避免过多的索引,因为每个索引都会增加写操作的成本。
-
定期分析查询日志和执行计划,优化慢查询。
-
-
-
线程锁的深入讨论:
-
锁的粒度:选择合适的锁粒度是重要的,过粗的锁可能导致不必要的阻塞,而过细的锁可能导致复杂的代码逻辑和性能开销。
-
锁竞争和线程饥饿:在高并发环境下,锁竞争可能导致某些线程长时间无法获取锁,造成线程饥饿。
-
锁的公平性和非公平性:公平锁虽然可以避免线程饥饿,但可能会降低系统的吞吐量;非公平锁可以提高吞吐量,但可能导致某些线程长时间等待。
-
-
Java集合类的深入讨论:
-
LinkedList
和ArrayList
的选择:-
ArrayList
适合随机访问操作,因为它的时间复杂度为O(1)。 -
LinkedList
适合插入和删除操作,因为它的时间复杂度为O(1)。
-
-
Java中复用链表的数据结构:除了
LinkedList
,还有LinkedHashMap
和LinkedHashSet
等,它们在哈希表的基础上增加了链表结构,以保持元素的插入顺序。
-
-
数据结构与算法的深入讨论:
-
支持二分查找的链表:理论上,链表不支持高效的二分查找,因为链表不支持随机访问。但是,可以通过平衡二叉搜索树(如AVL树或红黑树)来实现类似的功能,这些树结构可以在O(log n)时间内进行查找、插入和删除操作。
-
-
锁结构与JUC工具类的深入讨论:
-
JUC工具类
Semaphore
:Semaphore
可以用来实现资源池,限制同时访问资源的线程数,从而保证线程安全。 -
CountDownLatch
和CyclicBarrier
:这两个类可以用于线程间的协作,CountDownLatch
用于等待多个线程完成任务,而CyclicBarrier
则用于多个线程在某个点上同步。
-
-
线程安全性的深入讨论:
-
线程安全性问题的防范:除了使用锁,还可以通过以下方式来防范线程安全性问题:
-
使用原子变量,如
AtomicInteger
、AtomicReference
等。 -
使用线程安全集合类,如
ConcurrentHashMap
、CopyOnWriteArrayList
等。 -
使用不可变对象,如
String
、Integer
等。
-
-
-
synchronized
的深入讨论:-
synchronized
锁住的对象实例变化:如果对象实例在锁住期间被修改,可能会导致锁的粒度不正确或锁失效。为了避免这种情况,可以锁住一个不可变的对象或使用final
关键字确保对象引用不会改变。
-
-
ReentrantLock
的深入讨论:-
ReentrantLock
的公平锁和非公平锁的实现:公平锁在tryAcquire
方法中会检查队列中是否有等待的线程,而非公平锁则可能会直接尝试获取锁。 -
锁竞争的优先级:在非公平锁中,新来的线程可能会抢占已经在等待的线程,这取决于线程调度和锁的实现。
-
-
-
1: Kafka的版本需要查看具体的部署配置。在Kafka 2.8.0版本之前,Kafka使用Zookeeper来维护集群的元数据。从Kafka 2.8.0开始,引入了KRaft模式(Kafka Raft Metadata mode),这是一种不依赖Zookeeper的元数据管理方式。具体使用哪种模式,需要根据部署的Kafka版本和配置来确定。
2. ISR(In-Sync Replicas)列表是Kafka为了保证数据不丢失的机制。ISR列表包含了与Kafka主题分区leader副本保持同步的所有副本。如果一个副本由于网络问题或者机器故障不能及时与leader副本同步数据,那么它将被踢出ISR列表。ISR列表的作用是确保在发生副本选举时,新的leader拥有所有已确认的消息,从而保证数据的一致性和可靠性。
3: 消费者组的Coordinator是Kafka集群中的broker。每个消费者组都会被分配一个Coordinator,通常是该消费者组第一个成员加入时所在的broker。Coordinator的选举是通过Kafka集群内部机制自动完成的,主要是基于消费者组的ID进行哈希,然后映射到对应的broker上。
4: Kafka的生产者可以通过配置来保证消息不丢失:
-
At Least Once(至少一次):确保消息不会因为网络问题等丢失,但可能会重复发送。
-
At Most Once(最多一次):消息可能会丢失,但不会重复。
Kafka默认是至少一次的保证,通过开启幂等性或者事务功能可以避免消息的重复。
5: Kafka消费者组默认是自动提交offset的,但也可以配置为手动提交。自动提交在某些情况下可能会导致消息的重复消费,而手动提交可以更精确地控制offset的提交时机。
6: Kafka消费者组可能会重复消费消息,特别是在发生再平衡时。防止重复消费可以通过以下机制:
-
使用具有唯一标识的消息。
-
在应用层面实现幂等处理。
-
手动管理offset提交,确保消息处理完成后再提交。
7: Zookeeper通过以下机制来避免脑裂:
-
使用原子广播协议(Zab协议)来保证集群中所有节点的数据一致性。
-
集群中的节点需要获得多数节点的投票才能成为新的领导者。
-
配置合适的超时时间,防止网络分区导致的服务中断。
8.: 集群的部署方式可以是虚拟机、物理机或者Kubernetes(K8s)。CI/CD流程通常用于自动化部署和管理,具体使用哪种部署方式和是否有CI/CD流程取决于公司的实际需求和技术栈。
9: Redis Cluster的槽位是一个分布式的概念,它将所有的键空间分成16384个槽位,每个Redis节点负责一部分槽位。槽位用于在多个节点之间分配和定位键值对。
10: 在Redis中可以为key设置TTL(Time To Live),这样key在指定的时间后会自动被删除。
11: KEYS
命令会一次性返回所有匹配的key,可能导致服务阻塞,不适用于大数据量的场景。而SCAN
命令则是通过游标分批返回匹配的key,不会阻塞服务,适用于大数据量查询。
12: Redis的持久化通常使用RDB(快照)和AOF(追加文件)两种方式。RDB+AOF混合持久化结合了两者的优点,RDB提供了数据恢复的快速性,而AOP确保了数据的持久性。这种混合模式可以在数据恢复速度和数据安全性之间取得平衡。
13: Keepalived是一个高可用解决方案,它底层使用VRRP(Virtual Router Redundancy Protocol)协议来实现。Keepalived通过模拟路由器的功能,在多个节点之间进行健康检查和虚拟IP的漂移,以确保服务的连续性。
14: 集群扩容通常涉及到以下步骤:
-
增加新的节点到集群中。
-
重新分配槽位或者数据分区,确保数据均衡分布在所有节点上。
-
更新集群配置信息,让所有节点识别新的集群拓扑。
15: 如果是自己设计权限模型,通常会包括以下方面:
-
用户认证:确保只有合法用户可以访问系统。
-
权限控制:定义不同的角色和权限,限制用户可以执行的操作。
-
访问控制:基于角色和权限的访问控制列表(ACL)。
-
审计日志:记录用户的操作行为,用于监控和审计。
10.华为od一面
1、继承和多态
继承是面向对象编程中的一个基本概念,它允许我们根据一个已有的类创建一个新的类,新类继承了原有类的属性和方法。多态是指同一个行为具有多个不同表现形式或形态的能力。在Java中,多态可以通过继承和接口实现。继承实现多态的方式是通过子类重写父类的方法,然后通过父类引用指向子类对象,调用重写的方法。
2、方法重写和重载的区别
方法重写(Overriding)是指子类重写继承自父类的方法,要求方法名、参数列表、返回类型(或子类)都相同。方法重载(Overloading)是在同一个类中存在多个方法名相同但参数列表不同的方法。重写是子类和父类之间的关系,而重载是同一个类中方法之间的关系。
3、双亲委派类加载原理
双亲委派模型是Java类加载器的一种机制。当一个类需要被加载时,类加载器首先将请求委托给父类加载器去完成,只有当父类加载器无法完成这个加载请求时,才自己去加载。这种方式防止了类的重复加载,保护了Java核心API不被随意篡改。
4、ArrayList和LinkedList底层原理
ArrayList底层是基于动态数组实现的,具有查询快、增删慢的特点。LinkedList底层是基于双向链表实现的,具有增删快、查询慢的特点。
5、增删改查效率
-
增:LinkedList的增效率高于ArrayList,因为LinkedList在添加元素时只需在链表中插入节点。
-
删:同样,LinkedList的删除效率高于ArrayList,因为不需要移动其他元素。
-
改:两者修改效率相差不大,因为都是通过索引直接访问。
-
查:ArrayList的查询效率高于LinkedList,因为ArrayList可以直接通过索引访问,而LinkedList需要从头节点开始遍历。
6、代码原子性
代码原子性指的是一个操作在执行过程中不会被中断,要么全部执行,要么都不执行。在多线程环境中,保证代码原子性是非常重要的。可以通过synchronized关键字、Lock接口及其实现类、原子类等手段保证代码的原子性。
7、ConcurrentHashMap底层实现
ConcurrentHashMap底层采用了分段锁技术,将数据分成一段段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。在Java 8中,ConcurrentHashMap摒弃了分段锁,而是采用CAS算法+Synchronized保证并发安全。
8、对try/finally/return理解
try块中存放正常执行的代码,finally块中存放必须执行的代码,如资源释放。无论try块中的代码是否抛出异常,finally块都会执行。如果在try或finally块中有return语句,finally块会在return之前执行。需要注意的是,如果finally块中也有return语句,它会覆盖try块中的return。
9、OOM内存泄露有遇到过或者解决过吗
是的,我遇到过OutOfMemoryError(OOM)问题。解决方法包括:
-
使用内存分析工具(如VisualVM、MAT)分析堆转储文件,找出内存泄漏的原因。
-
优化代码,避免创建大量无用对象。
-
增加JVM启动参数,如-Xmx、-Xms等,提高堆内存。
10、使用redis三方件缓存如何保证一致性
为了保证缓存一致性,可以采取以下措施:
-
使用发布/订阅模式,当数据库更新时,发布消息通知缓存更新或失效。
-
设置合理的缓存过期时间,让缓存数据定期失效,从数据库重新加载。
-
在更新数据库的同时,直接更新缓存。
-
使用分布式锁,确保在更新数据库和缓存时,操作的原子性。
11.小米日常实习一面
-
项目中采用了多种设计模式,介绍一下你了解的设计模式。
设计模式是软件工程中的最佳实践,以下是一些常见的设计模式:
-
单例模式(Singleton):确保一个类只有一个实例,并提供一个全局访问点。
-
工厂模式(Factory Method):定义一个用于创建对象的接口,让子类决定实例化哪一个类。
-
抽象工厂模式(Abstract Factory):创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
-
建造者模式(Builder):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
-
原型模式(Prototype):用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
-
适配器模式(Adapter):将一个类的接口转换成客户期望的另一个接口。
-
装饰器模式(Decorator):动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式比生成子类更为灵活。
-
代理模式(Proxy):为其他对象提供一种代理以控制对这个对象的访问。
-
观察者模式(Observer):对象间的一对多依赖关系,当一个对象改变状态,所有依赖于它的对象都会得到通知并自动更新。
-
策略模式(Strategy):定义一系列的算法,把它们一个个封装起来,并且使它们可以互相替换。
-
了解动态代理吗?了解反射吗?java 中有哪些技术应用到了反射技术。
是的,动态代理和反射都是Java的高级特性。动态代理允许在运行时创建一个实现了一组给定接口的新类。反射则是在运行时分析或调用类的属性和方法的能力。Java中用到反射的技术包括:
-
动态代理:如Java的
java.lang.reflect.Proxy
类。 -
框架和库:如Spring框架的依赖注入、Hibernate的ORM映射。
-
调试和分析工具:能够分析类和对象信息的工具。
-
从输入一条 url 地址到显示,经历了什么过程。
这个过程大致包括以下步骤:
-
DNS解析:将URL中的域名解析为IP地址。
-
建立连接:通过TCP三次握手与目标服务器建立连接。
-
发送HTTP请求:浏览器发送一个HTTP请求到服务器。
-
服务器处理请求:服务器接收到请求后处理并返回响应。
-
浏览器解析渲染:浏览器解析HTML文档,构建DOM树,加载CSS样式和JavaScript脚本,渲染页面。
-
关闭连接:通过TCP四次挥手断开与服务器连接。
-
GET请求和POST请求有什么不同?还有什么请求类型。
GET请求和POST请求的不同点包括:
-
用途:GET用于请求数据,POST用于提交数据。
-
数据大小:GET请求通过URL传输数据,数据大小有限制;POST请求将数据放在请求体中,理论上不受限制。
-
安全性:GET请求的数据暴露在URL中,不如POST安全。
-
缓存:GET请求可以被缓存,POST请求不会被缓存。
其他请求类型包括:
-
PUT:更新资源。
-
DELETE:删除资源。
-
HEAD:类似于GET请求,但只返回响应头,不返回响应体。
-
OPTIONS:用于描述目标资源的通信选项。
-
PATCH:用于对资源进行部分更新。
-
类加载机制。
Java类加载机制包括以下几个步骤:
-
加载:通过类加载器读取类的字节码文件,生成一个
Class
对象。 -
验证:确保被加载的类的正确性。
-
准备:为类变量分配内存,并设置默认初始值。
-
解析:将符号引用替换为直接引用。
-
初始化:执行类的初始化代码,包括静态代码块和对静态变量的赋值。
-
怎么避免加载重复的类(双亲委派机制)。
Java通过双亲委派机制避免加载重复的类。当一个类需要被加载时,类加载器首先将请求委托给父类加载器去完成,只有当父类加载器无法完成这个加载请求时,才自己去加载。这样可以确保每个类只被加载一次。
-
介绍一下 Java 堆栈。除了堆栈 JVM 内存里面还有哪些区域。
Java堆栈是线程私有的内存区域,用于存储局部变量、方法调用的上下文等。JVM内存还包括以下区域:
-
堆(Heap):所有线程共享的内存区域,用于存储Java对象实例。
-
方法区(Method Area):所有线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量等。
-
程序计数器(Program Counter Register):线程私有的内存区域,存储当前线程执行的字节码指令地址。
-
本地方法栈(Native Method Stack):为虚拟机使用到的Native方法服务。
-
遇到过死锁吗,形成的死锁得条件
条件有哪些。
死锁是指两个或多个线程永久阻塞,每个线程等待其他线程释放资源的情况。形成死锁的四个必要条件如下:
-
互斥条件:资源不能被多个线程共同使用,只能由一个线程独占。
-
占有和等待条件:线程至少持有一个资源,并且正在等待获取其他线程持有的资源。
-
不可抢占条件:已经分配给一个线程的资源在该线程完成任务前不能被强制抢占。
-
循环等待条件:存在一种线程资源的循环等待链,每个线程都在等待下一个线程所持有的资源。
-
Java 加锁有哪些方式? synchronized 关键字使用场景。 Java中加锁的方式主要包括:
-
内置锁(synchronized):通过
synchronized
关键字实现,可以修饰方法或代码块。 -
重入锁(ReentrantLock):实现了
Lock
接口,提供了比内置锁更丰富的功能,如可中断的锁获取、尝试非阻塞地获取锁等。
synchronized
关键字的使用场景包括:
-
同步方法:当一个方法被声明为
synchronized
时,同一时间只有一个线程能够执行该方法。 -
同步代码块:当一个代码块被
synchronized
关键字包围时,同一时间只有一个线程能够执行该代码块。
-
了解对象头吗? 是的,对象头是Java对象结构的一部分,存在于每个Java对象中。对象头包含以下信息:
-
Mark Word:存储对象的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID等。
-
类型指针:指向对象的类元数据的指针,确定这个对象所属的类型。
-
数组长度(如果对象是数组):记录数组的长度。
-
介绍一下 ConcurrentHashMap 底层原理。ConcurrentHashMap 怎么保证线程安全的(加锁方式) ConcurrentHashMap的底层原理如下:
-
分段锁:ConcurrentHashMap内部使用Segment数组结构和HashEntry数组结构,Segment继承自ReentrantLock,从而实现了分段锁。
-
CAS操作:在Java 8中,ConcurrentHashMap摒弃了分段锁,而是使用CAS操作和synchronized关键字来保证线程安全。
-
Node数组:存储键值对,当发生哈希冲突时,形成链表。
-
红黑树:当链表长度超过一定阈值时,链表会转换为红黑树,以提高搜索效率。
ConcurrentHashMap保证线程安全的方式:
-
在Java 7中,通过分段锁,每个Segment独立加锁,减少锁竞争。
-
在Java 8及以后版本,通过synchronized关键字加锁,但只对链表或红黑树的头节点进行加锁,这样锁的粒度更细,减少了锁的竞争。
-
了解线程池吗? 是的,线程池是一种管理和复用线程的机制,可以减少创建和销毁线程的开销,提高系统性能。Java中的线程池是通过
java.util.concurrent
包中的ExecutorService
接口和其实现类(如ThreadPoolExecutor
)来实现的。线程池的主要参数包括:
-
核心线程数:线程池中始终保持存活的线程数。
-
最大线程数:线程池中允许的最大线程数。
-
存活时间:当线程数大于核心线程数时,多余的空闲线程在终止前等待新任务的最长时间。
-
工作队列:用于存放待执行任务的队列。
-
介绍一下 MySQL 数据库中的连接(JOIN) MySQL中的连接(JOIN)用于根据两个或多个表中的相关列之间的关系,从这些表中查询数据。常见的连接类型包括:
-
INNER JOIN:返回两个表中都有匹配的行。
-
LEFT JOIN(或LEFT OUTER JOIN):返回左表的所有行,即使右表中没有匹配的行。
-
RIGHT JOIN(或RIGHT OUTER JOIN):返回右表的所有行,即使左表中没有匹配的行。
-
FULL JOIN(或FULL OUTER JOIN):返回左表和右表中的所有行,当某行在另一表中没有匹配时,会以NULL填充。
-
了解过 RPC 框架吗 是的,RPC(Remote Procedure Call)框架是一种允许程序调用另一个地址空间(通常是一个远程服务器上)的过程或函数,而无需了解底层网络通信细节的技术。常见的RPC框架包括:
-
gRPC:由Google开发,使用Protocol Buffers作为接口描述语言,支持多种编程语言。
-
Apache Thrift:由Facebook开发,支持多种编程语言,使用自己的接口定义语言(IDL)。
-
Dubbo:由阿里巴巴开发,是一款高性能、轻量级的开源Java RPC框架。
-
Spring Cloud:提供了基于Spring Boot的微服务框架,其中包括服务发现、配置管理、负载均衡、断路器等组件,支持RESTful风格的RPC调用。
12.字节一面
-
B+树为什么范围查询比B树快:
-
B+树的所有数据都在叶子节点,并且叶子节点之间是通过指针相连的,这样在进行范围查询时,可以快速地通过指针遍历叶子节点,而不需要回到树的上层。而B树的非叶子节点也存储数据,范围查询时可能需要多次回到树的上层,导致查询效率降低。
-
InnoDB索引类型:
-
InnoDB支持以下几种索引类型:
-
聚簇索引(Clustered Index):数据行存放在索引的叶子节点上。
-
二级索引(Secondary Index):数据行不存放在索引的叶子节点上,而是存放指向聚簇索引的指针。
-
全文索引(Full-Text Index):用于全文搜索。
-
-
聚簇索引和非聚簇索引的区别:
-
聚簇索引的叶子节点存储了数据行,而非聚簇索引的叶子节点存储的是指向数据行的指针。
-
一个表只能有一个聚簇索引,但可以有多个非聚簇索引。
-
聚簇索引通常查询效率更高,因为可以直接定位到数据行。
-
-
Mysql事务隔离级别,分别解决什么问题:
-
READ UNCOMMITTED:存在脏读、不可重复读、幻读问题。
-
READ COMMITTED:解决脏读问题,存在不可重复读、幻读问题。
-
REPEATABLE READ(默认):解决脏读、不可重复读问题,存在幻读问题。
-
SERIALIZABLE:解决脏读、不可重复读、幻读问题,但性能最低。
-
-
什么时候使用varchar,tinytext,Text,mediumtext,longtext:
-
varchar:用于存储可变长度的字符串,当字符串长度不确定,但不会超过一定范围时使用。
-
tinytext:用于存储小文本,最大长度为255字节。
-
text:用于存储普通文本,最大长度为65,535字节。
-
mediumtext:用于存储中等长度文本,最大长度为16,777,215字节。
-
longtext:用于存储极大文本,最大长度为4,294,967,295字节。
-
-
Mediumtext和text哪个大:
-
Mediumtext比text大,mediumtext最大长度为16,777,215字节,而text最大长度为65,535字节。
-
数据库连接池的配置有什么使用心得吗:
-
合理设置连接池的大小,避免过大或过小。
-
设置合适的连接超时时间。
-
监控连接池的使用情况,及时调整配置。
-
使用连接池时,确保正确地关闭连接。
-
-
Java的集合类介绍一下:
-
Java集合类主要分为以下几类:
-
List:有序、可重复的集合,如ArrayList、LinkedList。
-
Set:无序、不可重复的集合,如HashSet、TreeSet。
-
Map:键值对集合,如HashMap、TreeMap。
-
Queue:队列集合,如PriorityQueue、LinkedList。
-
-
map如果要实现线程安全需要怎么做:
-
使用Collections.synchronizedMap()方法包装一个Map。
-
使用ConcurrentHashMap,它提供了更好的并发性能。
-
-
java的运行时内存区域介绍一下:
-
方法区(Method Area):存储类信息、常量、静态变量等。
-
堆(Heap):存储对象实例和数组。
-
栈(Stack):存储局部变量和方法调用。
-
程序计数器(Program Counter Register):存储当前线程执行的字节码行号。
-
本地方法栈(Native Method Stack):为本地方法服务。
-
-
内存溢出了该怎么排查:
-
使用JVM监控工具,如VisualVM、JProfiler。
-
分析堆栈信息,查找大对象或内存泄漏。
-
使用MAT(Memory Analyzer Tool)分析堆转储文件。
-
-
Io分为哪几种:
-
同步IO和异步IO。
-
阻塞IO和非阻塞IO。
-
BIO(Blocking IO)、NIO(Non-blocking IO)、AIO(Asynchronous IO)。
-
-
设计一个线程安全的自增id该怎么做:
-
使用AtomicInteger类。
-
使用synchronized关键字或ReentrantLock。
-
使用数据库的序列或自增字段。
-
-
AtomicInteger怎么实现线程安全,哪些地方用到了cas:
-
AtomicInteger通过CAS(Compare And Swap)操作实现线程安全。
-
在AtomicInteger的incrementAndGet、decrementAndGet等方法中用到了CAS。
-
-
介绍一下BIO,NIO,AIO:
连接,通过选择器(Selector)来实现非阻塞IO操作。
IO多路复用发生在“等待数据准备好”的阶段。它允许一个线程同时监视多个文件描述符,一旦某个文件描述符准备好进行IO操作,线程就可以进行相应的处理,从而提高IO操作的效率。
(17)计算机网络每层有哪些协议:
(18)http1.0,1.1,2.0,3.0的区别:
(19)ipv4和ipv6的区别:
-
BIO(Blocking IO):传统的IO模型,每个请求都会阻塞线程。
-
NIO(Non-blocking IO):基于事件的IO模型,使用选择器(Selector)处理多个通道(Channel)
-
AIO(Asynchronous IO):异步IO模型,基于事件和回调机制,不需要阻塞等待IO操作完成。
-
(16)Io分为哪几个阶段,io多路复用发生在哪个阶段: IO操作通常分为以下阶段:
-
等待数据准备好
-
数据从内核空间拷贝到用户空间
-
应用层:HTTP, HTTPS, FTP, SMTP, DNS等。
-
传输层:TCP, UDP。
-
网络层:IP, ICMP, IGMP。
-
数据链路层:ARP, RARP, IEEE 802.3/802.11等。
-
物理层:各种物理硬件标准,如以太网、光纤等。
-
HTTP/1.0:每次请求/响应后,连接关闭;没有持久连接的概念。
-
HTTP/1.1:引入持久连接(Keep-Alive),允许在一个连接中传输多个请求/响应;引入管道化(Pipelining),但服务器可能需要按顺序响应;引入了缓存控制机制。
-
HTTP/2.0:引入了多路复用,一个连接内可以并行处理多个请求;引入了头部压缩;支持服务器推送。
-
HTTP/3.0:基于QUIC协议,提供了更好的性能,包括更快的连接建立、更好的拥塞控制和更强的安全性。
-
地址长度:IPv4地址长度为32位,IPv6地址长度为128位。
-
地址表示:IPv4使用点分十进制表示,IPv6使用冒号分隔的十六进制表示。
-
地址空间:IPv4地址空间较小,IPv6地址空间巨大,几乎可以无限分配。
-
配置:IPv6简化了地址配置,支持无状态地址自动配置(SLAAC)。
-
安全性:IPv6在设计时考虑了安全性,内置了IPsec支持。
-
首部格式:IPv6的首部格式更简单,提高了数据包处理效率。
-
13.高顿教育一二面:
Java 集合类
-
List:接口,允许重复和 null 值,常用实现类有 ArrayList 和 LinkedList。
-
ArrayList:基于动态数组,适合查找和更新操作。
-
LinkedList:基于双向链表,适合插入和删除操作。
-
-
Set:接口,不允许重复元素,常用实现类有 HashSet 和 TreeSet。
-
HashSet:基于 HashMap,存储无序且不重复的元素。
-
TreeSet:基于红黑树,元素有序。
-
-
Map:接口,存储键值对,常用实现类有 HashMap 和 TreeMap。
-
HashMap:基于散列桶,适合快速查找。
-
HashMap 的实现原理
-
HashMap 使用数组加链表(或红黑树)的结构存储数据。
-
通过 hash 函数计算键的 hash 值,确定元素在数组中的位置。
-
如果发生 hash 冲突,则使用链表或红黑树解决。
注解的实现原理
-
注解是通过 Java 的反射机制实现的。
-
注解本身不会做任何事情,它只是一种标记。
-
在运行时,通过反射读取注解信息,并根据这些信息执行相应的操作。
反射的原理
-
反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法。
-
反射机制主要提供以下功能:在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法。
异常处理
-
使用 try-catch-finally 结构进行异常处理。
-
try 块中放置可能抛出异常的代码。
-
catch 块用于捕获并处理异常。
-
finally 块用于执行必要的清理工作,无论是否发生异常都会执行。
数据库的最左匹配原则
-
最左匹配原则是指 SQL 语句中的 WHERE 条件按照索引的顺序从左到右进行匹配。
-
只有当查询条件满足最左边的索引字段时,索引才会被使用。
MySQL 事务的隔离级别
-
READ UNCOMMITTED:读未提交
-
READ COMMITTED:读已提交
-
REPEATABLE READ:可重复读
-
SERIALIZABLE:串行化
-
常用的是 **REPEATABLE READ**。
分布式锁的使用场景
-
当多个节点需要访问同一资源,并且要保证操作的一致性时,会使用分布式锁。
为什么用 Redis?它为什么快?
-
Redis 是基于内存的键值数据库,支持多种数据结构。
-
它快的原因包括:单线程模型避免了线程切换开销,数据结构简单,直接操作内存。
如何实现分布式锁
-
可以使用 Redis 的 SETNX 命令实现分布式锁。
-
锁的键值可以是一个唯一标识,设置过期时间防止死锁。
锁失效的解决
-
可以通过守护线程定期续期来防止锁失效。
MQ 的使用
-
MQ(消息队列)用于解耦系统组件,处理异步任务,实现系统间的通信。
-
使用场景包括:订单处理、日志收集等。
消息中间件的好处
-
提高系统吞吐量,降低系统耦合度,实现异步通信。
Docker 的使用
-
Docker 用于创建、部署和运行容器。
-
使用 Dockerfile 定义应用环境,通过 docker build 命令构建镜像,使用 docker run 运行容器。
Shell 脚本和 Lua 脚本的使用
-
Shell 脚本常用于自动化部署、日志分析等。
-
Lua 脚本在某些场景下(如 Redis)用于复杂的数据处理。
杀掉进程的命令
-
kill -9 <进程ID>
:强制杀掉进程。
ConcurrentHashMap 底层实现
-
使用分段锁(Segment),每个 Segment 相当于一个小的 HashMap。
扩容机制
-
当元素数量达到容量阈值时,会进行扩容操作,通常是创建一个新的数组,并将旧数组中的元素重新 hash 到新数组中。
分段锁的加锁机制
-
分段锁通过 ReentrantLock 实现,每个 Segment 对应一个锁。
分段锁是否可重入
-
是的,ReentrantLock 是可重入锁。
使用 synchronized 和 CAS 的原因
-
synchronized 用于保证操作的原子性。
-
CAS 用于实现无锁编程,提高并发性能。
ConcurrentHashMap 使用的锁
-
ConcurrentHashMap 主要是使用乐观锁(通过 CAS 操作)来提高并发性能。
ConcurrentHashMap 的锁机制
-
乐观锁(CAS):在 ConcurrentHashMap 中,主要使用乐观锁机制,通过 Compare And Swap(CAS)操作来更新数据,以减少锁的使用,提高并发性能。
-
分段锁(Segment):在 JDK 1.7 及之前版本中,ConcurrentHashMap 使用分段锁技术,将内部数据分为多个 Segment,每个 Segment 对应一把锁,这样可以在多线程环境下减少锁竞争。
-
synchronized:在 JDK 1.8 中,ConcurrentHashMap 放弃了分段锁,转而使用 synchronized 来控制对桶(bucket)的访问,同时在扩容和统计数据时使用 CAS 操作。
ConcurrentHashMap 的扩容机制
-
动态扩容:当元素数量达到容量阈值(即装载因子乘以当前容量)时,会触发扩容操作。
-
并发扩容:ConcurrentHashMap 支持并发扩容,多个线程可以同时参与扩容过程,通过转移元素到新的桶数组来提高效率。
-
转移链表:扩容时,会遍历旧桶中的链表,并将链表中的节点重新 hash 到新桶中。
分段锁的加锁机制
-
在 JDK 1.7 中,每个 Segment 对应一个 ReentrantLock,当操作某个 Segment 时,需要先获取该 Segment 的锁。
-
分段锁使得不同的 Segment 可以并行操作,从而提高了并发性能。
分段锁是否可重入
-
是的,分段锁是基于 ReentrantLock 实现的,因此它是可重入的。
已经用了 synchronized,为什么还要用 CAS 呢?
-
性能:在某些操作中,CAS 可以比锁更高效,因为它不需要上下文切换和线程状态变更。
-
粒度:CAS 允许更细粒度的并发控制,尤其是在 ConcurrentHashMap 中,通过 CAS 可以实现无锁的更新操作。
ConcurrentHashMap 用了悲观锁还是乐观锁?
-
乐观锁:在大多数情况下,ConcurrentHashMap 使用乐观锁机制,通过 CAS 操作来更新数据。
-
悲观锁:在必要时,如初始化 Segment 或扩容时,也会使用 synchronized 来保证操作的原子性。
ConcurrentHashMap 的其他细节
-
计数:ConcurrentHashMap 使用一个 baseCount 变量和一个 counterCells 数组来记录元素的数量,以支持并发计数。
-
搜索:在进行查找操作时,ConcurrentHashMap 不会加锁,而是通过 volatile 关键字确保可见性。
-
迭代器:ConcurrentHashMap 的迭代器是弱一致性的,它们不会抛出 ConcurrentModificationException,但可能不会立即反映出其他线程的结构修改。
以上是对 ConcurrentHashMap 及相关概念的详细解释。在实际应用中,理解这些原理有助于更好地使用 Java 的并发工具类,并优化并发程序的性能。
14.25届影石正式批一面
OSI 七层模型及其作用
-
物理层(Physical Layer):
-
负责在物理媒体上实现原始的比特流传输,例如电缆、光纤。
-
-
数据链路层(Data Link Layer):
-
负责在相邻节点之间的可靠链接,处理帧的传输,错误检测和修正。
-
-
网络层(Network Layer):
-
负责数据包从源到目的地的传输和路由选择,例如 IP 协议。
-
-
传输层(Transport Layer):
-
负责提供端到端的数据传输服务,如 TCP 和 UDP。
-
-
会话层(Session Layer):
-
负责建立、管理和终止会话,例如 SSL。
-
-
表示层(Presentation Layer):
-
负责数据的转换、加密和压缩,确保数据在网络中传输的正确表示。
-
-
应用层(Application Layer):
-
为应用程序提供服务,例如 HTTP、FTP、SMTP。
-
HTTP 协议内容
-
请求行:包括方法、URL 和 HTTP 版本。
-
请求头:包括一系列的键值对,如 Host、User-Agent、Accept 等。
-
空行:请求头和请求体之间的分隔。
-
请求体(可选):包含请求的数据,如 POST 请求中的表单数据。
GET 和 POST 协议
-
GET:通常用于请求服务器发送资源,请求参数附加在 URL 之后,大小有限制,数据暴露在 URL 中。
-
POST:通常用于提交数据给服务器,请求参数包含在请求体中,大小无限制,数据不会暴露在 URL 中。
TCP 交互过程
-
握手:三次握手建立连接(SYN, SYN-ACK, ACK)。
-
数据传输:在建立连接后进行数据传输。
-
挥手:四次挥手断开连接(FIN, ACK, FIN, ACK)。
如果是 HTTPS:
-
在 TCP 握手之后,会进行 TLS 握手,用于加密通信。
-
TLS 握手:包括证书交换、密钥交换等步骤,确保加密通道的建立。
ConcurrentHashMap 底层数据结构
-
ConcurrentHashMap 在 JDK 1.8 中使用数组 + 链表 + 红黑树的数据结构。
-
使用这种结构是为了在并发环境下提供更高的性能,通过分段锁或 synchronized 保证线程安全,同时使用红黑树优化搜索效率。
MySQL 索引结构
-
聚簇索引(Clustered Index):叶节点包含完整的数据行。
-
非聚簇索引(Secondary Index):叶节点包含索引列和指向数据行的指针。
索引结构除了 B+ 树
-
哈希索引:通过哈希表实现,适用于等值查询。
-
全文索引:用于全文搜索。
-
R-Tree:用于空间数据类型。
B+ 树索引自增与随机的区别
-
自增:写入效率高,不会产生页分裂,适用于插入操作频繁的场景。
-
随机:可能导致页分裂,写入效率低,适用于更新操作频繁的场景。
索引是 ID 自身写入效率
-
如果索引是自增的 ID,写入通常会更为高效,因为新记录总是插入到 B+ 树的末尾。
B+ 树与二叉树的区别
-
B+ 树:是一种多路平衡查找树,所有的数据都在叶子节点出现,叶子节点之间通过指针连接,适合大量数据的磁盘存储。
-
二叉树:每个节点最多有两个子节点,不一定是平衡的,适用于内存中数据的查找。
Redis 数据结构
-
字符串(Strings):可以存储字符串、整数或浮点数。
-
列表(Lists):按照插入顺序排序的字符串列表。
-
集合(Sets):无序集合,元素具有唯一性。
-
哈希(Hashes):键值对集合。
-
有序集合(Sorted Sets):集合中每个元素都会关联一个分数,可以根据分数排序。
-
位图(Bitmaps):以位为单位进行操作。
-
HyperLogLog:用于估计集合的基数。
-
流(Streams):支持消息队列功能。
B+ 树整体结构描述
B+ 树是一种自平衡的树结构,它通常用于数据库和操作系统的文件系统中。以下是 B+ 树的主要特点:
-
根节点:位于树的最顶层,可能包含多个键和子节点指针。
-
内部节点:包含多个键和子节点指针,但不像叶子节点那样存储数据记录。
-
叶子节点:包含所有数据记录,并且叶子节点之间是通过指针连接的双向链表。
-
键:每个节点包含多个键,这些键用于在树中进行查找操作。
-
平衡:在插入或删除操作后,B+ 树会进行必要的分裂或合并,以保持树的平衡。
与二叉树相比,B+ 树的优势在于:
-
节点存储更多键:B+ 树节点可以有多个子节点,这意味着树的高度比二叉树低,从而减少了磁盘 I/O 操作。
-
更好的磁盘 I/O 性能:由于节点存储了多个键,每次磁盘 I/O 可以读取更多的键,减少了查找操作的磁盘访问次数。
-
范围查询:由于叶子节点是链表,B+ 树支持高效的区间查询。
Redis 数据结构(续)
继续描述 Redis 的一些其他数据结构:
-
地理空间索引(Geospatial Indexes):用于存储地理位置信息并进行距离和范围查询。
-
位图(Bitmaps):可用于实现高效的位数组操作,如统计活跃用户等。
-
HyperLogLog:用于估算集合中不同元素的数量,具有非常高的空间效率。
-
发布订阅(Pub/Sub):实现消息发布和订阅功能,类似于消息队列系统。
-
事务(Transactions):允许执行一组命令作为一个单独的操作,保证原子性。
-
Lua 脚本:可以使用 Lua 脚本来编写复杂的操作,这些操作可以在 Redis 服务器端原子性地执行。
其他数据库索引结构
除了 B+ 树,数据库系统可能还会使用以下索引结构:
-
哈希索引:通过哈希表实现,适用于等值查询,但不支持范围查询。
-
全文索引:用于文本搜索,通常使用倒排索引来实现。
-
R-Tree:用于空间数据类型,如地理信息系统(GIS)中的数据。
-
Trie(前缀树):用于处理字符串前缀匹配的查询,例如自动补全功能。
索引是自增与随机的适用场景
-
自增索引:
-
适用于插入操作频繁的场景,如日志记录和时间序列数据。
-
由于新记录总是追加到索引的末尾,可以减少页分裂,提高插入性能。
-
-
随机索引:
-
适用于更新操作频繁的场景,如用户信息表,其中用户 ID 不一定是自增的。
-
随机索引可能导致页分裂,但可以更好地处理非顺序的数据插入。
-
B+ 树结构下索引是 ID 自身写入效率
如果索引是基于自增的 ID,写入操作通常更高效,因为:
-
顺序写入:新记录可以直接追加到现有数据的末尾,减少了页分裂。
-
减少页分裂:减少了因插入操作导致的页分裂,从而提高了写入效率。
B+ 树与二叉树的区别(续)
-
节点度数:B+ 树的节点可以有多个子节点(度数大于 2),而二叉树的节点最多有两个子节点。
-
数据存储:B+ 树只在叶子节点存储数据,而二叉树可能在内部节点也存储数据。
-
搜索效率:B+ 树由于节点度数高,通常树的高度比二叉树低,从而提高了搜索效率。
-
范围查询:B+ 树支持高效的区间查询,而二叉树不支持。
这些概念和结构是数据库和缓存系统设计中的关键组成部分,理解它们可以帮助开发人员在设计高效的数据存储和检索系统时做出更好的决策。
15.得物后端Java一面
gRPC代替HTTP的时间降低取决于多种因素,包括网络条件、数据大小、请求的复杂性等。gRPC使用二进制协议(如Protocol Buffers)进行序列化和反序列化,并且通常使用HTTP/2进行传输,这可以显著减少数据传输的大小和延迟。相比HTTP/1.1,gRPC可以降低大约30%到50%的延迟,但这只是一个大致的范围,具体数值会根据实际情况有所不同。
gRPC的调用过程
-
服务定义:使用Protocol Buffers定义服务接口和消息类型。
-
服务端实现:服务端实现定义的接口,并启动gRPC服务器。
-
客户端调用:
-
客户端使用gRPC stub(客户端存根)来调用服务端的方法。
-
客户端通过服务发现机制(如DNS、Consul、Zookeeper等)获取到服务端地址。
-
客户端与服务端建立HTTP/2连接。
-
客户端发送RPC请求,服务端接收请求并处理。
-
服务端返回响应给客户端。
-
RPC底层通信
RPC(远程过程调用)底层通信通常涉及以下步骤:
-
序列化:将请求对象转换为字节流。
-
网络传输:通过网络将序列化后的字节流传送到服务端。
-
反序列化:服务端接收到字节流后,将其还原为请求对象。
-
执行:服务端执行请求指定的方法。
-
序列化响应:将执行结果序列化。
-
网络传输响应:将序列化后的响应传回客户端。
-
反序列化响应:客户端接收到响应字节流后,将其还原为结果对象。
Netty
Netty是一个异步事件驱动的网络应用框架,用于快速开发高性能、高可靠性的网络服务器和客户端程序。它提供了多路复用、事件通知和线程管理等功能。
多路复用模型
多路复用模型是指单个线程可以同时处理多个网络连接(或文件描述符)的I/O事件。在Java中,NIO(非阻塞I/O)提供了Selector组件,用于实现多路复用。
TCP粘包和拆包问题
TCP粘包和拆包问题可以通过以下方法解决:
-
固定长度:每个数据包大小固定。
-
分隔符:在每个数据包末尾添加特殊分隔符。
-
长度字段:在数据包头部添加表示数据长度的字段。
TCP拥塞控制
TCP拥塞控制是TCP协议用来避免网络拥塞的一系列算法,常见的算法包括:
-
慢启动:逐渐增加发送窗口的大小。
-
拥塞避免:当拥塞窗口接近阈值时,线性增加窗口大小。
-
快速重传:在接收方连续收到三个重复的ACK时,无需等待重传计时器到期,立即重传丢失的报文段。
-
快速恢复:在快速重传后,不是立即执行慢启动,而是将阈值设置为当前窗口的一半,然后执行拥塞避免算法。
常见垃圾回收算法
-
标记-清除:标记出所有活动的对象,然后清除未被标记的对象。
-
标记-整理:标记出所有活动的对象,然后将所有活动对象移动到内存的一端,清除边界以外的内存。
-
复制:将内存分为两个半区,每次只使用一个,在垃圾回收时,将活动的对象复制到另一个半区,清理掉旧半区。
垃圾标记和三色标记法
垃圾标记是通过可达性分析来确定哪些对象是垃圾。三色标记法将对象分为三种颜色:
-
白色:未被访问过的对象。
-
灰色:正在访问的对象。
-
黑色:已经访问完毕的对象。
记忆集
记忆集是一种用于记录从非收集区域指向收集区域指针的数据结构,它用于处理跨代引用问题。
G1垃圾回收器
G1垃圾回收器通过划分多个区域(Region)来避免全堆扫描,使用Remembered Sets来解决跨代引用问题。
ZGC
ZGC(Z Garbage Collector)是一种低延迟垃圾回收器,它使用染色指针和读屏障技术来实现。
染色指针
染色指针是ZGC中的一种技术,它将指针分为多个部分,其中包括标记位,用于表示对象的存活状态。
常见限流算法
-
令牌桶:按照固定的速率生成令牌,请求需要消耗令牌才能执行。
-
漏桶:请求按照固定的速率流出,如果请求过多,会暂存在漏桶中。
Redis分布式锁
Redis分布式锁是用于在分布式系统中保持数据一致性的锁机制。
如果分布式锁到期了但没有完成锁内部的逻辑调用,可以采取以下措施:
-
锁续期:在锁快要到期时,由持有锁的进程或线程自动延长锁的过期时间。
-
超时重试:当锁过期后,尝试重新获取锁,可能需要配合额外的逻辑来处理并发冲突。
-
业务补偿:如果无法重新获取锁,执行一些补偿逻辑,比如回滚操作或者标记任务为失败,稍后重新执行。
Redis分布式锁能实现可重入吗?
是的,Redis分布式锁可以实现可重入。这通常通过在锁的值中存储线程或进程的标识以及重入次数来实现。每次进入锁时,增加重入次数;每次退出锁时,减少重入次数。只有当重入次数为零时,锁才会被释放。
了解Redlock吗?Redlock主要解决哪些问题?
Redlock是一个基于Redis的分布式锁算法,由Redis的作者提出。它主要解决了以下问题:
-
容错性:即使部分Redis节点失败,锁仍然有效。
-
安全性:确保锁在任意时间点只有一个客户端持有。
-
可用性:即使系统部分不可用,客户端仍然能够获取和释放锁。
Redlock通过在多个独立的Redis节点上获取锁来实现高可用性和容错性。
JDK框架中的ReentrantLock应用多吗?
是的,ReentrantLock
在JDK框架中应用广泛,尤其是在并发编程中。它是一个可重入的互斥锁,提供了比synchronized
更丰富的功能,如可中断的锁获取、尝试非阻塞地获取锁以及支持多个条件变量。
了解ReentrantLock内部的实现吗?
ReentrantLock
内部主要基于AQS(AbstractQueuedSynchronizer)实现。它维护了一个同步状态来表示锁的持有情况,并使用一个FIFO队列来管理等待获取锁的线程。ReentrantLock
支持公平锁和非公平锁两种模式:
-
公平锁:按照线程请求锁的顺序来获取锁。
-
非公平锁:允许线程插队获取锁,可能会造成线程饥饿。
了解synchronized的锁升级过程吗?
synchronized
的锁升级过程如下:
-
偏向锁:当锁第一次被线程获取时,会设置为偏向模式,假设将来只有该线程会访问该锁,减少不必要的同步开销。
-
轻量级锁:当偏向锁的锁有竞争时,会升级为轻量级锁,通过CAS操作来避免线程阻塞。
-
重量级锁:当轻量级锁竞争激烈时,会升级为重量级锁,此时会涉及操作系统层面的线程阻塞和唤醒。
能简单介绍一下轻量级锁的实现吗?轻量级锁更新的是哪个对象的哪个字?
轻量级锁通过CAS操作尝试将锁对象的对象头中的Mark Word更新为指向锁记录的指针。轻量级锁更新的是持有锁的线程栈中的Lock Record(锁记录)的指针,这个指针指向锁对象的对象头。
了解Mark Word吗?
Mark Word是Java对象头的一部分,用于存储对象的