一、ACM模式
1、多行输入,每行两个整数
import java.lang.*;
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while (in.hasNextInt()) {
int a = in.nextInt();
int b = in.nextInt();
System.out.println(a + b);
}
}
}
2、多组数据,每组第一行为n, 之后输⼊n行两个整数
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner scanner=new Scanner(System.in);
while(scanner.hasNext()){
int n=scanner.nextInt();
for(int i=0;i<n;i++){
int a=scanner.nextInt();
int b=scanner.nextInt();
System.out.println(a+b);
}
}
}
}
3、若干行输入,每行输入两个整数,遇到特定条件终止
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner scanner=new Scanner(System.in);
while(scanner.hasNext()){
int a=scanner.nextInt();
int b=scanner.nextInt();
if(a==0&&b==0){
break;
}
System.out.println(a+b);
}
}
}
4、若干行输入,遇到0终止,每行第⼀个数为N,表示本行后面有N个数
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
int n = scanner.nextInt();
if (n == 0) {
break;
}
int sum = 0;
for (int i = 0; i < n; i++) {
sum += scanner.nextInt();
}
System.out.println(sum);
}
}
}
5、多组n行数据,每行先输入一个整数N,然后在同一行内输入 M个整数,每组输出之间输出一个空行。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNextLine()) {
int N = sc.nextInt();
// 每组有n⾏数据
while (N-- > 0) {
int M = sc.nextInt();
int sum = 0;
// 每⾏有m个数据
while (M-- > 0) {
sum += sc.nextInt();
}
System.out.println(sum);
if (N > 0) System.out.println();
}
}
}
}
6、多组测试样例,每组输入数据为字符串,字符用空格分隔,输出为小数点后两位
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Map<String, Integer> map = new HashMap<>();
map.put("A", 4);
map.put("B", 3);
map.put("C", 2);
map.put("D", 1);
map.put("F", 0);
while (scanner.hasNextLine()) {
String[] s = scanner.nextLine().split(" "); // 分割字符串
double score = 0.0;
boolean invalid = false; // 标记有无特殊字母(非ABCDF)出现
for (int i = 0; i < s.length; i++) {
if (!map.containsKey(s[i])) {
invalid = true;
break;
}
score += map.get(s[i]);
}
if (invalid) {
System.out.println("Unknown");
} else {
System.out.printf("%.2f\n", score / s.length); // 保留2位
}
}
}
}
1. java输出总结
1. System.out
-
类型:
PrintStream(继承自OutputStream) -
特点:
- 自动刷新缓冲区(当换行
\n或println()调用时) - 继承
PrintStream,支持多种输出方法
- 自动刷新缓冲区(当换行
-
常用方法:
System.out.print("文本"); // 输出文本不换行 System.out.println("文本"); // 输出文本并换行 System.out.printf("格式", args); // 格式化输出(类似C语言printf)
2. String.format()
-
静态方法:返回格式化后的字符串
String formatted = String.format("%d年%d月%d日", 2023, 10, 1); System.out.println(formatted); // 输出:2023年10月1日
3. printf() 方法
-
直接输出格式化字符串:
System.out.printf("%.2f%%\n", 0.95); // 输出:95.00%
4. 占位符与格式说明符
| 占位符 | 说明 | 示例 |
|---|---|---|
%d | 整数 | %d → 123 |
%f | 浮点数(默认6位小数) | %f → 3.141593 |
%s | 字符串 | %s → "Hello" |
%n | 换行符 | 自动换行 |
%tx | 日期时间格式 | %tF → 2023-10-01 |
2. String ↔ 基本类型
-
String.valueOf():int num = 10; String str = String.valueOf(num); // "10" -
Integer.parseInt():int num = Integer.parseInt("123"); // 123
7、多组测试用例,第一行为正整数n, 第二行为n个正整数,n=0 时,结束输入,每组输出结果的下面都输出一个空行
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
int size = scanner.nextInt();
if (size == 0) {
break;
}
// 创建list
ArrayList<Integer> list = new ArrayList<>();
// 添加数据到list中
for (int i = 0; i < size; i++) {
int num = scanner.nextInt();
list.add(num);
}
int sum=0;
for (int i = 0; i < list.size(); i++) {
sum+=list.get(i);
}
sum=sum/size;
int res=0;
for (int i = 0; i < list.size(); i++) {
res+=Math.abs(list.get(i)-sum) ;
}
System.out.println(res/2);
System.out.println();
}
}
}
8、瓶盖换饮料
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
int m = scanner.nextInt(), k = scanner.nextInt();
if (m == 0 && k == 0) break;
int time = (m - 1) / (k - 1) + m;
System.out.println(time);
}
}
}
二、语雀-MySQL面试题
1、有了关系型数据库,为什么还需要NOSQL?
2、InnoDB和MyISAM有什么区别?
3、char和varchar的区别?
4、MySQL 5.x和8.0有什么区别?
5、为什么不建议使用多表join?
6、说一说MySQL一条SQL语句的执行过程?
7、InnoDB的一次更新事务过程是怎么样的?
8、什么是脏读、幻读、不可重复读?
9、InnoDB如何解决脏读、不可重复读和幻读的?
10、事务隔离级别怎么实现的?
MySQL 的事务隔离级别通过其存储引擎(主要是 InnoDB)的底层机制实现,核心在于 多版本并发控制(MVCC) 和 锁机制 的结合。以下是各隔离级别的实现原理及关键技术:
1. 事务隔离级别与问题
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
|---|---|---|---|---|
| 读未提交 | ✔️ | ✔️ | ✔️ | 最低级别,无锁 |
| 读已提交 | ❌ | ✔️ | ✔️ | 基于 MVCC 的快照读 |
| 可重复读 | ❌ | ❌ | ✔️ | MVCC + 间隙锁防幻读 |
| 串行化 | ❌ | ❌ | ❌ | 全表锁/行锁,强制顺序执行 |
2. 实现机制的核心技术
(1) MVCC(多版本并发控制)
• 核心思想:为每一行数据维护多个版本(快照),通过版本号或时间戳管理数据的可见性。
• 关键结构:
- Undo Log:存储事务未提交前的数据版本,用于回滚和快照读。
- Redo Log:保证数据持久化,崩溃恢复时重做未提交的事务。
- 版本链:每个行数据关联一个版本链,事务根据
Read View的规则选择可见版本。
(2) 锁机制
• 共享锁(S Lock):允许读操作,阻止其他事务加排他锁。
- 排他锁(X Lock):允许写操作,阻止其他事务加任何锁。
- 意向锁:表级锁,表示后续操作的范围(意向共享/排他)。
- 间隙锁(Gap Lock):锁定索引区间,防止插入幻读(仅 InnoDB)。
3. 各隔离级别的实现细节
(1) 读未提交(Read Uncommitted)
- 实现:使用 非锁定读(Non-Locking Read),直接读取当前最新数据。
- 风险:可能读取到未提交的数据(脏读)、不可重复读、幻读。
- 适用场景:极少使用,主要用于调试或高性能要求低且容忍不一致的场景。
(2) 读已提交(Read Committed)
• 实现:
- MVCC 快照读:事务启动时生成
Read View,仅读取已提交事务的版本。 - 非锁定读:默认使用
MVCC版本,无需显式加锁。 - 风险:仍可能因多版本共存导致 不可重复读(两次读同一行结果不同)。
- 优化:可通过
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;启用。
(3) 可重复读(Repeatable Read)
• 实现:
- MVCC 快照读:事务开始时固定
Read View,全程使用该快照。 - 间隙锁:对范围查询(如
SELECT ... WHERE id BETWEEN 100 AND 200)自动加间隙锁,防止其他事务插入新数据导致幻读。 - 风险:高并发下锁竞争加剧,性能下降。
- 示例:两次查询同一行数据结果一致,因始终读取事务开始时的快照。
(4) 串行化(Serializable)
• 实现:
- 强制串行化:对所有操作加 表级排他锁(Table Lock) 或 行级锁,确保事务顺序执行。
- 范围查询加间隙锁:与可重复读类似,但锁粒度更粗。
• 风险:严重降低并发性能,仅用于必须完全避免幻读的场景。
4. 幻读的解决策略
- 可重复读:通过 间隙锁 锁定查询条件涉及的索引区间,阻止新数据插入。
- 串行化:通过 表级锁 阻止其他事务修改数据。
- 读已提交:无法避免幻读,需依赖应用层处理(如使用唯一索引)。
三、语雀-RabbitMQ面试题
1、rabbitMQ的整体架构是怎么样的?
2、RabbitMQ是怎么做消息分发的?
1. 简单模式
2. 工作队列模式
3. 发布订阅模式
4. 路由模式
5. 主题模式
3、rabbitMQ如何实现延迟消息?
1. 死信队列
4、什么是RabbitMQ的死信队列?
5、RabbitMQ 是如何保证高可用的?
1. 普通集群
2. 镜像集群
6、RabbitMQ如何实现消费端限流
7、RabbitMQ如何防止重复消费
1. 接口幂等
8、如何保障消息一定能发送到RabbitMQ
9、RabbitMQ如何保证消息不丢
1. 队列和交换机的持久化
2. 持久化消息
3. 消费者确认机制
10、介绍下RabbitMQ的事务机制
四、语雀-设计模式面试题
0、代理模式在项目中应用
1. 工厂模式(Factory Pattern)
工厂模式在项目中主要用于创建复杂对象,隐藏创建逻辑,通过统一接口来指向新创建的对象。
DefaultTreeFactory
在LogicTreeTest.java中可以看到工厂模式的应用:
@Resource
private DefaultTreeFactory defaultTreeFactory;
@Test
public void test_tree_rule() {
// 构建规则树
// ...
// 使用工厂创建决策树引擎
IDecisionTreeEngine treeEngine = defaultTreeFactory.openLogicTree(ruleTreeVO);
// 使用决策树处理业务
DefaultTreeFactory.StrategyAwardVO data = treeEngine.process("xiaofuge", 100001L, 100, null);
}
工厂类DefaultTreeFactory负责创建决策树引擎对象,客户端不需要关心具体实现细节,只需要通过工厂获取对象即可。
策略装配工厂
在RaffleStrategyTest.java中:
@Resource
private IStrategyArmory strategyArmory;
@Before
public void setUp() {
// 策略装配 100001、100002、100003
log.info("测试结果:{}", strategyArmory.assembleLotteryStrategy(100001L));
log.info("测试结果:{}", strategyArmory.assembleLotteryStrategy(100006L));
}
IStrategyArmory作为工厂接口,负责装配和创建抽奖策略对象。
2. 代理模式(Proxy Pattern)
代理模式在项目中主要用于控制对其他对象的访问,增强原有对象的功能。
示例:数据库路由代理
在AwardRepository.java中:
@Resource
private IDBRouterStrategy dbRouter;
@Override
public void saveUserAwardRecord(UserAwardRecordAggregate userAwardRecordAggregate) {
// ...
try {
dbRouter.doRouter(userId); // 代理路由到特定数据库
transactionTemplate.execute(status -> {
// 执行数据库操作
// ...
});
} finally {
dbRouter.clear();
}
// ...
}
IDBRouterStrategy作为代理,控制数据库的路由访问,增强了原有数据库操作的功能,实现了分库分表的能力。
3. 模板模式(Template Pattern)
模板模式在项目中定义了算法的骨架,将一些步骤延迟到子类中实现。
示例:BaseEvent抽象类
在BaseEvent.java中:
@Data
public abstract class BaseEvent<T> {
// 定义抽象方法,由子类实现
public abstract EventMessage<T> buildEventMessage(T data);
public abstract String topic();
// 定义消息结构
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class EventMessage<T> {
private String id;
private Date timestamp;
private T data;
}
}
子类实现示例(SendAwardMessageEvent.java):
@Component
public class SendAwardMessageEvent extends BaseEvent<SendAwardMessageEvent.SendAwardMessage> {
@Value("${spring.rabbitmq.topic.send_award}")
private String topic;
@Override
public EventMessage<SendAwardMessage> buildEventMessage(SendAwardMessage data) {
return EventMessage.<SendAwardMessage>builder()
.id(RandomStringUtils.randomNumeric(11))
.timestamp(new Date())
.data(data)
.build();
}
@Override
public String topic() {
return topic;
}
// 内部消息类定义
// ...
}
BaseEvent定义了事件处理的骨架,子类实现具体的消息构建和主题定义。
4. 策略模式(Strategy Pattern)
策略模式在项目中定义了一系列算法,并使这些算法可以互相替换,让算法的变化独立于使用算法的客户端。
示例:抽奖策略
在RaffleStrategyTest.java中:
@Resource
private IRaffleStrategy raffleStrategy;
@Test
public void test_performRaffle() throws InterruptedException {
RaffleFactorEntity raffleFactorEntity = RaffleFactorEntity.builder()
.userId("xiaofuge")
.strategyId(100006L)
.build();
// 执行抽奖策略
RaffleAwardEntity raffleAwardEntity = raffleStrategy.performRaffle(raffleFactorEntity);
}
项目中还有规则链和规则树的实现:
@Resource
private RuleWeightLogicChain ruleWeightLogicChain;
@Resource
private RuleLockLogicTreeNode ruleLockLogicTreeNode;
这些都是策略模式的应用,通过不同的策略实现,可以灵活切换抽奖规则、权重计算等算法。
总结
该项目通过多种设计模式的组合使用,构建了一个灵活、可扩展的抽奖系统:
- 工厂模式:用于创建复杂对象,如决策树引擎、抽奖策略等
- 代理模式:用于增强功能,如数据库路由代理
- 模板模式:用于定义算法骨架,如事件处理基类
- 策略模式:用于实现可替换的算法,如抽奖策略、规则链等
这些设计模式的应用使得系统具有良好的可维护性和可扩展性,能够应对业务需求的变化。
1、三种工厂模式的区别和特点
1. 简单工厂模式