广州兴业面经(已凉)带答案
面试官很好,感恩🥹我不会的他都会给我解答,我太菜了。更多面经请查看,这是第一篇,以后会陆续更新https://github.com/haandfeng/Mianjing
面试公司:广州兴业
面试岗位:全栈开发
面试问题:
ajax用什么框架
axios 是一个非常流行的 HTTP 请求库,用来执行 AJAX 请求。它支持 Promise,并且有更简洁的 API,比原生的 XMLHttpRequest 更容易使用。
js的箭头函数和普通函数有什么区别
this 的指向是箭头函数和普通函数之间的最大区别之一。==• 箭头函数的 this:箭头函数 不会创建自己的 this,它会 继承外部作用域的 this(即,箭头函数的 this 指向它被定义时的上下文中的 this,而不是调用时的 this)。==• 箭头函数 没有自己的 arguments 对象。它会继承外部函数的 arguments 对象• 箭头函数 不能作为构造函数• 箭头函数 中没有 super:
• 普通函数的 this:普通函数会根据调用时的上下文来决定 this 的值。通常,它指向调用该函数的对象。
js怎么判断一个变量的类型
typeof 操作符, instantof操作符
js == 和 === 的区别
== 是 抽象相等运算符,它会 进行类型转换,即在比较两个值之前,会尝试将它们转换成相同的类型,然后再进行比较。
=== 是 严格相等运算符,也称为 全等运算符。它 不会进行类型转换,即只有在两个值的类型和值都相等时,=== 才会返回 true。
== 出现隐式类型转换的可能问题是什么
当你期望严格比较值时,== 会进行隐式转换,导致类型和期望值不匹配。
- 比如 '1' == true 返回 true,但实际上它们是不同的类型。
- == 会将 null 和 undefined 认为是相等的,但 === 认为它们是不同的类型。
- 当使用 == 比较一个对象和基本类型时,JavaScript 会尝试将对象转换为原始值。如果对象无法转换为原始值,结果是 false。
- == 也会有一个特殊的行为:NaN 与任何值(包括 NaN 自身)比较时都不相等。
v-model的底层原理
==感觉好复杂==应为不做前端,不研究了v-model 的底层原理涉及:
• 数据绑定:通过 v-bind:value 将数据绑定到表单控件的属性上。
• 事件监听:通过事件(如 input)监听表单控件的变化,并通过 @input 事件将数据同步到 Vue 实例中。
• getter/setter:Vue 使用 getter/setter 来实现数据的响应式,并使得视图和数据之间保持同步。
SpringBootApplication注解是干嘛的,由什么注解合成的
==不是很理解==
在 Spring Boot 中,@SpringBootApplication 是一个非常重要的注解,它是 Spring Boot 应用的入口标志。它实际上是一个组合注解,结合了多个常用的注解,用于启动和配置 Spring Boot 应用程序。
@Configuration:• @SpringBootApplication 本质上包含了 @Configuration 注解,表示当前类是一个配置类,用于定义 Spring 配置。• 它允许你定义一些 Bean 配置和其他 Spring 组件,如数据库、事务等。
@EnableAutoConfiguration:• @SpringBootApplication 还包含了 @EnableAutoConfiguration 注解。这个注解告诉 Spring Boot 根据类路径中存在的库来自动配置应用程序的环境。• 例如,如果你在类路径中添加了 spring-boot-starter-data-jpa,Spring Boot 会自动配置 JPA 相关的 Bean(如 EntityManagerFactory 和 DataSource)。• 它避免了需要手动配置很多应用的细节。
@ComponentScan:• @SpringBootApplication 还包含了 @ComponentScan 注解,它让 Spring 自动扫描当前包及其子包中的所有组件、配置和服务,并将它们注册为 Spring Bean。• 这意味着所有在当前类及其子包中的类(带有 @Component、@Service、@Repository 等注解的类)都会被自动扫描和注册。
RequestMapping注解是干嘛的
@RequestMapping 可以用来将 HTTP 请求(如 GET、POST、PUT、DELETE)映射到具体的 Java 方法上。你可以在控制器类或者控制器方法上使用 @RequestMapping 注解,以定义请求的路径和处理该请求的方法。
@Controller public class MyController { @RequestMapping("/hello") public String sayHello() { return "hello"; // 返回视图名(页面) } }
@RequestMapping 可以处理不同的 HTTP 请求方法,如 GET、POST、PUT、DELETE 等。通过 method 属性指定请求方法。
@Controller public class MyController { // 处理 GET 请求 @RequestMapping(value = "/getExample", method = RequestMethod.GET) public String getExample() { return "This is a GET request!"; } // 处理 POST 请求 @RequestMapping(value = "/postExample", method = RequestMethod.POST) public String postExample() { return "This is a POST request!"; } }
简化版本:@GetMapping、@PostMapping 等
git 假设你现在正在开发了另外一个功能,但是现在线上它有报了个bug过来,怎么操作
先提交,再回滚
说你这个代码他没有办法提交,他如果不能没有办法提交到某个分支上的时候该怎么办?
先add到暂存区,再回滚
Mysql 索引失效的场景
- 当我们使用左或者左右模糊匹配的时候,也就是 like %xx 或者like %xx%这两种方式都会造成索引失效;
- 当我们在查询条件中对索引列使用函数,就会导致索引失效。
- 当我们在查询条件中对索引列进行表达式计算,也是无法走索引的。
- MySQL 在遇到字符串和数字比较的时候,会自动把字符串转数字,然后再进行比较。如果字符串是索引列,而条件语句中的输入参数是数字的话,那么索引列会发生隐式类型转换,由于隐式类型转换是通过 CAST 函数实现的,等同于对索引列使用了函数,所以就会导致索引失效。
- 联合索引要能正确使用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引的匹配,否则就会导致索引失效。
- 在 WHERE 子句中,如果在 OR 前的条件列是索引列,而在 OR后的条件列不是索引列,那么索引会失效。
为什么MySQL 采用 B+ 树作为索引
- B+树的非叶子节点不存放实际的记录数据,仅存放索引,因此数据量相同的情况下,相比存储即存索引又存记录的B树,B+树的非叶子节点可以存放更多的索引,因此B+树可以比B树更「矮胖」,查询底层节点的磁盘1/0次数会更少。
- B+树有大量的冗余节点(所有非叶子节点都是冗余索引),这些冗余索引让B+树在插入、删除的效率都更高,比如删除根节点的时候,不会像 B树那样会发生复杂的树的变化;
- B+ 树叶子节点之间用链表连接了起来,有利于范围查询,而B树要实现范围查询,因此只能通过树的遍历来完成范围查询,这会涉及多个节点的磁盘!/O 操作,范围查询效率不如 B+树
1, B+Tree vs B Tree
B+Tree 只在叶子节点存储数据,而B树的非叶子节点也要存储数据,所以B+Tree 的单个节点的数据量更小,在相同的磁盘1/0次数下,就能查询更多的节点。
另外,B+Tree 叶子节点采用的是双链表连接,适合 MySQL 中常见的基于范围的顺序查找,而B树无法做到这一点。
2、B+Tree vs 二叉树
对于有 N 个叶子节点的B+Tree,其搜索复杂度为 0(1ogdN),其中d表示节点允许的最大子节点个数为d个。
在实际的应用当中,d值是大于100的,这样就保证了,即使数据达到千万级别时,B+Tree 的高度依然维持在3~4 层左右,也就是说一次数据查询操作只需要做3~4次的磁盘1/0 操作就能查询到目标数据。
而二叉树的每个父节点的儿子节点个数只能是2个,意味着其搜索复杂度为 0(1ogN),这已经比
B+Tree 高出不少,因此二叉树检索到目标数据所经历的磁盘1/0次数要更多。
3、B+Tree vs Hash
Hash 在做等值查询的时候效率贼快,搜索复杂度为 0(1)。
但是 Hash 表不适合做范围查询,它更适合做等值的查询,这也是 B+Tree 索引要比 Hash 表索引有着更广泛的适用场景的原因。
哈希索引和B+树索引有什么区别
Hash 在做等值查询的时候效率贼快,搜索复杂度为 O(1)。但是 Hash 表不适合做范围查询,它更适合做等值的查询,这也是 B+Tree 索引要比 Hash 表索引有着更广泛的适用场景的原因。
引擎支持情况:B+ Tree: InnoDB MyISAM MemoryHash: InnoDB不支持,但是内存结构中有一个自适应hash索引, 只有Memory引擎支持
用过MongoDB吗
没有。
Redis常用的数据结构
String,Hash,Set,Zset,BitMap,Stream
100万条数据怎么优雅的丢redis,不想一次性
1. 使用管道(Pipeline)/分批次导入(分段式导入)
Redis 提供了 管道(Pipeline) 功能,使得可以一次发送多个命令而不等待每个命令的响应,从而显著提高批量写入的性能。你可以将数据分成多个批次(例如,每批 1000 条),然后利用管道一次性发送这些命令。
Jedis jedis = new Jedis("localhost"); Pipeline pipeline = jedis.pipelined(); int batchSize = 1000; for (int i = 0; i < 1000000; i++) { String key = "key" + i; String value = "value" + i; pipeline.set(key, value); // 使用管道批量写入 if (i % batchSize == 0) { // 批量提交 pipeline.sync(); } } // 最后一批数据提交 pipeline.sync(); jedis.close();
• 优点:通过管道一次性发送多个命令,可以大大减少网络延迟和 Redis 响应时间,提高写入性能。• 分批处理:每处理一定数量的数据就提交一次,避免 Redis 阻塞或内存溢出
2. 使用多线程/并发写入
将数据分为多个批次,使用多个线程并行地将这些数据写入 Redis。这种方式可以进一步提高数据写入的吞吐量,尤其是在多核 CPU 环境下,能充分利用多核优势。
public class RedisBatchWrite { private static final String REDIS_HOST = "localhost"; private static final int REDIS_PORT = 6379; private static final int BATCH_SIZE = 1000; // 每个批次的数据量 private static final int NUM_THREADS = 10; // 线程池大小 // 批量写入 Redis 的方法 public static void writeToRedis(List<String[]> batch) { try (Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT)) { Pipeline pipeline = jedis.pipelined(); // 使用管道进行批量操作 for (String[] entry : batch) { String key = entry[0]; String value = entry[1]; pipeline.set(key, value); // 向管道中添加 set 命令 } pipeline.sync(); // 执行所有批量操作 } } public static void main(String[] args) { // 创建固定大小的线程池 ExecutorService executorService = Executors.newFixedThreadPool(NUM_THREADS); // 生成 100 万条键值对数据 List<String[]> data = new ArrayList<>(); for (int i = 0; i < 1000000; i++) { data.add(new String[]{"key" + i, "value" + i}); } // 将数据分批次并行处理 for (int i = 0; i < data.size(); i += BATCH_SIZE) { List<String[]> batch = data.subList(i, Math.min(i + BATCH_SIZE, data.size())); executorService.submit(() -> writeToRedis(batch)); // 提交任务到线程池 } // 关闭线程池 executorService.shutdown(); } }
• 优点:并行处理多个批次的写入操作,提高了 Redis 的写入吞吐量。• 注意:并发写入时需要注意 Redis 的负载和网络带宽,不要过度并发导致 Redis 或网络瓶颈。
3. 使用 Redis Sorted Set 或 List 进行批量处理如果数据有一定的排序要求,可以使用 Redis 的 Sorted Set(有序集合) 或 List 数据结构,在批量处理时通过这些结构存储数据。
Jedis jedis = new Jedis("localhost"); int batchSize = 1000; for (int i = 0; i < 1000000; i++) { String key = "key" + i; String value = "value" + i; jedis.lpush("myList", key + ":" + value); // 向 List 中推送数据 if (i % batchSize == 0) { // 定期提交 jedis.save(); } } jedis.close();
5. 使用 Redis 批量导入工具在某些场景下,如果数据非常大,可以考虑使用专门的工具进行数据导入。例如,使用 Redis 提供的 redis-bulk 工具或者其他支持批量导入的第三方工具。
单向链表和双向链表的区别
• 单向链表(Singly Linked List):每个节点包含 数据 和 指向下一个节点的指针(next)。没那么占用内存• 双向链表(Doubly Linked List):每个节点包含 数据、指向下一个节点的指针(next),以及 指向上一个节点的指针(prev)。比较占用内存
频繁插入用什么数据结构,单or双
我个人认为差不多,没什么区别,删除节点要选择双可以直接通过 prev 指针访问目标节点的前一个节点,删除更简单。但是插入无法直接访问前一个节点,还是需要遍历,求大神解答
解释一下悲观锁和乐观锁
1. 悲观锁(Pessimistic Lock)悲观锁是基于“假设最坏情况”的思想。它假设在多线程环境下,数据的并发访问会发生冲突,因此,在每次操作数据之前都会加锁,确保其他线程不能同时修改同一份数据。
2. 乐观锁(Optimistic Lock)乐观锁则是基于“假设并发访问不会发生冲突”的思想。它认为在多线程环境下,大多数情况下不会发生数据冲突,因此它不会在每次操作数据前加锁,而是在操作完成后进行检查。如果检查到数据在操作过程中被修改过,则操作失败;如果没有修改过,则提交操作。
乐观锁怎么解决超卖
修改代码方案一、
VoucherOrderServiceImpl 在扣减库存时,改为:
boolean success = seckillVoucherService.update() .setSql("stock= stock -1") //set stock = stock -1 .eq("voucher_id", voucherId).eq("stock",voucher.getStock()).update(); //where id = ? and stock = ?
以上逻辑的核心含义是:只要我扣减库存时的库存和之前我查询到的库存是一样的,就意味着没有人在中间修改过库存,那么此时就是安全的,但是以上这种方式通过测试发现会有很多失败的情况,失败的原因在于:在使用乐观锁过程中假设100个线程同时都拿到了100的库存,然后大家一起去进行扣减,但是100个人中只有1个人能扣减成功,其他的人在处理时,他们在扣减时,库存已经被修改过了,所以此时其他线程都会失败
修改代码方案二、
之前的方式要修改前后都保持一致,但是这样我们分析过,成功的概率太低,所以我们的乐观锁需要变一下,改成stock大于0 即可
boolean success = seckillVoucherService.update() .setSql("stock= stock -1") .eq("voucher_id", voucherId).update().gt("stock",0); //where id = ? and stock > 0
怎么解决 一人一单的问题
用redis的set集合,存用户id问了gpt只列了两个,感觉其他的没那么重要
redis上分布式锁和数据库锁的区别
1. 锁的粒度和作用范围
• 数据库锁:
• 数据库锁通常是在单一数据库实例或数据库内部进行的锁,主要针对同一数据库的多个并发操作进行控制。
• 数据库锁通常是应用于数据库表中的行级(行锁)或表级(表锁)锁。
• 数据库锁的作用范围限于一个数据库实例内,无法跨数据库或跨服务器。
• Redis 分布式锁:
• Redis 分布式锁是跨多个应用实例和跨多个机器的分布式锁,通常用于需要跨多个服务实例共享的资源控制。
• Redis 锁使用 Redis 提供的 SETNX 或者 Redisson 等工具来实现,可以有效地在多个系统节点间同步锁状态。
2. 实现方式
• 数据库锁:
• 数据库锁通常使用数据库管理系统(如 MySQL、PostgreSQL)的锁机制,常见的方式有:
• 行锁(Row Locking):通过 SELECT FOR UPDATE 语句或 UPDATE 操作,锁住特定行,防止其他事务修改该行数据。
• 表锁(Table Locking):通过 LOCK TABLES 语句,可以锁住整张表,防止其他操作对表进行修改。
• 数据库锁会阻塞其他请求,直到锁被释放,通常适合在单个数据库实例内部进行数据同步。
• Redis 分布式锁:
• Redis 分布式锁的实现一般基于 SETNX(SET if Not eXists)命令或使用专门的 Redis 分布式锁工具库(如 Redisson)。
• SETNX 是 Redis 提供的一个命令,如果指定的 key 不存在,就会设置该 key 的值并返回 OK,否则返回 0。利用这一特性可以实现基本的锁机制。
• Redis 分布式锁还可以使用 超时机制,如使用 EXPIRE 设置锁的过期时间,防止因程序异常未能释放锁而导致的死锁。
#软件开发笔面经##牛客创作赏金赛#