整理-美团面经
1.ArrayList和LinkedList简介和区别?
ArrayList(数组列表):
- 简介:
ArrayList
是一个基于动态数组实现的集合类。它允许在列表的末尾快速地添加和删除元素,同时可以根据索引直接访问列表中的元素。 - 内部实现:
ArrayList
内部使用一个数组来存储元素,当数组空间不足时,会进行自动扩容(通常是当前容量的1.5倍),以容纳更多的元素。 - 特点:
- 随机访问快速: 由于使用数组实现,
ArrayList
支持按索引随机访问,时间复杂度为 O(1)。 - 动态扩容: 当元素数量超过当前容量时,
ArrayList
会自动扩容,但在扩容时需要重新分配内存和复制元素,可能带来性能开销。
- 随机访问快速: 由于使用数组实现,
LinkedList(链表):
- 简介:
LinkedList
是一个基于双向链表实现的集合类。它允许在列表的开头和末尾快速地添加和删除元素,同时支持高效的插入和删除操作。 - 内部实现:
LinkedList
内部使用双向链表来存储元素,每个节点包含了对前一个节点和后一个节点的引用。 - 特点:
- 高效的插入和删除: 由于是链表结构,
LinkedList
在列表中间插入和删除操作非常高效,时间复杂度为 O(1)。 - 顺序访问相对较慢: 链表结构使得
LinkedList
不支持按索引随机访问,必须从头节点开始顺序查找,时间复杂度为 O(n)。
- 高效的插入和删除: 由于是链表结构,
区别总结:
- 内部实现:
ArrayList
使用动态数组,而LinkedList
使用双向链表。 - 随机访问:
ArrayList
支持按索引随机访问,时间复杂度为 O(1),而LinkedList
不支持随机访问,需要按顺序查找,时间复杂度为 O(n)。 - 插入和删除:
LinkedList
在中间插入和删除操作上更高效,时间复杂度为 O(1),而ArrayList
在中间插入和删除操作上较慢,因为需要移动元素,时间复杂度为 O(n)。 - 空间复杂度:
ArrayList
的空间复杂度相对较小,因为只需要存储元素和少量的附加信息,而LinkedList
需要额外的空间来存储链表节点的引用,相对较大。
2.HashMap底层实现,扩容机制,执行流程?
底层实现:
HashMap
的底层实现是一个哈希表,它包含一个数组(称为哈希桶)和一些链表或红黑树。数组中的每个位置都可以存储一个链表或红黑树,用于解决哈希冲突。
扩容机制:
HashMap
在初始化时创建一个初始容量的哈希表,默认初始容量为 16,负载因子为 0.75。当哈希表中的元素数量达到负载因子与容量的乘积时(即负载因子 * 容量),HashMap
将触发扩容操作。- 扩容操作会将哈希表的容量翻倍,并重新分配每个键值对到新的位置,以确保哈希冲突的几率降低。
- 扩容操作是一个比较耗时的操作,因为它需要重新哈希和重新分配所有的键值对。
执行流程:
- 当你向
HashMap
中添加键值对时,HashMap
首先会计算键的哈希码(使用键的hashCode()
方法)。 - 接着,它会通过哈希码和当前容量计算出要存储的位置,通常是使用哈希码模以容量的余数来确定位置。
- 如果该位置为空(即没有冲突),则直接将键值对存储在这个位置。
- 如果该位置已经有键值对存在,可能会发生哈希冲突。在这种情况下,
HashMap
会将新键值对添加到链表的末尾(或转化为红黑树),或者替换已经存在的键值对(如果键相同的话)。 - 当键值对数量达到负载因子与容量的乘积时,
HashMap
触发扩容操作。 - 扩容操作会创建一个新的容量是原容量两倍的哈希表,然后将所有键值对重新分配到新的位置。这个过程可能比较耗时,但是确保了
HashMap
保持了合理的负载因子,避免了哈希冲突的频繁发生。 - 最后,当你查询键值对时,
HashMap
会根据键的哈希码计算位置,并在链表或红黑树中查找对应的值。
3.HashMap为何不安全?不安全主要包括哪些方面?
当多个线程同时操作同一个 HashMap
实例时,可能会导致不一致的状态。这包括插入、删除或修改键值对。例如,一个线程正在遍历 HashMap
,而另一个线程在这个过程中插入或删除键值对,可能导致遍历的线程抛出
4.线程安全的ConcurrentHashMap的实现原理?
- 分段锁(Segment):
ConcurrentHashMap
使用了一种叫做分段锁的机制。它将整个哈希表分成多个小的哈希表段(Segment),每个段独立地加锁。这样,不同的段可以在不同的线程上进行并发操作,减小了竞争的范围。 - 哈希桶(Buckets): 每个段包含一个哈希桶数组,用于存储键值对。哈希桶的大小可以根据需要自动调整,以确保在高负载时能够保持性能。
- 哈希冲突的解决: 当发生哈希冲突时,
ConcurrentHashMap
会锁定相应的段,而不是整个表,以减小锁的粒度。这使得其他线程仍然可以在不同的段上进行并发操作,从而提高了性能。 - 安全迭代器:
ConcurrentHashMap
提供了安全的迭代器,允许在迭代过程中进行并发修改。这是通过在遍历时使用分段的快照来实现的,以避免并发修改引起的问题。 - CAS 操作:
ConcurrentHashMap
使用了 CAS(Compare-And-Swap)操作来保证线程安全。CAS 是一种原子操作,用于在没有锁的情况下修改共享数据。这可以减小锁的粒度,提高并发性能。 - 自动扩容:
ConcurrentHashMap
在需要时会自动扩容,以防止哈希表负载过高。扩容过程是逐段进行的,只涉及到正在进行扩容的段。
5.什么是Hash冲突?如何解决Hash冲突?
哈希冲突(Hash Collision)指的是当两个或多个不同的键通过哈希函数映射到相同的哈希桶或位置时的情况。哈希冲突是哈希表中常见的问题,因为哈希函数通常会将大范围的输入映射到有限范围的输出。
链地址法(Separate Chaining):每个哈希桶中维护一个链表或其他数据结构,用于存储映射到同一个位置的键值对。当发生哈希冲突时,新的键值对会被添加到链表的末尾。这意味着多个键可以共享同一个哈希桶,每个键值对可以通过链表中的顺序来区分。
6.Redisson原理?
- 基于Redis: Redisson 利用 Redis 数据库的强大特性来实现分布式锁。Redis是一个高性能的内存数据库,它提供了原子性的操作和分布式数据存储。
- 核心数据结构: Redisson 的核心数据结构是
RMap
,它表示分布式的Map。在Redis中,这个数据结构对应一个Hash类型的数据结构。 - 获取锁: 当一个线程想要获得一个分布式锁时,它会在Redisson中创建一个
RMap
键值对,将锁名作为键,线程标识作为值,这个操作是原子的。如果多个线程同时尝试获取锁,只有一个线程会成功,其他线程将被阻塞。 - 锁的续约: Redisson 支持锁的续约机制。当一个线程成功获得锁后,它可以在一定的时间内不断续约锁的有效期。这可以防止锁的持有时间过短,导致其他线程误以为锁已经释放。
- 锁的释放: 当一个线程释放锁时,它会在
RMap
中删除相应的键值对,将锁释放给其他等待的线程。 - 锁的可重入性: Redisson支持可重入锁,允许同一个线程多次获得同一把锁。这是通过将线程标识添加到一个列表中来实现的。
- 锁的公平性: Redisson还支持公平锁,可以按照请求锁的顺序分配锁。
- 实现原理: Redisson的实现依赖于Redis的特性,特别是Redis的事务机制和原子性操作,以确保分布式锁的正确性。
7.synchronized原理?
8.锁的类型介绍,升级流程?
9.什么是慢查询?如何定位?如何解决?
什么是慢查询? 慢查询是指数据库系统中执行时间较长的查询或操作,它们通常超出了预定的性能指标或期望的响应时间。慢查询可能是由于查询语句复杂、索引缺失、大数据量、锁竞争等原因导致的。
如何定位慢查询? 慢查询的定位通常包括以下步骤:
- 性能监控: 使用数据库性能监控工具或服务,例如MySQL的慢查询日志、Percona Toolkit、PgBadger等,来捕获慢查询的信息。
- 分析执行计划: 使用数据库工具分析查询的执行计划,查看索引使用情况、连接方式、扫描行数等关键信息。
- 审查SQL语句: 仔细审查慢查询的SQL语句,检查是否存在不必要的复杂性、子查询、全表扫描等问题。
- 检查索引: 确保表的字段经常被查询的字段上有合适的索引,优化索引策略。
- 优化数据库配置: 调整数据库参数、缓冲池大小、连接池配置等,以适应查询负载。
如何解决慢查询? 解决慢查询问题通常包括以下方法:
- 优化SQL语句: 重写查询,减少复杂性,避免全表扫描,合理使用索引。
- 创建合适的索引: 确保经常查询的字段上有合适的索引,避免不必要的索引,同时定期维护索引。
- 查询缓存: 使用缓存技术,将频繁查询的数据缓存在内存中,减少数据库访问次数。
- 数据库分区: 如果数据量庞大,可以考虑使用数据库分区技术,将数据分散存储,提高查询性能。
- 硬件升级: 增加服务器资源,例如CPU、内存、磁盘,以提高数据库性能。
- 负载均衡: 使用负载均衡技术将查询分散到多个数据库节点,以分担查询负载。
- 数据库缓存: 使用内存数据库或缓存系统,如Redis、Memcached,缓存热门数据。
- 数据库优化工具: 使用数据库性能优化工具,如EXPLAIN,Percona Toolkit,pg_stat_statements等,来分析和优化查询。
10.死锁产生原因?如何解决?
11.http各个版本介绍以及各自特点?
- HTTP/1.0:
- 首个公开发布的HTTP版本。
- 使用短连接:每个HTTP请求/响应都需要建立和关闭一个独立的连接。
- 不支持持久连接,每次请求都需要重新建立连接,效率较低。
- HTTP/1.1:
- 引入了持久连接(Keep-Alive),允许多个请求和响应共享一个连接,提高了性能。
- 引入了管道化(Pipelining),允许客户端在不等待响应的情况下发送多个请求,提高并发性能。
- 引入了Host头字段,允许一个HTTP服务器托管多个域名。
- 使用Chunked传输编码支持流式传输。
- HTTP/2:
- 引入了二进制分帧传输,提高了效率,允许多个请求/响应并行传输。
- 头部压缩减小了数据传输大小。
- 使用多路复用允许多个请求/响应在一个连接上并行传输。
- 支持服务器推送,服务器可以主动向客户端推送资源。
- 支持优先级,可以指定请求的优先级。
- 提供更多的安全性,使用加密。
- HTTP/3:
- 基于UDP协议,使用QUIC协议作为传输层。
- 引入了头部压缩和多路复用,继承了HTTP/2的一些特性。
- 引入了连接迁移,允许在不同网络接口之间迁移连接,以提高鲁棒性。
- 具备更好的安全性,加密是默认设置。
- 支持0-RTT(零往返时间)握手,提高了性能。
12.TCP为什么可靠?
- 确认与重传: TCP使用了确认机制,接收方会回复确认信息以告知发送方已成功接收数据。如果发送方未收到确认信息,它会重传数据,确保数据的可达性。这消除了数据在传输过程中的丢失问题。
- 流量控制: TCP采用流量控制机制,通过窗口大小来限制发送方的数据量,以避免接收方过载。这确保了在高负载情况下的可靠传输,防止数据包丢失或拥塞。
- 拥塞控制: TCP还具有拥塞控制机制,它监测网络拥塞的迹象并相应地调整发送速率。这有助于防止网络拥塞,确保数据可靠传输。
13.Redis数据结构类型,各自介绍以及使用场景?
- 字符串(String):
- 介绍:字符串是Redis中最简单的数据结构,它可以包含任何类型的数据,如文本、二进制数据等。
- 使用场景:适用于存储各种数据,如缓存、计数器、会话数据等。
- 哈希表(Hash):
- 介绍:哈希表是一个键值对的集合,类似于关联数组,每个键都映射到一个值。
- 使用场景:适用于存储对象的属性集合,如存储用户信息、配置参数等。
- 列表(List):
- 介绍:列表是一个有序的字符串元素集合,支持从两端添加和删除元素。
- 使用场景:适用于实现队列、栈、消息发布和订阅等数据结构。
- 集合(Set):
- 介绍:集合是一个无序且不重复的字符串元素集合。
- 使用场景:适用于存储不重复的元素,如标签、关键词等。
- 有序集合(Sorted Set):
- 介绍:有序集合是一个有序的字符串元素集合,每个元素都有一个分数,可以根据分数排序。
- 使用场景:适用于排行榜、范围查询、按分数排序的需求。
- 位图(Bitmap):
- 介绍:位图是一种特殊的字符串,可以进行位级别的操作,如设置、清除、统计位等。
- 使用场景:适用于记录用户行为、统计活跃用户、布隆过滤器等。
- 超级日志(HyperLogLog):
- 介绍:超级日志是用于估算基数(不重复元素的数量)的数据结构,占用固定内存。
- 使用场景:适用于统计网站独立访客、UV等需要估算基数的场景。
- 地理空间(Geospatial):
- 介绍:Redis支持地理空间数据类型,可以存储地理坐标和执行地理位置查询操作。
- 使用场景:适用于位置服务、附近搜索等地理空间应用。
14.Redis中的set和HashSet底层实现原理?
- SET:
SET
是Redis中的集合数据结构,它是无序、不重复的字符串集合。- 底层实现:
SET
的底层实现通常是使用哈希表(Hash Table)。哈希表的键存储了集合中的元素,而哈希表的值通常为空,只使用键作为集合的元素。当执行添加、删除、查找等操作时,Redis使用哈希表提供的快速查找特性。 - 时间复杂度:
SET
的插入、删除和查找操作的时间复杂度通常为O(1)。
- HASH:
HASH
是Redis中的哈希表数据结构,它是一个键值对的集合。- 底层实现:
HASH
的底层实现同样使用哈希表,但它使用哈希表的键值对来存储多个字段和值,每个字段都映射到一个值。 - 时间复杂度:
HASH
的操作时间复杂度也是通常为O(1),因为哈希表提供了快速查找。
15.bitmap底层实现原理?
Redis的Bitmap底层实现通常使用字符串(String)数据结构,其中每个字符代表一个位。下面是Bitmap的具体实现方式:
- 字符串存储: Redis使用字符串来存储Bitmap数据。每个字符表示一个位,通常用
0
表示未设置(false),用1
表示已设置(true)。例如,一个Bitmap数据可能会存储为字符串"10100110"
,其中每个字符表示一个位。 - 位索引: Redis通过索引来访问位。索引从0开始递增,对应于字符串中的字符位置。例如,索引0对应字符串的第一个字符,索引1对应第二个字符,以此类推。
- 位设置和清除: Redis提供了
SETBIT
和BITOP
等命令来设置和清除位。使用SETBIT
命令可以设置指定位的状态为1,使用BITOP
命令可以进行位操作,如与(AND)、或(OR)、异或(XOR)等,以实现复杂的位操作。 - 位获取: 使用
GETBIT
命令可以获取指定位的状态,即0或1。 - 内存优化: Redis通常会采用紧凑的方式来存储位,以降低内存消耗。例如,8个位可以存储在一个字节中。
示例:
- 设置第3位为1:
SETBIT mybitmap 3 1
- 获取第5位的状态:
GETBIT mybitmap 5
- 进行位操作:
BITOP AND destbitmap mybitmap1 mybitmap2
16.Redis缓存雪崩、击穿、穿透是什么?各自如何解决?
- 缓存雪崩(Cache Avalanche):
- 概念: 缓存雪崩是指在某个时刻,大量缓存的数据同时失效或被清除,导致大量请求直接访问数据库,造成数据库负载激增,甚至导致数据库宕机。
- 解决方法:
- 给缓存数据设置不同的过期时间,避免同时失效。
- 使用自动刷新缓存的策略,保证缓存数据不会在同一时间全部失效。
- 使用热点数据永不过期,或采用二级缓存,保证热点数据的可用性。
- 缓存击穿(Cache Key Penetration):
- 概念: 缓存击穿是指某个热点数据失效后,大量请求同时访问该数据,导致这些请求都穿透到数据库,增加了数据库负载。
- 解决方法:
- 使用互斥锁或分布式锁来保护访问数据库的请求,避免并发请求穿透到数据库。
- 针对热点数据,采用“缓存空对象”策略,即在缓存中设置一个空对象或标记,避免数据库查询。
- 使用布隆过滤器等数据结构来快速判断请求是否有效,避免无效请求穿透。
- 缓存穿透(Cache Miss):
- 概念: 缓存穿透是指请求的数据在缓存中不存在,每次请求都穿透到数据库,对数据库造成巨大压力。通常是因为请求参数异常或恶意攻击导致的。
- 解决方法:
- 使用布隆过滤器来预先过滤无效请求,不允许无效请求到达数据库。
- 在缓存中设置空值或默认值,以避免无效请求穿透到数据库。
- 针对热点数据,使用热点数据预热策略,提前加载热点数据到缓存。
17.IOC原理?AOP原理?
- IOC原理(控制反转):
- 概念: IOC是一种设计原则,它反转了传统的控制流程。在传统编程中,应用程序控制组件的生命周期和依赖关系。而在IOC中,控制反转容器(如Spring)负责管理组件的生命周期和依赖关系,应用程序只需要定义组件和它们之间的关系。
- 实现原理: IOC容器使用配置文件或注解来定义组件和它们之间的依赖关系。容器负责实例化和管理组件,以及解析依赖关系。应用程序通过容器来获取所需的组件,而不需要自己创建或管理它们。
- 优势: IOC提高了代码的可测试性、可维护性和可扩展性,降低了组件之间的耦合度,使应用程序更灵活和易于维护。
- AOP原理(面向切面编程):
- 概念: AOP是一种编程范式,它允许将横切关注点(如日志、事务、安全性)从应用程序的核心业务逻辑中分离出来。通过AOP,这些关注点可以被模块化地定义,并跨越不同的组件应用到应用程序中。
- 实现原理: AOP使用切面(Aspect)来定义横切关注点。切面可以包含通知(Advice)和切点(Pointcut)。通知定义了在何时、何地执行横切逻辑,切点定义了在何处执行横切逻辑。AOP容器会在运行时动态织入切面,将横切逻辑应用到应用程序中。
- 优势: AOP提高了代码的模块化性,避免了代码的重复性,使关注点分离更清晰,降低了代码的复杂度。
18.接口和抽象类区别?
19.static修饰的访问非static修饰出现的问题,以及为什么会出现这个问题,那么应该如何访问?
在Java中,当一个静态成员(包括静态字段和静态方法)尝试访问非静态成员(实例字段或实例方法)时,会出现以下问题:
- 不能直接访问非静态成员: 静态成员在类加载时就已经存在,而非静态成员需要通过类的实例来访问。因此,静态成员不能直接访问非静态成员,因为在没有实例的情况下,非静态成员没有定义。
- 无法使用this关键字: 静态成员中无法使用
this
关键字,因为this
表示当前对象的引用,而在静态上下文中,没有具体的对象实例。
解决方法:
- 静态成员可以通过创建类的实例来访问非静态成员。首先,实例化类,然后使用实例对象来访问非静态成员。
- 如果非静态成员不依赖于特定的实例状态,可以将它们标记为静态,这样静态成员就可以直接访问它们。
20.springMVC执行流程?
21.Redis集群,键过期策略?
Redis集群采用了主从复制的方式来实现数据的同步机制。在Redis集群中,有多个主节点和从节点,主节点负责处理写操作,而从节点用于复制主节点的数据,并负责读操作。以下是Redis集群的同步机制的关键点:
- 主从复制: Redis集群中的每个主节点都可以有多个从节点。主节点负责接收写操作,并将这些写操作的数据同步到从节点。
- 同步过程: 当主节点接收到写操作时,它会将写操作的数据变更记录(操作日志)发送给所有从节点。从节点会根据这些变更记录来复制主节点的数据。
- 全量同步: 新的从节点在初始加入集群时,需要进行一次全量同步。主节点会将完整的数据集发送给新的从节点,以确保从节点和主节点的数据一致性。
- 部分同步(增量同步): 一旦从节点完成全量同步,它将与主节点保持持续的连接,主节点会将增量的数据变更记录发送给从节点,以保持数据同步。
- 数据一致性: Redis集群的同步机制确保了数据的一致性。从节点会定期向主节点发送PING命令,以检测主节点是否在线。如果主节点宕机,从节点会选择另一个主节点作为新的主节点,并重新建立同步。
- 故障切换: 当主节点宕机或不可用时,Redis集群会自动进行故障切换,选择一个新的主节点,并将从节点重新同步到新的主节点上。
22.Redis跳表介绍
- 跳表基本原理: 跳表是一种多级索引数据结构,其中包含多层有序链表。每一层链表包含部分元素,且元素按照升序排列。底层链表包含所有元素,而上层链表只包含部分元素,从而在查找时可以通过多层索引快速定位目标元素。
- Redis中的跳表: Redis在内部使用跳表来实现有序集合(Sorted Set)。有序集合是一种键值对的数据结构,其中的值是唯一的,但有序集合中的每个元素都与一个分数(score)相关联,允许按照分数进行排序。
- 优点: 跳表在有序数据的存储和查询方面具有良好的性能。在Redis中,有序集合的基本操作(插入、删除、查找等)的时间复杂度通常是O(log(N)),其中N是元素的数量。
- 跳表与其他数据结构的比较: Redis中使用跳表来实现有序集合,而不是传统的红黑树或平衡树,这是因为跳表相对简单,易于维护,且有良好的平均性能。它适用于大部分有序数据的场景。
- 跳表的实现细节: Redis中的跳表是一种带有多级索引的数据结构。每个节点通常包含多个指向下一层链表中的节点的指针,从而允许快速跳跃。Redis还使用随机化的方法来生成索引层次,以确保平均查询性能。
23.压缩列表讲一下
- 基本原理: 压缩列表是一种紧凑的、线性存储结构,用于存储多个数据元素。每个数据元素可能是一个字节数组、整数或浮点数。压缩列表中的元素按照插入的顺序进行存储。
- 压缩机制: 压缩列表采用了一些特定的压缩机制来降低内存消耗。这包括相邻元素的存储,以及根据元素的类型采用不同的编码方式。例如,如果连续的多个元素是整数,它们可以被连续存储而不需要每个元素都占用额外的字节。
- 性能特点: 压缩列表的性能特点是读写操作的时间复杂度通常是O(1),因为元素的插入和删除操作都可以在常数时间内完成。此外,由于紧凑的存储方式,压缩列表在内存占用方面比传统的数据结构更有效。
- 适用场景: 压缩列表通常用于存储长度有限的列表、哈希表和有序集合。它在一些内部数据结构的实现中得到广泛应用,以提高Redis的性能和内存效率。
- 限制: 压缩列表的设计使得它适用于较小的数据集,当数据集较大时,Redis会自动将其转换为其他数据结构,如双向链表,以处理更大的数据量。
24.项目中的nginx如何使用的?如何代理?项目中他属于几级代理?
在一个项目中使用Nginx通常涉及到反向代理和负载均衡的配置,以提高性能、安全性和高可用性。Nginx通常作为Web服务器或代理服务器在项目中使用。
以下是关于项目中如何使用Nginx的一般步骤和配置方式:
安装和配置Nginx: 首先,需要在服务器上安装Nginx并进行基本的配置。配置文件通常位于
/etc/nginx/nginx.conf
或/etc/nginx/sites-available/
目录中。静态文件服务: Nginx可以用于直接提供静态文件,例如HTML、CSS、JavaScript和图像。这可以通过在Nginx配置文件中指定
location
块来实现。例如:nginxCopy code location /static/ { alias /path/to/static/files; }
反向代理: Nginx经常用作反向代理服务器,将客户端请求转发到后端应用服务器。这可以通过配置
proxy_pass
指令来实现。例如:nginxCopy code location /app/ { proxy_pass http://backend-server; }
这将把以
/app/
开头的请求代理到backend-server
。负载均衡: 如果项目中有多个后端服务器,Nginx可以用于负载均衡以分发请求到多个服务器。这可以通过配置
upstream
和proxy_pass
来实现。例如:nginxCopy code upstream backend { server backend-server1; server backend-server2; } location /app/ { proxy_pass http://backend; }
这将把请求分发到
backend-server1
和backend-server2
。SSL和安全性: 如果项目需要安全连接,Nginx可以配置SSL证书来支持HTTPS。这需要在Nginx配置文件中指定SSL证书和相关参数。
高可用性: 在一些项目中,Nginx可以部署为多个实例,实现高可用性。这通常涉及使用主备或多节点配置,并使用负载均衡器来管理它们。
Nginx可以作为一级代理或二级代理,具体层次取决于项目的架构和需求。如果Nginx直接代理客户端请求并将其转发到应用服务器,那么它是一级代理。如果Nginx代理请求到另一个Nginx服务器,然后由第二个Nginx服务器将请求转发给应用服务器,那么它是二级代理。代理层次可以根据项目的复杂性和需求进行设置。
25.双亲委派机制讲解,为什么这样做?什么时候打破?
双亲委派机制(Parent Delegation Model)是Java类加载器(ClassLoader)机制的一种实现方式,用于加载Java类。它基于层次化的类加载器结构,其核心思想是:当一个类加载器试图加载一个类时,它首先委派给其父类加载器,只有在父类加载器无法加载该类时,才由子类加载器尝试加载。这个机制有以下重要方面:
- 层次化结构: Java类加载器组成了一个树形的层次结构,根加载器(Bootstrap ClassLoader)是顶层的加载器,其他加载器依次向下排列。每个加载器都有一个父加载器,除了根加载器。
- 为什么这样做: 双亲委派机制的主要目的是确保类的一致性和安全性。通过层次化加载,防止同一个类被多个加载器重复加载,确保Java核心类库的一致性。它还有助于隔离不同应用程序或模块间的类,提高应用程序的安全性。
- 打破机制: 双亲委派机制不是严格强制执行的,可以通过以下方式打破:
- 在自定义类加载器中,覆盖
loadClass
方法,并不调用父加载器的loadClass
方法,从而绕过父加载器的委派。 - 使用
Thread.currentThread().setContextClassLoader(customClassLoader)
来设置线程的上下文类加载器,从而在多线程环境中改变类加载器的委派顺序。
- 在自定义类加载器中,覆盖
26.负载均衡策略?
- 轮询(Round Robin): 轮询策略按照顺序逐个分发请求到每个服务器,确保每个服务器都有机会处理请求。它是一种简单且公平的负载均衡策略,适用于服务器性能相近的情况。
- 加权轮询(Weighted Round Robin): 加权轮询策略允许为每个服务器分配不同的权重,以根据服务器性能的不同分配负载。性能较好的服务器可以分配更多的请求。
- 随机(Random): 随机策略以随机方式将请求分发给服务器。它可以在某些情况下提供良好的负载均衡,但可能不够稳定。
- 最小连接数(Least Connections): 最小连接数策略将请求发送到当前连接数最少的服务器。这有助于将请求发送到最闲的服务器,但需要维护连接数信息。
- IP哈希(IP Hash): IP哈希策略基于客户端的IP地址计算哈希值,并将请求发送到对应的服务器。这有助于确保同一客户端的请求总是被发送到同一服务器。
- 最少响应时间(Least Response Time): 最少响应时间策略将请求发送到最快响应的服务器。它需要监测服务器的响应时间,并动态调整请求分发。
- URL路径哈希: URL路径哈希策略根据请求的URL路径计算哈希值,并将请求发送到对应的服务器。这有助于确保同一URL的请求总是被发送到同一服务器。
- 一致性哈希(Consistent Hashing): 一致性哈希策略根据请求的哈希值将请求分发到哈希环上的一个服务器节点。这有助于减少请求重分布,当服务器节点变化时。
- 动态权重(Dynamic Weighting): 动态权重策略根据服务器的实时性能信息动态调整权重,以实现更智能的负载均衡。
- 自适应负载均衡: 自适应负载均衡策略使用自动学习算法,根据服务器的实际负载和性能动态调整请求分发策略。
27.spring生命周期,作用域?
Spring Bean生命周期:
Spring容器中的Bean有其生命周期,它包括以下关键阶段:
- 实例化(Instantiation): 这是Bean对象创建的阶段。在这个阶段,Spring容器根据配置文件或注解创建一个新的Bean实例。
- 属性赋值(Property Setting): 在Bean对象创建后,Spring容器将会设置Bean的属性值,通常是通过依赖注入实现的。
- 初始化(Initialization): 在属性赋值之后,Spring容器可以调用Bean的初始化方法(如果配置了的话)。初始化方法可以通过XML配置或Java注解指定。通常,初始化方法用于完成一些必要的准备工作。
- 使用(In Use): Bean被容器管理后,它可以被其他Bean或组件引用和使用。这是Bean的正常使用阶段。
- 销毁(Destruction): 在Bean不再被需要时,Spring容器可以调用Bean的销毁方法(如果配置了的话)。销毁方法通常用于清理资源或完成一些清理工作。
Spring Bean作用域:
Spring提供了不同的Bean作用域,以定义Bean的生命周期范围。以下是常见的Spring Bean作用域:
- Singleton(默认): 在Spring容器中只存在一个Bean实例,所有请求都返回同一个实例。这是Spring的默认作用域。
- Prototype: 每次请求都会创建一个新的Bean实例,每个实例都有独立的状态。适用于无状态的Bean。
- Request: 在Web应用中,每个HTTP请求都会创建一个新的Bean实例,该Bean仅在当前请求中可用。
- Session: 在Web应用中,每个HTTP会话(Session)都会创建一个新的Bean实例,该Bean在整个会话期间可用。
- Application(ServletContext): 在Web应用中,每个ServletContext只有一个Bean实例,该Bean在整个Web应用生命周期中可用。
- WebSocket: 用于WebSocket应用,每个WebSocket会话都会创建一个新的Bean实例。
- Custom Scope: 可以自定义Bean的作用域,以满足特定应用的需求。
28.spring与springboot对比
- Spring:
- Spring是一个开源的Java框架。 它提供了大量的功能和库,用于构建Java应用程序,尤其是企业级应用。
- 配置复杂。 Spring应用程序通常需要复杂的XML配置文件来定义Bean、依赖注入、AOP等。这需要开发人员显式配置很多东西。
- 需要手动配置和集成。 开发人员需要手动配置各种组件,并可能需要集成多个框架和库。
- 可以定制性强。 Spring框架提供了很高的灵活性,但也需要更多的配置和工作。
- Spring Boot:
- Spring Boot是Spring的子项目。 它是一个用于快速开发、配置和部署Spring应用程序的框架。
- 自动配置。 Spring Boot使用自动配置功能,它根据类路径和约定自动配置应用程序,减少了手动配置的需求。
- 快速开发。 Spring Boot旨在提供快速开发体验,让开发人员能够迅速搭建起一个可运行的应用程序。
- 集成了内嵌Web服务器。 Spring Boot集成了内嵌的Web服务器(如Tomcat、Jetty或Undertow),简化了Web应用程序的部署。
- 共同之处:
- 基于Spring框架。 Spring Boot构建在Spring框架之上,因此仍然可以使用Spring框架的所有功能。
- 依赖管理。 Spring Boot提供了依赖管理工具,简化了构建和管理项目依赖的过程。
29.线程池好处、原理、执行流程、参数介绍
30.TCP和UPD对比,各自使用场景
TCP(传输控制协议):
- 可靠性: TCP是一种可靠的协议,它确保数据的可靠传输,提供数据包的排序和重传机制,以确保数据的完整性。
- 连接导向: TCP是面向连接的协议,它在通信之前需要建立连接,然后在通信结束后释放连接。这确保了数据的可靠传输。
- 流控制: TCP使用流控制来调整发送速度,以防止发送方过载接收方。
- 适用场景: TCP适用于需要可靠数据传输的应用,如Web浏览、电子邮件、文件传输、远程登录等。它适用于那些不能容忍数据丢失或乱序的应用。
UDP(用户数据报协议):
- 不可靠性: UDP是一种不可靠的协议,它不提供数据包的排序、重传或流控制。数据可能会丢失或以乱序到达。
- 无连接: UDP是一种无连接的协议,不需要建立连接,通信双方之间直接发送和接收数据包。
- 低延迟: 由于没有连接建立和复杂的控制机制,UDP通常具有较低的延迟,适合实时应用。
- 适用场景: UDP适用于需要低延迟和可以容忍一些数据丢失的应用,如音频和视频流传输、在线游戏、实时通信、DNS查询等。
31.一次http流程,详细介绍各部分
HTTP请求过程:
- 建立TCP连接: 客户端(浏览器或应用程序)首先需要与服务器建立TCP连接。这是通过向服务器的IP地址和端口号发送一个TCP连接请求来完成的。通常,Web服务器的标准端口号是80。
- 发起HTTP请求: 一旦TCP连接建立,客户端可以发送HTTP请求。HTTP请求通常包括以下部分:
- 请求方法(Request Method): 定义请求的类型,如GET、POST、PUT、DELETE等。
- URL(Uniform Resource Locator): 指定请求的资源的地址。
- HTTP版本: 指定所使用的HTTP协议版本,如HTTP/1.1。
- 请求头(Request Headers): 包括附加的信息,如User-Agent(客户端标识)、Accept(可接受的数据类型)、Cookie等。
- 请求正文(Request Body): 用于POST请求等,传递数据给服务器。
- 服务器处理请求: 一旦服务器接收到HTTP请求,它将解析请求并处理它。这可能包括访问数据库、执行业务逻辑等。
- 服务器发送HTTP响应: 服务器会构建HTTP响应并将其发送回客户端。HTTP响应通常包括以下部分:
- HTTP状态码(Status Code): 定义请求的处理结果,如200(成功)、404(未找到)或500(服务器错误)。
- 响应头(Response Headers): 包括服务器信息、日期、响应数据的类型等。
- 响应正文(Response Body): 包括请求的实际数据,如HTML网页、图片、文本等。
- 客户端接收响应: 客户端接收服务器发送的HTTP响应。它会解析响应,检查状态码,然后处理响应数据。
- 关闭TCP连接: 一旦请求和响应完成,客户端和服务器会关闭TCP连接,释放资源。
HTTP响应过程:
HTTP响应的过程与HTTP请求类似,但反过来:
- 建立TCP连接: 客户端与服务器建立TCP连接。
- 发起HTTP请求: 客户端发送HTTP请求。
- 服务器处理请求: 服务器处理请求并构建HTTP响应。
- 服务器发送HTTP响应: 服务器发送HTTP响应给客户端。
- 客户端接收响应: 客户端接收并处理HTTP响应。
- 关闭TCP连接: 客户端和服务器关闭TCP连接。
32.mysql事务、隔离机制、每个隔离级别会出现什么问题,如何解决脏读、幻读等问题?
MySQL事务:
事务是一组SQL操作,它们要么全部成功执行,要么全部失败回滚。事务具有以下四个特性,通常称为ACID特性:
- 原子性(Atomicity):事务是不可分割的单元,要么全部成功,要么全部失败。
- 一致性(Consistency):事务将数据库从一个一致状态转移到另一个一致状态。
- 隔离性(Isolation):不同事务之间的操作相互隔离,互不干扰。
- 持久性(Durability):一旦事务提交,其结果将永久保存在数据库中。
隔离级别:
MySQL支持多种隔离级别,包括:
- 读未提交(Read Uncommitted):最低隔离级别,允许一个事务读取另一个事务未提交的数据,可能导致脏读、幻读和不可重复读。
- 读提交(Read Committed):允许一个事务只读取已提交的数据,解决了脏读问题,但仍可能出现幻读和不可重复读。
- 可重复读(Repeatable Read):允许一个事务读取另一个事务已提交的数据,解决了脏读和不可重复读问题,但仍可能出现幻读。
- 可串行化(Serializable):最高隔离级别,禁止多个事务并发执行,保证了完全的隔离性。
隔离级别导致的问题和解决方法:
- 脏读(Dirty Read): 一个事务读取另一个未提交的事务的数据。解决方法是使用"读提交"隔离级别。
- 幻读(Phantom Read): 一个事务在两次查询之间,另一个事务插入或删除了数据。解决方法是使用"可重复读"或"可串行化"隔离级别。
- 不可重复读(Non-Repeatable Read): 一个事务在两次查询之间,另一个事务修改或删除了数据。解决方法是使用"可重复读"或"可串行化"隔离级别。
- 丢失更新(Lost Update): 两个事务同时更新相同的数据,其中一个事务的更新被覆盖。解决方法可以使用锁或乐观锁机制。
33.HashMap默认加载因子,结合扩容机制
HashMap是Java中的一个常用数据结构,它基于哈希表实现。HashMap的默认加载因子是0.75。加载因子是一个用来衡量哈希表满程度的参数,它表示当哈希表中的元素个数达到总容量的多少比例时,会触发扩容操作。
HashMap的扩容机制如下:
- 初始化HashMap: 当创建一个HashMap实例时,它会初始化一个默认容量的哈希表,通常是16个桶,以及一个加载因子,默认是0.75。
- 添加元素: 当往HashMap中添加元素时,首先会根据元素的键(key)的哈希值计算出它应该存放在哪个桶中。如果该桶为空,直接将元素放入。如果该桶已经有元素,就会以链表或红黑树的形式将元素添加到桶中,保持桶内元素的顺序。
- 检查加载因子: 在每次添加元素后,HashMap会检查当前元素个数是否超过了容量乘以加载因子。如果超过了,就会触发扩容。
- 扩容: 当触发扩容时,HashMap会创建一个新的哈希表,容量是原来的两倍,并重新分配元素到新的桶中。这个操作需要将所有的元素重新计算哈希值,并放入新的桶中,所以扩容是一个开销较大的操作。
- 重新哈希: 元素的重新分配过程称为重新哈希,它确保元素在新表中的位置是新的哈希值决定的,而不是之前的位置。
34.hashtable底层实现
数组和链表结构: Hashtable
使用一个数组来存储元素,数组中的每个元素都是一个链表。这些链表用于解决哈希冲突,即多个键映射到同一个数组索引的情况。
同步: Hashtable
是线程安全的,它的方法都被同步,这意味着多个线程可以安全地访问和修改 Hashtable
实例。这是通过使用 synchronized
关键字来实现的。
扩容: 当 Hashtable
中的元素个数达到容量的75%时,它会自动进行扩容。扩容后,容量会翻倍,并重新计算每个元素的新位置,以确保哈希表的性能。
键和值的存储: Hashtable
使用键值对来存储数据,键和值都可以为 null
,但键和值都必须是对象类型,不能是基本数据类型。