Commit on 2025/05/28 周三 20:35:08.96
This commit is contained in:
parent
376e78117c
commit
e653d997f0
@ -2251,3 +2251,83 @@ public int multipleKnapsack(int V, int[] weight, int[] value, int[] count) {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## ACM风格输入输出
|
||||
|
||||
```text
|
||||
**
|
||||
* 题目描述
|
||||
*
|
||||
* 给定一个整数数组 Array,请计算该数组在每个指定区间内元素的总和。
|
||||
*
|
||||
* 输入描述
|
||||
*
|
||||
* 第一行输入为整数数组 Array 的长度 n,接下来 n 行,每行一个整数,表示数组的元素。随后的输入为需要计算总和的区间下标:a,b (b > = a),直至文件结束。
|
||||
* 输入示例:
|
||||
* 5
|
||||
* 1
|
||||
* 2
|
||||
* 3
|
||||
* 4
|
||||
* 5
|
||||
* 0 1
|
||||
* 1 3
|
||||
*输出:
|
||||
* 3
|
||||
* 9
|
||||
* 输出每个指定区间内元素的总和。
|
||||
*/
|
||||
```
|
||||
|
||||
```java
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws IOException {
|
||||
// 快速 IO
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
|
||||
PrintWriter out = new PrintWriter(System.out);
|
||||
String line;
|
||||
|
||||
// 1)读数组长度
|
||||
line = br.readLine();
|
||||
if (line == null) return;
|
||||
int n = Integer.parseInt(line.trim());
|
||||
|
||||
// 2)读数组元素并构造前缀和
|
||||
long[] prefix = new long[n + 1];
|
||||
for (int i = 0; i < n; i++) {
|
||||
line = br.readLine();
|
||||
if (line == null) throw new IOException("Unexpected EOF when reading array");
|
||||
prefix[i + 1] = prefix[i] + Long.parseLong(line.trim());
|
||||
}
|
||||
|
||||
// 3)依次读查询直到 EOF
|
||||
// 每行两个整数 a, b (0 ≤ a ≤ b < n)
|
||||
while ((line = br.readLine()) != null) {
|
||||
line = line.trim();
|
||||
if (line.isEmpty()) continue;
|
||||
StringTokenizer st = new StringTokenizer(line);
|
||||
int a = Integer.parseInt(st.nextToken());
|
||||
int b = Integer.parseInt(st.nextToken());
|
||||
|
||||
// 区间和 = prefix[b+1] - prefix[a]
|
||||
long sum = prefix[b + 1] - prefix[a];
|
||||
out.println(sum);
|
||||
}
|
||||
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`line.trim()` 是把原 `line` 首尾的所有空白字符(空格、制表符、换行符等)都去掉之后的结果。
|
||||
|
||||
`StringTokenizer st = new StringTokenizer(line);`把一整行字符串 `line` 按默认的分隔符(空格、制表符、换行符等)拆成一个一个的“词”(token)
|
||||
|
||||
`StringTokenizer st = new StringTokenizer(line, ",;");` 第二个参数 `",;"` 中的**每个字符**都会被当作分隔符;如果想把空白也当分隔符,可以在里边加上空格 `" ,; "`
|
||||
|
||||
|
||||
|
||||
|
28
自学/微服务.md
28
自学/微服务.md
@ -162,6 +162,34 @@ mysql是服务名,不能写localhost(或 `127.0.0.1`),它永远只会指
|
||||
|
||||
|
||||
|
||||
### Docker Compose问题
|
||||
|
||||
如果你把某个服务从 `docker-compose.yml` 里删掉,然后再执行:
|
||||
|
||||
```shell
|
||||
docker compose down
|
||||
```
|
||||
|
||||
默认情况下 **并不会** 停止或删除那个已经“离开”了 Compose 配置的容器。
|
||||
|
||||
只能:
|
||||
|
||||
```shell
|
||||
docker compose down --remove-orphans #清理这些“孤儿”容器
|
||||
```
|
||||
|
||||
或者手动清理:
|
||||
|
||||
```shell
|
||||
docker ps #列出容器
|
||||
docker stop <container_id_or_name>
|
||||
docker rm <container_id_or_name>
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 认识微服务
|
||||
|
||||
微服务架构,首先是服务化,就是将单体架构中的功能模块从单体应用中拆分出来,独立部署为多个服务。
|
||||
|
216
自学/消息队列MQ.md
216
自学/消息队列MQ.md
@ -37,6 +37,7 @@ mq:
|
||||
restart: unless-stopped
|
||||
hostname: mq
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
RABBITMQ_DEFAULT_USER: admin
|
||||
RABBITMQ_DEFAULT_PASS: "admin"
|
||||
RABBITMQ_PLUGINS_DIR: "/plugins:/custom-plugins"
|
||||
@ -61,4 +62,217 @@ http://localhost:15672/ 访问控制台
|
||||
- **`consumer`**:消费者,消费消息的一方
|
||||
- **`queue`**:队列,存储消息。生产者投递的消息会暂存在消息队列中,等待消费者处理
|
||||
- **`exchange`**:交换机,负责消息路由。生产者发送的消息由交换机决定投递到哪个队列。**不存储**
|
||||
- **`virtual host`**:虚拟主机,起到数据隔离的作用。每个虚拟主机相互独立,有各自的exchange、queue
|
||||
- **`virtual host`**:虚拟主机,起到数据隔离的作用。每个虚拟主机相互独立,有各自的exchange、queue(每个项目+环境有各自的vhost)
|
||||
|
||||
一个队列最多指定给一个消费者!
|
||||
|
||||
|
||||
|
||||
## Spring AMQP
|
||||
|
||||
### 快速开始
|
||||
|
||||
**交换机和队列都是直接在控制台创建,消息的发送和接收在Java应用中实现!**
|
||||
|
||||
简单案例:直接向队列发送消息,**不经过交换机**
|
||||
|
||||

|
||||
|
||||
**引入依赖**
|
||||
|
||||
```xml
|
||||
<!--AMQP依赖,包含RabbitMQ-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
**配置MQ地址**,在`publisher`和`consumer`服务的`application.yml`中添加配置:
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
rabbitmq:
|
||||
host: localhost # 你的虚拟机IP
|
||||
port: 5672 # 端口
|
||||
virtual-host: /hmall # 虚拟主机
|
||||
username: hmall # 用户名
|
||||
password: 123 # 密码
|
||||
```
|
||||
|
||||
|
||||
|
||||
**消息发送:**
|
||||
|
||||
然后在`publisher`服务中编写测试类`SpringAmqpTest`,并利用`RabbitTemplate`实现消息发送:
|
||||
|
||||
```java
|
||||
@SpringBootTest
|
||||
public class SpringAmqpTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void testSimpleQueue() {
|
||||
// 队列名称
|
||||
String queueName = "simple.queue";
|
||||
// 消息
|
||||
String message = "hello, spring amqp!";
|
||||
// 发送消息
|
||||
rabbitTemplate.convertAndSend(queueName, message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
**消息接收**
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class SpringRabbitListener {
|
||||
// 利用RabbitListener来声明要监听的队列信息
|
||||
// 将来一旦监听的队列中有了消息,就会推送给当前服务,调用当前方法,处理消息。
|
||||
// 可以看到方法体中接收的就是消息体的内容
|
||||
@RabbitListener(queues = "simple.queue")
|
||||
public void listenSimpleQueueMessage(String msg) throws InterruptedException {
|
||||
System.out.println("spring 消费者接收到消息:【" + msg + "】");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
然后启动启动类,它能自动从队列中取出消息。取出后队列中就没消息了!
|
||||
|
||||
|
||||
|
||||
### 交换机
|
||||
|
||||
**1)fanout:广播给每个绑定的队列**
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
发送消息:
|
||||
|
||||
`convertAndSend`如果2个参数,第一个表示队列名,第二个表示消息;如果3个参数,第一个表示交换机,第二个表示`RoutingKey`,第三个表示消息。
|
||||
|
||||
```java
|
||||
@Test
|
||||
public void testFanoutExchange() {
|
||||
// 交换机名称
|
||||
String exchangeName = "hmall.fanout";
|
||||
// 消息
|
||||
String message = "hello, everyone!";
|
||||
rabbitTemplate.convertAndSend(exchangeName, "", message);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
**2)Direct交换机**
|
||||
|
||||
- 队列与交换机的绑定,不能是任意绑定了,而是要**指定**一个`RoutingKey`(路由key)
|
||||
- 消息的发送方在 向 Exchange发送消息时,也必须指定消息的 `RoutingKey`。
|
||||
- Exchange不再把消息交给每一个绑定的队列,而是根据消息的`Routing Key`进行判断,只有队列的`Routingkey`与消息的 `Routing key`完全一致,才会接收到消息
|
||||
|
||||
**注意,RoutingKey不等于队列名称**
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
**3)Topic交换机**
|
||||
|
||||
`Topic`类型的`Exchange`与`Direct`相比,都是可以根据`RoutingKey`把消息路由到不同的队列。
|
||||
|
||||
只不过`Topic`类型`Exchange`可以让队列在绑定`BindingKey` 的时候使用**通配符**!
|
||||
|
||||
BindingKey一般都是有一个或**多个单词**组成,多个单词之间以`.`分割
|
||||
|
||||
通配符规则:
|
||||
|
||||
- `#`:匹配一个或多个词
|
||||
- `*`:匹配不多不少恰好**1个词**
|
||||
|
||||
举例:
|
||||
|
||||
- `item.#`:能够匹配`item.spu.insert` 或者 `item.spu`
|
||||
- `item.*`:只能匹配`item.spu`
|
||||
|
||||
|
||||
|
||||
### 基于注解声明交换机队列
|
||||
|
||||
以往我们都在 RabbitMQ 管理控制台手动创建队列和交换机,开发人员还得把所有配置整理一遍交给运维,既繁琐又容易出错。更好的做法是在应用启动时自动检测所需的队列和交换机,若不存在则直接创建。
|
||||
|
||||
**基于注解方式来声明**
|
||||
|
||||
`durable="true"`:队列在 RabbitMQ 重启后依然存在。
|
||||
|
||||
`type` 默认交换机类型为ExchangeTypes.DIRECT
|
||||
|
||||
```java
|
||||
@RabbitListener(bindings = @QueueBinding(
|
||||
value = @Queue(name = "direct.queue1",durable = "true"),
|
||||
exchange = @Exchange(name = "hmall.direct", type = ExchangeTypes.DIRECT),
|
||||
key = {"red", "blue"}
|
||||
))
|
||||
public void listenDirectQueue1(String msg){
|
||||
System.out.println("消费者1接收到direct.queue1的消息:【" + msg + "】");
|
||||
}
|
||||
|
||||
@RabbitListener(bindings = @QueueBinding(
|
||||
value = @Queue(name = "direct.queue2"),
|
||||
exchange = @Exchange(name = "hmall.direct", type = ExchangeTypes.DIRECT),
|
||||
key = {"red", "yellow"}
|
||||
))
|
||||
public void listenDirectQueue2(String msg){
|
||||
System.out.println("消费者2接收到direct.queue2的消息:【" + msg + "】");
|
||||
}
|
||||
```
|
||||
|
||||
**检查队列**
|
||||
|
||||
- 如果 RabbitMQ 中已经有名为 `direct.queue1` 的队列,就不会重复创建;
|
||||
- 如果不存在,`RabbitAdmin` 会自动帮你创建一个。
|
||||
|
||||
**检查交换机**
|
||||
|
||||
- 同理,会查看有没有名为 `hmall.direct`、类型为 `direct` 的交换机,若不存在就新建。
|
||||
|
||||
**检查绑定**
|
||||
|
||||
- 最后再去声明绑定关系:把 `direct.queue1` 绑定到 `hmall.direct`,并且 routing-key 为 `"red"` 和 `"blue"`。
|
||||
- 如果已有相同的绑定(队列、交换机、路由键都一致),也不会再重复创建。
|
||||
|
||||
|
||||
|
||||
### 消息转换器
|
||||
|
||||
使用JSON方式来做序列化和反序列化,替换掉默认方式。
|
||||
|
||||
1)引入依赖
|
||||
|
||||
```XML
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-xml</artifactId>
|
||||
<version>2.9.10</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
2)配置消息转换器,在`publisher`和`consumer`两个服务的启动类中添加一个Bean即可:
|
||||
|
||||
```Java
|
||||
@Bean
|
||||
public MessageConverter messageConverter(){
|
||||
// 1.定义消息转换器
|
||||
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
|
||||
// 2.配置自动创建消息id,用于识别不同消息,也可以在业务中基于ID判断是否是重复消息
|
||||
jackson2JsonMessageConverter.setCreateMessageIds(true);
|
||||
return jackson2JsonMessageConverter;
|
||||
}
|
||||
```
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user