大家好,我是二哥呀。
贝壳找房也开奖了,星球里有球友拿到了大 SP,让他开始犹豫了起来。
贝壳算是房地产行业方面的 TOP,业务涵盖二手房、新房交易以及家装家居,百度百科显示:
2024年8月12日,贝壳发布2024年第二季度财务业绩,报告期内,净利润为26.9亿元,同比增长13.9%。
我也统计了一波 25 届秋招的开奖信息,放在了 Java 面试指南中,同步给大家参考一下。
同学 1,25k*16,Java 岗,没透露学校,说爱了,还挺满意的。 同学 2,总包 35 万,wlb 部门,学习没透露,说对贝壳的工作氛围很感兴趣 同学 3,东南大学,总包 37,开发语言是 Go 同学 4,本科 92(没说 9 还是 2),开了 23*16,后端开发岗
总体来说,比较符合贝壳的薪资定位,去年有硕士 211 的同学反馈说只给 18k 的侮辱价,今年算是比较大方的情况了,尤其是贝壳在业界竟然还有“WLB”的说法。
接下来,我们就以Java 面试指南中收录的同学 1 贝壳找房后端技术一面为例来看看,贝壳找房的面试官水准到底咋样。
1、二哥的 Linux 速查备忘手册.pdf 下载 2、三分恶面渣逆袭在线版:https://javabetter.cn/sidebar/sanfene/nixi.html
同学 1 贝壳找房后端技术一面
简单介绍一下项目
技术派是一个前后端分离的,面向互联网开发者的技术内容分享与交流平台,包括前端 PC 和管理后台。我们通过文章、教程、AI 助手等产品和服务,旨在打造一个激发开发者创作灵感,传播优质技术内容,陪伴开发者快速成长的技术社区。作为核心研发人员,我主要负责登录认证、消息通知、文章教程、AI 助手,以及管理后台的开发工作。
Spring用了什么设计模式
Spring 框架中用了蛮多设计模式的:
①、比如说工厂模式用于 BeanFactory 和 ApplicationContext,实现 Bean 的创建和管理。
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyBean myBean = context.getBean(MyBean.class);
②、比如说单例模式,这样可以保证 Bean 的唯一性,减少系统开销。
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService1 = context.getBean(MyService.class);
MyService myService2 = context.getBean(MyService.class);
// This will print "true" because both references point to the same instance
System.out.println(myService1 == myService2);
③、比如说 AOP 使用了代理模式来实现横切关注点(如事务管理、日志记录、权限控制等)。
@Transactional
public void myTransactionalMethod() {
// 方法实现
}
bean生命周期
Bean 的生命周期大致分为五个阶段:
实例化:Spring 首先使用构造方法或者工厂方法创建一个 Bean 的实例。在这个阶段,Bean 只是一个空的 Java 对象,还未设置任何属性。 属性赋值:Spring 将配置文件中的属性值或依赖的 Bean 注入到该 Bean 中。这个过程称为依赖注入,确保 Bean 所需的所有依赖都被注入。 初始化:Spring 调用 afterPropertiesSet 方法,或通过配置文件指定的 init-method 方法,完成初始化。 使用中:Bean 准备好可以使用了。 销毁:在容器关闭时,Spring 会调用 destroy 方法或 destroy-method 方法,完成 Bean 的清理工作。
bean是单例还是多例的,具体怎么修改
在 Spring 中,Bean 默认是单例的,即在整个 Spring 容器中,每个 Bean 只有一个实例。
可以通过在配置中指定 scope 属性,将 Bean 改为多例(Prototype)模式,这样每次获取的都是新的实例。
@Bean
@Scope("prototype") // 每次获取都是新的实例
public MyBean myBean() {
return new MyBean();
}
除了单例和多例,Spring 还支持其他作用域,如请求作用域(Request)、会话作用域(Session)等,适合 Web 应用中特定的使用场景。
为什么用b+树不用b树
B+ 树相比 B 树有几个显著优势:
首先,B+ 树的叶子节点通过链表相连,非常适合范围查询,如 ORDER BY 和 BETWEEN。
只需要找到符合条件的第一个叶子节点,顺序扫描后续的叶子节点就可以了。相比之下,B 树的每次范围查询都需要回溯到父节点,查询效率较低。
其次,B+ 树的非叶子节点不存储数据,能包含更多的键值指针,因此在相同节点容量下,B+ 树的层级更少,树的高度更低。较少的树层级意味着查找路径更短,从而减少磁盘 I/O 次数。
联合索引 (a, b),where a = 1 和 where b = 1,效果是一样的吗
不一样。
WHERE a = 1
能有效利用联合索引,因为 a 是联合索引的第一个字段,符合最左前缀匹配原则。而 WHERE b = 1
无法利用该联合索引,因为缺少 a 的匹配条件,MySQL 会选择全表扫描。
我们来验证一下,假设有一个 ab 表,建立了联合索引 (a, b)
:
CREATE TABLE ab (
a INT,
b INT,
INDEX ab_index (a, b)
);
插入数据:
INSERT INTO ab (a, b) VALUES (1, 2), (1, 3), (2, 1), (3, 3), (2, 2);
执行查询:
通过 explain 可以看到,WHERE a = 1
使用了联合索引,而 WHERE b = 1
需要全表扫描,依次检查每一行。
事务ACID
事务是一个或多个 SQL 语句组成的一个执行单元,这些 SQL 语句要么全部执行成功,要么全部不执行,不会出现部分执行的情况。主要作用是保证数据库操作的一致性。
事务具有四个基本特性,也就是通常所说的 ACID 特性,即原子性、一致性、隔离性和持久性。
慢查询怎么分析
首先,需要找到哪些 SQL 比较慢,可以启用慢查询日志,记录超过指定执行时间的查询。
也可以使用 show processlist;
查看当前正在执行的 SQL 语句,找出执行时间较长的 SQL。
或者在业务基建中加入对慢 SQL 的监控,常见的方案有字节码插桩、连接池扩展、ORM 框架扩展。
然后,使用 EXPLAIN 命令查看查询执行计划,判断查询是否使用了索引,是否存在全表扫描等问题。
EXPLAIN SELECT * FROM your_table WHERE conditions;
最后,根据分析结果,通过添加或优化索引、调整查询语句或者增加内存缓冲区来优化 SQL。
explain分析后, type的执行效率等级,达到什么级别比较合适
从高到低的效率排序是 system、const、eq_ref、ref、range、index 和 ALL。
一般情况下,建议 type 值达到 const、eq_ref 或 ref,因为这些类型表明查询使用了索引进行精确匹配,效率较高。
如果是范围查询,range 类型也是可以接受的。
通常要避免出现 ALL 类型,因为它表示全表扫描,性能最低。
设计一个秒杀场景
在设计秒杀系统时,我们需要考虑高并发、库存控制和系统的稳定性。
在请求到达时,通过 Redis 预扣库存,并将请求加入消息队列进行排队。消费者队列异步处理订单生成,并将订单写入数据库。
在这个过程中,我们通过 Redis 实现库存控制,防止超卖。同时,为了避免重复下单和恶意刷单,可以使用分布式锁和动态令牌机制。
最后,系统还可以加入限流、降级,以保证系统的高可用性。
手撕:3个线程交替打印1-100(伪代码就可以) , 怎么让main线程等待这3个线程执行完再执行
可以使用 synchronized 和 wait/notify 来控制线程交替执行。同时,可以使用 Thread.join() 方法来让 main 线程等待这 3 个线程完成。
class PrintTask implements Runnable {
private static int number = 1; // 共享变量
private int threadId; // 当前线程的编号
private static final Object lock = new Object();
public PrintTask(int threadId) {
this.threadId = threadId;
}
@Override
public void run() {
while (number <= 100) {
synchronized (lock) {
if (number % 3 == threadId) { // 判断是否当前线程的打印轮次
System.out.println("Thread-" + threadId + " prints: " + number++);
lock.notifyAll(); // 唤醒其他等待的线程
} else {
try {
lock.wait(); // 不是当前线程轮次,进入等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
// 主线程
public static void main(String[] args) {
Thread t1 = new Thread(new PrintTask(0)); // Thread-0
Thread t2 = new Thread(new PrintTask(1)); // Thread-1
Thread t3 = new Thread(new PrintTask(2)); // Thread-2
t1.start();
t2.start();
t3.start();
try {
t1.join(); // main 线程等待 t1 完成
t2.join(); // main 线程等待 t2 完成
t3.join(); // main 线程等待 t3 完成
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有线程执行完毕,Main 线程开始执行");
}
}
三个线程交替打印:
PrintTask 实现了 Runnable 接口,定义了线程执行的任务逻辑。 每个线程有一个唯一的 threadId,用来控制轮次(0、1、2)。 通过 number % 3 == threadId
判断是否是当前线程的轮次。如果是,则打印并递增 number,然后调用notifyAll()
唤醒其他线程。如果不是当前线程的轮次,调用 wait()
进入等待状态,等待其他线程唤醒。
让 main 线程等待 3 个线程完成:使用 Thread.join()
方法让 main 线程等待 t1、t2 和 t3 执行完毕后再继续执行。
其他题目
ThreadLocal,使用场景,原理 ArrayList,讲讲其他的List ArrayList线程安全版本的容器 HashMap原理 ConcurrentHashMap原理
这几个之前出现过的题目,推荐大家直接去看星球嘉宾三分恶的面渣逆袭。
ending
一个人可以走得很快,但一群人才能走得更远。二哥的编程星球已经有 6500 多名球友加入了,如果你也需要一个良好的学习环境,戳链接 🔗 加入我们吧。这是一个 编程学习指南 + Java 项目实战 + LeetCode 刷题 + 简历精修 的私密圈子,你可以阅读星球专栏、向二哥提问、帮你制定学习计划、和球友一起打卡成长。
两个置顶帖「球友必看」和「知识图谱」里已经沉淀了非常多优质的学习资源,相信能帮助你走的更快、更稳、更远。
欢迎点击左下角阅读原文了解二哥的编程星球,这可能是你学习求职路上最有含金量的一次点击。
最后,把二哥的座右铭送给大家:没有什么使我停留——除了目的,纵然岸旁有玫瑰、有绿荫、有宁静的港湾,我是不系之舟。共勉 💪。