/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}
思考:学习测开知识并搭一个项目,后续全栈,agent,测开都能投
盛最多水的容器
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
思路:双指针往中间走,每次计算当前容积,要求最大水容量,左右哪边小移动哪边
class Solution {
public int maxArea(int[] height) {
int left=0;
int right=height.length-1;
int max=0;
while(left<right){
int cur=-1;
if(height[left]<height[right]){
cur=height[left]*(right-left);
left++;
}
else{
cur=height[right]*(right-left);
right–;
}
if(cur>max)max=cur;
}
return max;
}
}
两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
思路:Hashmap
易错:不能一开始就建立数字->索引,当target=2*nums[i]会出重复
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer,Integer> map=new HashMap<>();
for(int i=0;i<nums.length;i++){
if(map.containsKey(target-nums[i])){
return new int[]{i,map.get(target-nums[i])};
}
map.put(nums[i],i);
}
return null;
}
}
5.Java设计模式:讲一下策略模式。
是一种代码设计模式,主要目的就是为了消除大量的ifelse代码,比如一个支付功能,支持微信支付宝等,最简单写法是利用ifelse根据支付类型走对应支付逻辑,但利用策略模式,可以先定义一个支付接口,对于不同的支付行为实现不同的支付类,然后搭配工厂模式写一个map进行动态的支付策略的路由,在调用的时候业务代码就变得很干净
好处:
消灭了 if-else:你的主流程代码变得非常干净、清晰。
符合“开闭原则”:下次加个“银联支付”。你不需要改动任何现有的代码。你只需要新建一个 UnionPayStrategy implements PayStrategy 类,打上 @Service("UNION") 注解,Spring 自动就把它注册进去了。业务立马就能用!
职责单一:微信支付出 Bug 了,你只需要去 WechatPayStrategy 里找,绝对不会碰到支付宝的代码
6 .架构设计:了解 DDD(Domain-Driven Design 领域驱动设计)吗?
“DDD 并不是一套技术框架,而是一种应对复杂业务架构的方法论。传统的 MVC 模式把所有的逻辑堆在 Service 里,容易产生‘上帝类’。
引入 DDD 后,我们会根据业务边界划分出不同的**‘限界上下文’(微服务)。在代码实现上,我们会采用四层架构,把核心业务逻辑沉淀到‘领域层’,通过‘聚合根’保证数据一致性,通过‘领域事件’**实现服务间的解耦。
我曾经在一个电商项目中应用过 DDD,把原本揉在一起的订单、库存、营销系统拆分,用事件驱动串联,大大降低了系统的耦合度,提升了代码的可维护性。”
MVC和DDD区别
在 MVC 中,所有的业务逻辑(计算价格、判断库存、校验状态)都被塞进了 Service 层,而 Entity(数据库实体)里只有 getter 和 setter。
假设业务规则是:下单时,必须校验库存是否充足,如果充足则扣减;如果订单总金额超过 500 元,还需要打 9 折。
一、 传统 MVC 写法(贫血模型)
在 MVC 架构下,我们的实体类(Entity)通常只是个“数据袋”,所有的逻辑全在 Service 里。
1. Entity(贫血模型)
java
public class Order {
private Long id;
private Double totalAmount;
private Integer status;
// 只有 getter 和 setter
public Double getTotalAmount() { return totalAmount; }
public void setTotalAmount(Double totalAmount) { this.totalAmount = totalAmount; }
// … 其他 getter/setter
}
public class Product {
private Long id;
private Integer stock;
// 只有 getter 和 setter
public Integer getStock() { return stock; }
public void setStock(Integer stock) { this.stock = stock; }
}
2. Service(上帝类)
所有的业务逻辑、判断、算钱都在这里,随着需求增加,这个类会变得无比臃肿。
java
@Service
public class OrderService {
@Autowired
private ProductMapper productMapper;
@Autowired
private OrderMapper orderMapper;
public void createOrder(Long productId, Integer quantity) {
// 1. 查数据库
Product product = productMapper.selectById(productId);
// 2. 业务判断:校验库存
if (product.getStock() < quantity) {
throw new RuntimeException("库存不足");
}
// 3. 业务逻辑:扣减库存 (直接修改属性)
product.setStock(product.getStock() – quantity);
productMapper.updateById(product);
// 4. 业务逻辑:计算金额
Double amount = 100.0 * quantity;
if (amount > 500) {
amount = amount * 0.9; // 满500打9折
}
// 5. 创建订单
Order order = new Order();
order.setTotalAmount(amount);
order.setStatus(1);
orderMapper.insert(order);
System.out.println("下单成功,金额:" + amount);
}
}
痛点:未来如果产品经理说“VIP用户积分超过1000也可以打折”,你得继续在这个 createOrder 里面写 if-else。数据(Entity)和操作数据的行为(Service)分家了,非常容易改出 Bug。
二、 DDD 写法(充血模型)
在 DDD 中,我们强调**“谁的数据,谁自己管”**。我们把计算金额、扣减库存的行为,直接搬到实体内部去。
1. Domain 层(充血模型)
实体不再只是数据袋,它有了自己的“灵魂”(行为)。
java
// 商品实体:自己负责扣库存
public class Product {
private Long id;
private Integer stock;
public Integer getStock() { return stock; }
// 核心业务逻辑:扣减库存由商品自己负责
public void deductStock(int quantity) {
if (this.stock < quantity) {
throw new BusinessException(“商品库存不足”);
}
this.stock -= quantity;
}
}
// 订单实体:自己负责算钱
public class Order {
private Long id;
private Double totalAmount;
public Double getTotalAmount() { return totalAmount; }
// 核心业务逻辑:计算总价由订单自己负责
public void calculateTotalAmount(Double unitPrice, int quantity) {
double amount = unitPrice * quantity;
if (amount > 500) {
amount = amount * 0.9;
}
this.totalAmount = amount;
}
}
2. Application 层(应用服务)
应用服务变成了“光杆司令”,它只负责调度:拿数据 -> 调领域模型 -> 存数据。没有具体的业务判断。
java
@Service
public class OrderApplicationService {
@Autowired
private ProductRepository productRepository;
@Autowired
private OrderRepository orderRepository;
@Transactional
public void createOrder(Long productId, Integer quantity) {
// 1. 获取聚合根(通过 Repository 查出来)
Product product = productRepository.findById(productId);
// 2. 委托给领域模型自己去扣库存(它内部有校验逻辑)
product.deductStock(quantity);
// 3. 创建订单并委托领域模型自己算钱
Order order = new Order();
order.calculateTotalAmount(100.0, quantity);
// 4. 持久化
productRepository.save(product);
orderRepository.save(order);
}
}
三、 用代码看本质区别
对比一下上面两段代码:
在 MVC 中:OrderService 像个操碎了心的老母亲,不但要查数据库,还要管算账、管扣库存。如果算账逻辑出错了,你需要在一个几千行的 Service 里找半天。
在 DDD 中:OrderApplicationService 就像个项目经理,它只说一句:order.calculateTotalAmount(…) 和 product.deductStock(…)。具体怎么算、怎么扣,让 Order 和 Product 这两个“专家(实体)”自己去搞定。
“谈谈你对 DDD、MVC 和微服务的理解?”
“我认为这三者是不同维度的概念。
MVC 是一种技术实现架构,它通过 Controller、Service、DAO 实现代码分层,但在复杂业务中,容易导致 Service 层变成‘上帝类’(贫血模型)。
DDD 是一种应对复杂业务的方法论。它的核心是把业务逻辑收拢到领域模型中(充血模型),并通过划分‘限界上下文’和‘聚合根’来保证业务的高内聚。
微服务 是一种物理部署架构。,如果没有 DDD 的指导,微服务很容易拆成‘分布式单体’。
所以,实际工程中,我们通常用 DDD 的思想来划分微服务的边界,把每个限界上下文落地为一个微服务,然后用 MVC 或六边形架构在微服务内部实现代码分层。这样既有宏观的业务解耦,又有微观的技术实现。”
7. Redis:Redis 为什么那么快?
纯内存操作:基于内存,读写速度极快,避免了磁盘 I/O。
单线程模型(针对命令执行):避免了多线程的上下文切换和锁竞争,保证了原子性(6.0 之后网络 I/O 是多线程,但核心命令执行仍是单线程)。
I/O 多路复用(epoll):一个线程可以同时处理大量网络连接,让 CPU 始终处于高利用率。
高效的数据结构:如 SDS、跳表(ZSet)、哈希表(渐进式 rehash)等,都经过极致优化。
“比如在高并发秒杀场景下,每秒几万次的库存读取和扣减,传统数据库会因为行锁和磁盘 IO 直接崩溃。利用 Redis 的原子命令(如 DECR)在内存中瞬间完成,效率极高。”
8. Spring:Spring 事务失效的场景有哪些?
“Spring 事务失效的本质是因为 AOP 动态代理没有生效。常见场景有 5 种:
方法非 public:Spring 事务默认只能拦截 public 方法。
自调用:同一个类中,A 方法(无事务)调用 B 方法(有事务),因为走的是 this 引用,不经过代理类。
异常被 catch 吞掉:或者抛出的是受检异常(Checked Exception),默认不回滚。
类未被 Spring 管理:没有加 @Service 等注解。
数据库引擎不支持:如 MySQL 的 MyISAM。”
9. Java并发:CAS 的原理是什么?
CAS(Compare and Swap)是一种无锁并发机制,包含三个操作数:内存位置 V、预期原值 A、新值 B。当且仅当 V 的值等于 A 时,才会将 V 更新为 B,否则不做任何操作,通常配合自旋循环重试。
但在高并发下,CAS 有三大问题:ABA 问题(加版本号解决)、自旋开销大、只能保证一个变量原子性。
10. Java并发:synchronized 的锁升级过程是怎样的?
“为了减少锁带来的性能开销,JDK 1.6 引入了锁升级机制。锁的状态保存在对象头的 Mark Word 中,过程为:
无锁:对象刚创建。
偏向锁:只有一个线程访问时,直接在对象头记录线程 ID,下次进来直接放行,无锁化开销。
轻量级锁:当有多个线程交替访问时,偏向锁升级为轻量级锁。线程通过 CAS 自旋获取锁,不会阻塞。
重量级锁:如果自旋失败(并发冲突严重),升级为重量级锁,没抢到锁的线程进入 Monitor 的 EntryList 阻塞等待,由操作系统唤醒。”
11. 场景题:如果接手了一堆 if-else 嵌套,里面还有一堆 try-catch 的代码,你会怎么优化重构?
使用设计模式:如果 if-else 是业务类型判断,引入策略模式 + 工厂模式;如果是状态流转,使用状态模式;如果是参数校验,使用责任链模式。
| 策略 + 工厂 | 消除平级的业务类型判断 | 选出唯一的算法去执行 | 支付方式、登录方式、打折计算 |
| 状态模式 | 消除随状态改变的大量分支 | 内部状态改变引发行为改变 | 订单流转、电梯运行、红绿灯 |
| 责任链模式 | 消除串行的前置校验或审批 | 像链条一样依次传递处理 | 参数校验、审批流、拦截器 |
12. 场景题:如果按照产品经理的方案做出了技术方案并上线,结果发现系统出现了很严重的问题,这个时候你该怎么解决?
“遇到线上事故,第一原则是**『先恢复,后排查,再复盘』**:
快速止损:立即回滚最近的上线版本(前提是回滚安全),或者通过配置中心降级该功能开关,优先保证核心业务可用。
定位问题:查看 APM 监控(如 SkyWalking)、日志错误率、数据库慢查询,排查是否因为流量突增、资源死锁或新代码的 Bug。
高效修复:定位到 Bug 后,紧急拉分支修复,走绿色通道快速测试上线。
事后复盘:写事故报告(RCA),分析根因,并补充监控告警、完善测试用例(特别是漏测的边界场景),优化发布流程(引入灰度发布)。”
14. 缓存设计:怎么保证 Redis 和数据库的数据一致性?
写操作:先更新数据库,再删除缓存
读操作:先读缓存,未命中读 DB,然后回写缓存。
兜底方案:针对高一致性业务,可以通过监听 MySQL 的 Binlog(如 Canal),将变更异步同步到 Redis
网硕互联帮助中心





评论前必须登录!
注册