练习题

数组和链表的区别

数组 (Array)

特点

  1. 连续内存分配:数组中的元素存储在连续的内存地址中。
  2. 固定大小:数组的大小在声明时确定,无法动态改变。
  3. 随机访问:通过索引可以在O(1)时间内直接访问任何元素。

优点

  1. 快速访问:由于内存地址连续,可以通过索引快速访问任意元素。
  2. 空间利用率高:没有额外的指针存储开销。

缺点

  1. 固定大小:无法动态调整大小,可能会导致内存浪费或不足。
  2. 插入删除效率低:在数组中插入或删除元素需要移动其他元素,时间复杂度为O(n)。

链表 (Linked List)

链表是一种由节点组成的线性数据结构,每个节点包含数据和一个或多个指向其他节点的指针。

特点

  1. 非连续内存分配:节点存储在不连续的内存地址中,通过指针连接。
  2. 动态大小:可以根据需要动态调整大小。
  3. 多种类型:包括单向链表(Singly Linked List)、双向链表(Doubly Linked List)、循环链表(Circular Linked List)等。

优点

  1. 动态调整:可以根据需要动态增加或删除节点,内存利用更灵活。
  2. 插入删除效率高:在链表中插入或删除元素只需调整指针,时间复杂度为O(1)(在已知位置的情况下)。

缺点

  1. 访问速度慢:需要从头开始逐个遍历,查找某个元素的时间复杂度为O(n)。
  2. 空间开销大:每个节点需要额外存储指针,占用更多内存。

总结

  • 数组适用于需要频繁随机访问的场景,如实现栈、队列等简单的数据结构。
  • 链表适用于需要频繁插入、删除操作的场景,如实现动态数据结构(如链式栈、链式队列)和一些高级数据结构(如链表表示的哈希表、图的邻接表等)。

Get和Post的区别

在Web开发中,GET和POST是两种常用的HTTP请求方法,用于客户端和服务器之间的数据传输。它们在用途、传输数据的方式、安全性等方面都有显著的区别。

GET 请求

特点

  1. 请求方式:通过URL传递数据,数据附加在URL的查询字符串中(即?后面的部分)。
  2. 数据长度:由于URL长度限制,GET请求的数据量有限(不同浏览器和服务器对URL长度的限制有所不同,通常在2000字符左右)。
  3. 缓存:GET请求可以被浏览器缓存,适合用来获取不变的数据。
  4. 幂等性:多次发出同一个GET请求的效果相同,不会对服务器资源产生副作用。
  5. 可见性:数据在URL中明文显示,用户和任何中间人都能看到。

用途

  • 获取资源或数据,例如访问网页、获取图像等。
  • 不涉及敏感数据的请求,例如搜索查询。

POST 请求

特点

  1. 请求方式:通过HTTP请求的主体(body)传递数据,数据不会显示在URL中。
  2. 数据长度:理论上没有数据长度限制,能够传输大量数据。
  3. 缓存:POST请求不会被浏览器缓存,适合用来提交需要处理的数据。
  4. 非幂等性:多次发出同一个POST请求可能会产生不同的结果(例如提交表单多次可能会创建多个记录)。
  5. 安全性:数据在URL中不可见,但仍需通过HTTPS协议确保数据传输的安全性。

用途

  • 提交表单数据,例如用户注册、登录等。
  • 上传文件。
  • 发送涉及敏感信息的请求。

总结

特性 GET POST
数据传输方式 URL查询字符串 HTTP请求主体
数据长度 有限 理论上无限
安全性 数据在URL中明文显示 数据在主体中,较为安全
缓存 可以缓存 不缓存
幂等性 幂等(无副作用) 非幂等(可能有副作用)
用途 获取资源,不修改服务器状态 提交数据,可能修改服务器状态

选择GET或POST取决于具体的应用场景和需求。在需要获取资源或进行简单查询时使用GET,而在需要提交数据或涉及敏感信息时使用POST。

分布式session怎么实现

在分布式系统中,保持用户会话状态(session)是一项挑战,因为用户的请求可能被路由到不同的服务器。为了实现分布式session管理,可以采用多种方法,每种方法都有其优缺点。以下是几种常见的实现方法:

1. 基于数据库的Session存储

将Session数据存储在共享的数据库中,所有服务器都可以访问和更新这些数据。

优点

  • 简单直接,易于实现。
  • 不依赖于特定的服务器,可以水平扩展。

缺点

  • 数据库成为瓶颈和单点故障。
  • 需要考虑数据库的读写性能和扩展性。

实现步骤

  1. 选择一个数据库(如MySQL、PostgreSQL)。
  2. 创建一个表来存储Session数据,表结构应包括Session ID、用户数据和过期时间。
  3. 在应用服务器中,实现Session读写操作,与数据库进行交互。

2. 基于缓存的Session存储

使用分布式缓存系统(如Redis、Memcached)来存储Session数据。

优点

  • 高性能,低延迟。
  • 支持数据的自动过期和淘汰策略。
  • 易于扩展,支持分布式部署。

缺点

  • 需要额外的缓存系统配置和管理。
  • 数据可能会丢失(特别是在使用非持久化的缓存系统时)。

实现步骤

  1. 部署一个分布式缓存系统(如Redis集群)。
  2. 将Session数据存储在缓存中,键为Session ID,值为用户数据。
  3. 在应用服务器中,实现Session读写操作,与缓存系统进行交互。

3. 基于Cookie的Session管理

将Session数据存储在客户端的Cookie中,服务器通过Cookie识别用户会话。

优点

  • 无需服务器端存储,完全去中心化。
  • 服务器无状态,易于扩展。

缺点

  • 安全性问题,敏感数据在客户端存储,容易被窃取或篡改。
  • Cookie大小受限(一般4KB),不适合存储大量数据。

实现步骤

  1. 将Session数据加密并存储在Cookie中,确保数据安全。
  2. 在每次请求时,解析并验证Cookie,提取Session数据。

4. 粘性会话(Session Affinity)

使用负载均衡器,将同一用户的所有请求都路由到同一台服务器。

优点

  • 无需额外的存储和管理Session数据。
  • 简单易行,适合某些特定场景。

缺点

  • 负载均衡的灵活性降低,某些服务器可能负载过重。
  • 如果服务器宕机,会话数据会丢失。

实现步骤

  1. 配置负载均衡器(如Nginx、HAProxy),启用Session Affinity。
  2. 确保用户的请求被路由到同一台服务器。

5. 使用Session管理中间件

使用专门的Session管理中间件或框架,如Spring Session。

优点

  • 提供了高层次的抽象,简化了开发。
  • 支持多种存储方式(如Redis、JDBC等)。

缺点

  • 需要学习和使用特定的框架。
  • 某些复杂场景下可能需要自定义配置。

实现步骤

  1. 引入Session管理中间件(如Spring Session)。
  2. 配置中间件使用的存储方式(如Redis)。
  3. 在应用中使用中间件提供的Session管理API。

选择合适的方案

选择哪种方案取决于具体的应用需求和环境。例如:

  • 对性能要求高且能容忍短暂数据丢失的应用,可以选择基于缓存的Session存储。
  • 对安全性和数据完整性要求高的应用,可以选择基于数据库的Session存储。
  • 对于简单、无状态的服务,可以考虑基于Cookie的Session管理。

综合考虑性能、安全性、实现复杂度和扩展性,选择最适合的分布式Session管理方案。

用消息队列主要做什么

消息队列是一种用于在分布式系统中实现异步通信、解耦、负载均衡和可靠消息传递的关键技术。消息队列系统允许不同的服务或组件之间以异步方式传递消息,从而实现更灵活和可靠的系统架构。以下是使用消息队列的主要用途:

1. 异步处理

消息队列允许将任务或消息异步处理,使系统能够快速响应用户请求而不必等待任务完成。例如,在一个电商系统中,用户下单后立即返回确认信息,而订单处理、库存更新等耗时任务可以异步处理。

2. 解耦

消息队列可以帮助不同系统或组件之间解耦,使它们能够独立开发、部署和扩展。通过消息队列,发送消息的组件和接收消息的组件不需要直接交互,只需通过队列传递消息。

3. 负载均衡

消息队列能够在多个消费者之间分配工作负载,实现负载均衡。例如,在一个日志处理系统中,多个消费者可以从队列中读取日志消息并进行处理,从而均衡负载,提高系统的处理能力。

4. 可靠消息传递

消息队列系统通常提供消息持久化、确认机制和重试机制,以确保消息的可靠传递。即使在某些组件暂时不可用的情况下,消息也不会丢失。

5. 流量削峰填谷

消息队列能够缓冲突发的流量,平滑流量峰值,防止系统过载。高峰期产生的消息可以先存储在队列中,然后逐步处理,从而保护后端系统的稳定性。

6. 事件驱动架构

消息队列支持事件驱动架构,系统可以根据事件触发特定的操作。例如,在用户注册后,可以通过消息队列触发发送欢迎邮件、统计用户数据等后续操作。

7. 分布式事务

在分布式系统中,消息队列可以用于实现分布式事务,保证数据的一致性。例如,使用消息队列配合事务协调器(如Saga模式或TCC模式)来处理跨服务的事务。

常见的消息队列系统

  1. RabbitMQ:基于AMQP协议,提供丰富的特性,如路由、负载均衡和可靠性保障。
  2. Apache Kafka:高吞吐量、分布式消息系统,适用于处理大量实时数据流。
  3. ActiveMQ:支持多种协议和持久化方式,适用于企业级消息传递。
  4. Amazon SQS:AWS提供的全托管消息队列服务,易于扩展和管理。
  5. Redis:通过List数据结构实现简单的消息队列功能,适用于轻量级消息队列场景。

使用场景示例

  1. 订单处理系统

    • 用户下单后,将订单信息放入消息队列。
    • 后端订单处理系统异步处理订单信息,更新库存、生成发票、安排发货等。
  2. 日志和监控系统

    • 各种应用程序将日志信息发送到消息队列。
    • 日志处理系统从队列中读取日志信息,进行存储、分析和报警。
  3. 邮件通知系统

    • 应用程序将邮件发送请求放入消息队列。
    • 独立的邮件服务从队列中读取请求并发送邮件。

通过消息队列,系统可以实现更高的扩展性、可靠性和灵活性,能够更好地应对复杂和多变的业务需求。

使用Spring的时候,你在什么情况下会使用事务

在使用Spring框架进行开发时,事务管理是确保数据一致性和完整性的重要手段。事务(Transaction)是一组操作的集合,这些操作要么全部成功,要么全部失败。使用事务可以避免数据在处理过程中的不一致情况。以下是一些常见的使用事务的情况和场景:

1. 数据库操作的一致性

在涉及多个数据库操作的场景下,如果这些操作必须作为一个整体成功或失败,那么需要使用事务。例如,在银行转账过程中,需要确保从一个账户扣款和向另一个账户存款这两个操作要么都成功,要么都失败。

1
2
3
4
5
@Transactional
public void transferMoney(String fromAccount, String toAccount, double amount) {
accountRepository.debit(fromAccount, amount);
accountRepository.credit(toAccount, amount);
}

2. 防止部分成功的更新

在执行多步更新操作时,如果中途出现错误,需要回滚之前所有的更新操作。例如,在电子商务应用中,处理订单时需要更新库存、订单状态和支付信息,这些操作需要作为一个事务整体来执行。

1
2
3
4
5
6
@Transactional
public void placeOrder(Order order) {
inventoryService.updateInventory(order);
paymentService.processPayment(order);
orderRepository.save(order);
}

3. 跨多个方法的事务管理

如果多个方法需要在同一个事务中执行,可以将事务管理放在调用这些方法的公共方法上。例如,在处理复杂业务逻辑时,需要调用多个服务,每个服务都包含一些数据库操作。

1
2
3
4
5
6
@Transactional
public void processComplexBusinessLogic(BusinessData data) {
serviceA.performStepA(data);
serviceB.performStepB(data);
serviceC.performStepC(data);
}

4. 确保数据完整性和一致性

在涉及多个表或多个数据库的操作时,使用事务可以确保数据的一致性和完整性。例如,在订单处理系统中,可能需要同时更新订单表、库存表和客户表,确保在任何一步失败时回滚所有操作。

1
2
3
4
5
@Transactional
public void updateOrderAndInventory(Order order, Inventory inventory) {
orderRepository.update(order);
inventoryRepository.update(inventory);
}

5. 处理并发问题

使用事务可以帮助处理并发问题,确保多个并发操作不会导致数据不一致。例如,在高并发环境下,确保两个并发的库存扣减操作不会导致库存不足的情况。

1
2
3
4
5
6
7
8
9
@Transactional
public void updateInventory(String productId, int quantity) {
Inventory inventory = inventoryRepository.findByProductId(productId);
if (inventory.getQuantity() < quantity) {
throw new InsufficientInventoryException();
}
inventory.setQuantity(inventory.getQuantity() - quantity);
inventoryRepository.save(inventory);
}

6. 声明式事务管理

Spring通过@Transactional注解提供了声明式事务管理,简化了事务的使用。可以在类或方法上使用@Transactional注解,Spring会自动管理事务的开始、提交和回滚。

1
2
3
4
5
6
7
8
@Service
public class OrderService {

@Transactional
public void createOrder(Order order) {
// 事务性操作
}
}

7. 自定义事务属性

Spring的@Transactional注解允许自定义事务属性,例如传播行为(propagation)、隔离级别(isolation)、超时时间(timeout)和回滚规则(rollbackFor)。

1
2
3
4
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, timeout = 30, rollbackFor = Exception.class)
public void performTransactionalOperation() {
// 事务性操作
}

总结

在Spring中使用事务时,主要是为了确保数据的一致性和完整性,防止部分操作成功导致数据不一致,以及处理并发问题。通过合理使用事务,可以提高系统的可靠性和数据的准确性。在设计事务时,还需要考虑性能开销、事务的粒度以及事务传播和隔离级别等因素。

说说数据库的索引底层逻辑原理

数据库索引是一种数据结构,用于提高数据库查询速度。索引的底层逻辑和原理涉及多种数据结构和算法,常见的索引类型包括B树、B+树和哈希表等。以下是关于数据库索引的主要类型及其底层逻辑原理的详细解释:

1. B树和B+树索引

B树和B+树是关系型数据库中最常用的索引结构,特别适用于范围查询和顺序访问。

B树

B树是一种自平衡的多路搜索树,每个节点可以有多个子节点。B树的特点包括:

  • 平衡性:B树保持平衡,每个叶子节点到根节点的距离相同。
  • 多路性:每个节点包含多个关键字和子节点指针,可以减少树的高度,从而减少查询时的I/O操作。

B树的结构

  • 内部节点:存储关键字和子节点指针。
  • 叶子节点:存储实际数据或指向实际数据的指针。

B+树

B+树是B树的变种,具有以下特点:

  • 所有实际数据都存储在叶子节点:内部节点只存储关键字和子节点指针,不存储实际数据。
  • 叶子节点链表:所有叶子节点形成一个双向链表,便于顺序扫描和范围查询。
  • 更高的扇出:由于内部节点只存储关键字和指针,B+树的节点扇出更高,树的高度更低。

B+树的结构

  • 内部节点:存储关键字和子节点指针。
  • 叶子节点:存储实际数据或指向实际数据的指针,并通过链表连接。

优点

  • 支持高效的范围查询和顺序访问。
  • 平衡性保证了稳定的查询和更新性能。

2. 哈希索引

哈希索引通过哈希函数将键值映射到哈希表中的位置。哈希索引的特点包括:

  • 哈希函数:将键值转换为哈希值。
  • 哈希表:哈希值指向实际数据的位置。

哈希索引的结构

  • 哈希表:每个哈希槽存储一个链表,链表中的每个节点存储实际数据。

优点

  • 查找效率高,适用于精确匹配查询。
  • 插入和删除操作速度快。

缺点

  • 不支持范围查询。
  • 哈希冲突可能导致性能下降。

3. 索引的工作原理

创建索引

  • 数据库根据索引类型(如B+树或哈希表)创建索引数据结构。
  • 将表中的数据按照索引规则插入到索引数据结构中。

查询操作

  • 根据查询条件,数据库利用索引结构快速定位到所需数据。
  • 对于B+树索引,数据库沿树路径遍历到叶子节点,找到目标数据。
  • 对于哈希索引,数据库通过哈希函数计算哈希值,定位到哈希表中的位置。

更新操作

  • 插入:数据库在插入数据时,同时更新索引结构,将新数据添加到适当的位置。
  • 删除:数据库在删除数据时,同时更新索引结构,移除对应的索引条目。
  • 修改:数据库在修改数据时,可能需要先删除旧索引条目,再插入新索引条目。

4. 索引的设计和选择

索引选择

  • 查询频率:高频查询字段适合建立索引。
  • 查询类型:精确匹配查询适合哈希索引,范围查询适合B+树索引。
  • 数据分布:选择合适的索引结构和字段组合,避免索引失效或性能下降。

索引优化

  • 复合索引:对于多字段查询,可以建立复合索引,提高查询效率。
  • 覆盖索引:在索引中包含查询所需的所有字段,避免回表操作。
  • 索引分区:对于大表,可以使用索引分区技术,提高查询和维护效率。

总结

数据库索引通过特殊的数据结构(如B+树和哈希表)来提高查询速度。B+树适用于范围查询和顺序访问,哈希索引适用于精确匹配查询。选择和设计索引时,需要根据具体的查询需求、数据特性和性能要求,综合考虑索引的类型和结构。

1. StringStringBufferStringBuilder

String

  • 不可变String 对象一旦创建就不能修改。
  • 线程安全:由于其不可变性,String 本质上是线程安全的。
  • 性能:每次对字符串进行操作时都会创建新的 String 对象,会导致性能开销。
  • 适用场景:适用于少量字符串操作或不涉及字符串内容修改的场景。

StringBuffer

  • 可变StringBuffer 是可变的,支持修改。
  • 线程安全StringBuffer 是线程安全的,内部使用 synchronized 关键字保证线程安全。
  • 性能:由于线程安全机制,相对于 StringBuilder 性能略低。
  • 适用场景:适用于多线程环境下需要频繁修改字符串的场景。

StringBuilder

  • 可变StringBuilder 也是可变的。
  • 非线程安全StringBuilder 没有内部的同步机制,因此不是线程安全的。
  • 性能:相对于 StringBuffer 性能更高,因为没有同步开销。
  • 适用场景:适用于单线程环境下需要频繁修改字符串的场景。

2. Java 集合类和 ArrayListLinkedList 区别与应用

Java 集合类

  • List:如 ArrayListLinkedList
  • Set:如 HashSetTreeSet
  • Map:如 HashMapTreeMap
  • Queue:如 PriorityQueueLinkedList
  • Stack:如 Stack(较老的类,现在推荐使用 Deque 实现栈)

ArrayListLinkedList

  • ArrayList

    • 底层实现:动态数组。
    • 访问速度:随机访问快(O(1)),因为基于索引。
    • 插入删除速度:在末尾插入或删除速度快(O(1)),中间插入或删除速度慢(O(n))。
    • 空间浪费:在扩容时可能浪费一些空间。
    • 应用场景:适用于频繁读取数据的场景。
  • LinkedList

    • 底层实现:双向链表。
    • 访问速度:随机访问慢(O(n))。
    • 插入删除速度:插入删除速度快(O(1)),不需要移动大量元素。
    • 空间浪费:每个元素需要额外的空间存储指针。
    • 应用场景:适用于频繁插入删除数据的场景。

3. HashMap 的应用及线程安全性

  • 应用HashMap 主要用于根据键快速查找对应的值,常用于缓存、数据存储等场景。
  • 线程安全性HashMap 不是线程安全的。在多线程环境中,可以使用 ConcurrentHashMap 替代。

4. Redis 为什么快

  • 内存存储:所有数据都存储在内存中,读取速度快。
  • 单线程模型:避免了多线程切换和竞争带来的开销。
  • 数据结构:优化了多种数据结构,如字符串、哈希、列表、集合、有序集合。
  • I/O 多路复用:使用了 epoll 等高效的 I/O 多路复用机制。

5. Redis 的基本数据类型

  • 字符串(String)
  • 哈希(Hash)
  • 列表(List)
  • 集合(Set)
  • 有序集合(Sorted Set,ZSet)

6. 缓存击穿、穿透、雪崩

  • 缓存击穿:指某个热点数据在缓存中失效,导致大量请求直接访问数据库。

    • 解决方案:使用互斥锁或设置较长的过期时间。
  • 缓存穿透:指查询一个不存在的数据,由于缓存中没有这个数据,会直接查询数据库,且数据库也没有,导致每次请求都打到数据库。

    • 解决方案:对查询结果为空的数据也进行缓存。
  • 缓存雪崩:指缓存中大量数据在同一时间失效,导致大量请求直接访问数据库。

    • 解决方案:设置不同的过期时间,或采用加锁机制。

7. MySQL 的索引

  • 索引类型:包括主键索引、唯一索引、普通索引、全文索引等。
  • 数据结构:常用的有 B+ 树索引、哈希索引等。

8. 最左匹配原则

  • 最左匹配:MySQL 索引会根据查询条件从最左边的字段开始匹配,直到遇到范围查询或不匹配的字段为止。

9. Spring 事务及事务失效原因

  • Spring 事务:通过 @Transactional 注解声明事务,Spring 负责事务的开始、提交和回滚。
  • 事务失效原因
    • 方法不是 public@Transactional 只能应用于 public 方法。
    • 方法内部调用:同类中方法互相调用不会触发事务。
    • 事务传播行为配置不当:如传播行为设置错误。
    • 事务管理器配置错误:事务管理器没有正确配置。

Spring 练习题

  1. Spring提供了众多容器类,最常用的有BeanFactory和ApplicationContext。

  2. 在Spring MVC中实现上传功能,主要依赖MultipartHttpServletRequest从读取请求中的文件,然后对读取到的MultipartFile类型进行处理。

  3. Spring MVC拦截器包含三个方法:preHandle()、postHandle()、afterCompletion()。

  4. :默认情况下,Bean在Spring容器中是单例的。默认作用范围singleton

  5. SpringMVC

    五大核心组件

    1.DispatcherServlet  请求入口

    2.HandlerMapping   请求派发,负责请求和控制器建立一一对应的关系

    3.Controller      处理器

    4.ModelAndView    封装模型信息和视图信息

    5.ViewResolver    视图处理器,定位页面

  6. 用于从URL中提取参数的注解是@PathVariable。

  7. JDK动态代理,是Java提供的动态代理技术,可以在运行时创建接口的代理实例。Spring AOP默认采用这种方式,在接口的代理实例中织入代码。CGLib动态代理,采用底层的字节码技术,在运行时创建子类代理的实例。当目标对象不存在接口时,Spring AOP就会采用这种方式,在子类实例中织入代码。

  8. @Autowired是Spring提供的注解,@Resource是JDK提供的注解。它们的区别是,@Autowired只能按类型注入,@Resource默认按名称注入,也支持按类型注入。