练习
递减元素使数组呈锯齿状
给你一个整数数组 nums,每次 操作 会从中选择一个元素并 将该元素的值减少 1。
如果符合下列情况之一,则数组 A 就是 锯齿数组:
每个偶数索引对应的元素都大于相邻的元素,即 A[0] > A[1] < A[2] > A[3] < A[4] > ...
或者,每个奇数索引对应的元素都大于相邻的元素,即 A[0] < A[1] > A[2] < A[3] > A[4] < ...
返回将数组 nums 转换为锯齿数组所需的最小操作次数。
示例 1:
输入:nums = [1,2,3]
输出:2
解释:我们可以把 2 递减到 0,或把 3 递减到 1。
示例 2:
输入:nums = [9,6,1,6,2]
输出:4
代码
class Solution {
public int movesToMakeZigzag(int[] nums) {
int n=0;int m=0;
for (int i = 0; i < nums.length; i++) {
if(i%2==0){//i是偶数
n = n+jishu(nums,i);//如果是每个奇数下标比两边小,可以判断多少次偶数下标比奇数小,得出奇数索引
}else {//i是奇数
m = m+jishu(nums,i);//如果是每个偶数下标比两边小,可以判断多少次奇数下标比偶数小,得出偶数索引
}
}
return Math.min(n,m);//返回最小的操作次数
}
public int jishu(int[] nums,int i){
int left = 0; int right = 0;
if(i-1>=0&&nums[i-1]<=nums[i]){//如果左边的值比它小
left=nums[i]-nums[i-1]+1;//需要这么多次才能让i比左边更小
//同理,需要这么多次才能让左边比i大
}
if(i+1<nums.length&&nums[i+1]<=nums[i]){//如果右边值比它小
right=nums[i]-nums[i+1]+1;//需要这么多次才能让i比右边更小
//同理,需要这么多次才能让右边比i大
}
return Math.max(left,right);//比较两边那个用的最多(也就是比两边最小值更小)
}
}
//以偶数索引为例 凡是奇数下标都比两边数小
//如果不符合要求 就要用i的位置-两边最小值+1 就得到符合要求的
//奇数索引同理
//将奇数位置的元素减少到刚好比相邻的偶数位置元素小,得出偶数索引
//将偶数位置的元素减少到刚好比相邻的奇数位置元素小,得出奇数索引
本来想一个上午写三道,这样下午就能多看会课了,但是这一道就给我干到12点了
总结一下,自己写的时候肯定是for循环了 也不粘了 一开始想的是要for两遍,一遍奇数索引,一遍偶数索引,结果花了70多行才写完,然后测试用例还bug不少,眼看就要12点了,遂放弃。
看了大神代码才明白,不需要那么繁琐, 因为题只让减 所以x>=y 要判断x减 不能判断y增
这也就是说,大神代码意思,就是用偶数序列判断奇数索引(因为只能让大的减)
用奇数序列判断偶数索引(同理)
算是搞明白...(可能)已经12点了,下午再开始都快1点多了 真是... 计划赶不上变化
得分最高的单词集合
你将会得到一份单词表 words,一个字母表 letters (可能会有重复字母),以及每个字母对应的得分情况表 score。
请你帮忙计算玩家在单词拼写游戏中所能获得的「最高得分」:能够由 letters 里的字母拼写出的 任意 属于 words 单词子集中,分数最高的单词集合的得分。
单词拼写游戏的规则概述如下:
玩家需要用字母表 letters 里的字母来拼写单词表 words 中的单词。
可以只使用字母表 letters 中的部分字母,但是每个字母最多被使用一次。
单词表 words 中每个单词只能计分(使用)一次。
根据字母得分情况表score,字母 'a', 'b', 'c', ... , 'z' 对应的得分分别为 score[0], score[1], ..., score[25]。
本场游戏的「得分」是指:玩家所拼写出的单词集合里包含的所有字母的得分之和。
示例 1:
输入:words = ["dog","cat","dad","good"], letters = ["a","a","c","d","d","d","g","o","o"], score = [1,0,9,5,0,0,3,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0]
输出:23
解释:
字母得分为 a=1, c=9, d=5, g=3, o=2
使用给定的字母表 letters,我们可以拼写单词 "dad" (5+1+5)和 "good" (3+2+2+5),得分为 23 。
而单词 "dad" 和 "dog" 只能得到 21 分。
示例 2:
输入:words = ["xxxz","ax","bx","cx"], letters = ["z","a","b","c","x","x","x"], score = [4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,10]
输出:27
解释:
字母得分为 a=4, b=4, c=4, x=5, z=10
使用给定的字母表 letters,我们可以组成单词 "ax" (4+5), "bx" (4+5) 和 "cx" (4+5) ,总得分为 27 。
单词 "xxxz" 的得分仅为 25 。
示例 3:
输入:words = ["leetcode"], letters = ["l","e","t","c","o","d"], score = [0,0,1,1,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0]
输出:0
解释:
字母 "e" 在字母表 letters 中只出现了一次,所以无法组成单词表 words 中的单词。
提示:
1 <= words.length <= 14
1 <= words[i].length <= 15
1 <= letters.length <= 100
letters[i].length == 1
score.length == 26
0 <= score[i] <= 10
words[i] 和 letters[i] 只包含小写的英文字母。
class Solution {
private String[] words;
private int[] score;
int[] left = new int[26];
int ans = 0;
// ☆☆☆☆☆ 回溯(子集型)
public int maxScoreWords(String[] words, char[] letters, int[] score) {
this.words = words;
this.score = score;
for (char c : letters) {
left[c - 'a']++;
}
backtrack(words.length - 1, 0);
return ans;
}
private void backtrack(int i, int curScore) {
if (i < 0) {
ans = Math.max(ans, curScore);
return;
}
backtrack(i - 1, curScore);
char[] cs = words[i].toCharArray();
int j;
for (j = 0; j < cs.length; j++) {
int idx = cs[j] - 'a';
if (--left[idx] < 0) {
// 提前结束遍历单词
break;
}
curScore += score[idx];
}
if (j >= cs.length) {
backtrack(i - 1, curScore);
}
for (int k = Math.min(cs.length - 1, j); k >= 0; k--) {
++left[cs[k] - 'a'];
}
}
}
直接粘的大神代码,放弃了
想我也曾学过回溯,现在忘光了,甚至看都看不明白
别人眼中的中等题,一次回溯搞定,我眼中的困难题,叹
重学数据结构了要
擦
两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
示例 2:
输入:l1 = [0], l2 = [0]
输出:[0]
示例 3:
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]
class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
//定义一个新联表伪指针,用来指向头指针,返回结果
ListNode prev = new ListNode(0);
//定义一个进位数的指针,用来存储当两数之和大于10的时候,
int carry = 0;
//定义一个可移动的指针,用来指向存储两个数之和的位置
ListNode cur = prev;
//当l1 不等于null或l2 不等于空时,就进入循环
while(l1!=null || l2!=null){
//如果l1 不等于null时,就取他的值,等于null时,就赋值0,保持两个链表具有相同的位数
int x= l1 !=null ? l1.val : 0;
//如果l1 不等于null时,就取他的值,等于null时,就赋值0,保持两个链表具有相同的位数
int y = l2 !=null ? l2.val : 0;
//将两个链表的值,进行相加,并加上进位数
int sum = x + y + carry;
//计算进位数
carry = sum / 10;
//计算两个数的和,此时排除超过10的请况(大于10,取余数)
sum = sum % 10;
//将求和数赋值给新链表的节点,
//注意这个时候不能直接将sum赋值给cur.next = sum。这时候会报,类型不匹配。
//所以这个时候要创一个新的节点,将值赋予节点
cur.next = new ListNode(sum);
//将新链表的节点后移
cur = cur.next;
//当链表l1不等于null的时候,将l1 的节点后移
if(l1 !=null){
l1 = l1.next;
}
//当链表l2 不等于null的时候,将l2的节点后移
if(l2 !=null){
l2 = l2.next;
}
}
//如果最后两个数,相加的时候有进位数的时候,就将进位数,赋予链表的新节点。
//两数相加最多小于20,所以的的值最大只能时1
if(carry == 1){
cur.next = new ListNode(carry);
}
//返回链表的头节点
return prev.next;
}
}
链表创建新节点用.next 以前可能学过忘了,又死在这了,思路不难懂,就是不会写。
附上大神代码
大体思路
两个链表相加,位数不够补零
就把它们当成普通算数 只不过以前是先算个位 正着写
而链表就相当于把个位放开头了
另外答案按逆序写 拿出来还是那个数
所以用指针去找
- 定义一个指针cur = prew去向后移位,用来存储两数字之和
- 定义一个指针carry存进位,用来存储大于10的数
- 定义一个指针prew定起始位,用来返回最终结果
- 循环双节点,直到一方或双方节点为null时,赋值0(用来保持同位,总不能9+NULL吧)
- 每次相加都会返回sum,就可以算出carry 所以sum=l1+l2+carry,同时进位sum取余
- 每个节点存一个值,需要让cur创建新节点再赋值 移动cur指针到新节点
- 做判断,如果l1l2不为null 每次cur移动后 它们也要节点后移
- 最后判断 如果carry还有进位数 就创建一个新节点存carry
- 最后返回就是prev.next 去找cur添加的子节点 返回的就是答案
字少的
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode left =new ListNode(0);//起始节点
ListNode right = left; //移动指针
int n=0;//存进位
while(l1!=null || l2!=null){//当都不为空时
int x;int y;
if(l1!=null){x = l1.val;}//l1不为空取值
else{ x = 0;}//l1为空时取0
if(l2!=null){y = l2.val;}//l2不为空取值
else{ y = 0;}//l2为空时取0
int sum = x+y+n;//取和(无进位就是0 有就是1)
n = sum/10;//判断是否有进位
sum = sum%10;//无论有无进位,都要取余
//因为起始节点有0了
right.next =new ListNode(sum);//下一个节点赋值
right = right.next;//指针右移
if(l1!=null){l1=l1.next;}//l1不为空,右移下一位
if(l2!=null){l2=l2.next;}//l2不为空,右移下一位
}
if(n==1){//如果最后相加进位了
right.next=new ListNode(n);//再右填一位
}
return left.next;//从起始节点0后一位的开始遍历
}
}
说实话,每天三题要弄明白加上背点八股都已经快6点了,还想着看视频学东西那可真是太屑了
感觉很低效,有的题要思考一下午才能完成,不仅是基础问题还有题的复杂性
以后尽量写写,不会超过上午了,下午听课上午写,不然一天都在死磕算法了,很费时间
羡慕那些算法牛逼的大神
八股
java 容器都有哪些?
List:vector arraylist linkedlist
set:hashset treeset linkhashset
map:hashmap treemap hashtable
queue
Collection 和 Collections 有什么区别?
前者是集合接口 后者是集合工具类
List、Set、Map 之间的区别是什么
list和set是继承于collection
list有安全集合vector map有hashtable
list是有序的 set是无序的
list的常见方法有add()remove()clear()get()contains()size()
set的常见方法有add()remove()clear()contains()size()
map的常见方法有pull()remove()get()values()keyset()getvalue()clear()containskey()containcalue()
size()
list的元素可以重复 后两者不可重复
HashMap 和 Hashtable 有什么区别?
hashmap不安全不同步 但是效率比hashtable快,使用containskey和containsvalue方法
可以存空键值
hashtable线程安全同步,但是效率比hashmap低,使用contains()方法,不可以存空键值
rabbitmq第三天..
发布信息
发布信息原理
发布确认 就是队列收到生产者发来的信息后 返回信息给生产者
Channel channel = RabbitMqUtils.getChannel()) {
//开启发布确认
channel.confirmSelect();
boolean durable = true;//需要让queue进行持久化
发布确认的策略
单个确认发布
发一个消息后被确认发布 后续消息才会继续发布
缺点:发布速度很慢
//单个确认
public static void publishMessageIndividually() throws Exception {
try (Channel channel = RabbitMqUtils.getChannel()) {
//队列声明
String queueName = UUID.randomUUID().toString();
//队列名字 持久化 共享 自动删除 其他
channel.queueDeclare(queueName, true, false, false, null);
//开启发布确认
channel.confirmSelect();
//开始时间
long begin = System.currentTimeMillis();
//批量发消息1000条
for (int i = 0; i < MESSAGE_COUNT; i++) {
String message = i + "";
//交换机 队列名称 其他 消息体
channel.basicPublish("", queueName, null, message.getBytes());
//单个消息就马上进行发布
//服务端返回 false 或超时时间内未返回,生产者可以消息重发
boolean flag = channel.waitForConfirms();
if(flag){
System.out.println("消息发送成功");
}
}
//结束时间
long end = System.currentTimeMillis();
System.out.println("发布" + MESSAGE_COUNT + "个单独确认消息,耗时" + (end - begin) +
"ms");
}
}
public static void main(String[] args) throws Exception {
//1.单个确认
//发布1000个单独确认消息,耗时899ms
ConfirmMessage.publishMessageIndividually();
}
批量确认发布
可以发布一批信息后一起确认,极大提高吞吐量
缺点:出现问题时不知道是什么消息出问题了
//批量确认
public static void publishMessageBatch() throws Exception {
try (Channel channel = RabbitMqUtils.getChannel()) {
//队列声明
String queueName = UUID.randomUUID().toString();
//队列名字 持久化 共享 自动删除 其他
channel.queueDeclare(queueName, true, false, false, null);
//开启发布确认
channel.confirmSelect();
//开始时间
long begin = System.currentTimeMillis();
//批量确认消息的大小
int batchSize=100;
//批量发布消息 批量确认
for (int i = 0; i < MESSAGE_COUNT; i++) {
String message=i+"";
channel.basicPublish("",queueName,null,message.getBytes());
//判断到100条消息的时候 批量确认一次
if(i%batchSize == 0){
//发布确认
channel.waitForConfirms();
}
}
//结束时间
long end = System.currentTimeMillis();
System.out.println("发布" + MESSAGE_COUNT + "个批量确认消息,耗时" + (end - begin) +
"ms");
}
}
}
public static void main(String[] args) throws Exception {
//1.单个确认
//发布1000个单独确认消息,耗时899ms
// ConfirmMessage.publishMessageIndividually();
//2.批量确认
//发布1000个批量确认消息,耗时106ms
ConfirmMessage.publishMessageBatch();
}
异步确认发布
性价比最高 又可靠又效率 通过函数回调来保证是否投递成功
原理图:
//异步发布确认
public static void publishMessageAsync() throws Exception{
try (Channel channel = RabbitMqUtils.getChannel()) {
//队列声明
String queueName = UUID.randomUUID().toString();
//队列名字 持久化 共享 自动删除 其他
channel.queueDeclare(queueName, true, false, false, null);
//开启发布确认
channel.confirmSelect();
//开始时间
long begin = System.currentTimeMillis();
//消息确认成功 回调函数
ConfirmCallback ackCallback =(deliveryTag, multiple) ->{
System.out.println("确认的消息:"+deliveryTag);
};
//消息确认失败 回调函数
/**
* 1.消息的标识
* 2.是否批量确认
* */
ConfirmCallback nackCallback =(deliveryTag,multiple) ->{
System.out.println("未确认的消息:"+deliveryTag);
};
//消息的监听器监听器放前面 可以监听后面的信息是否发送成功与失败
/**
* 1.监听那些消息成功了
* 2.监听那些消息失败了
* */
channel.addConfirmListener(ackCallback,nackCallback);//异步通知
//批量发布消息 不用它来确认消息 只负责发布
for (int i = 0; i < MESSAGE_COUNT; i++) {
String message=i+"";
channel.basicPublish("",queueName,null,message.getBytes());
}
//结束时间
long end = System.currentTimeMillis();
System.out.println("发布" + MESSAGE_COUNT + "个异步确认消息,耗时" + (end - begin) +
"ms");
}
}
//3.异步批量确认
//发布1000个异步确认消息,耗时68ms
ConfirmMessage.publishMessageAsync();
}
如果处理异步未确认消息
使用ConcurrentLinkedQueue 并发链路队列
//开启发布确认
channel.confirmSelect();
/**
* 线程安全有序的一个哈希表 适用于高并发情况
* 1.轻松的将序号与消息进行关联
* 2.轻松的批量删除条目 只要给到序号 序号key消息value
* 3.支持高并发(多线程)
*
* */
ConcurrentSkipListMap outstandingConfirms=new ConcurrentSkipListMap<>();
//监听器
//消息确认成功 回调函数
ConfirmCallback ackCallback =(deliveryTag, multiple) ->{
if(multiple) {//是批量吗
//2.删除掉已经确认的消息,剩下就是未确认的消息
//通过标识拿到被确认的消息并清理
ConcurrentNavigableMap confimed = outstandingConfirms.headMap(deliveryTag);
confimed.clear();
}else {
outstandingConfirms.remove(deliveryTag);
}
System.out.println("确认的消息:" + deliveryTag);
};
//消息确认失败 回调函数
/**
* 1.消息的标识
* 2.是否批量确认
* */
ConfirmCallback nackCallback =(deliveryTag,multiple) ->{
//3.打印未确认的消息有哪些
String message = (String) outstandingConfirms.get(deliveryTag);
System.out.println(
"未确认的消息是:"+ message+
"::::未确认的消息tag:"+deliveryTag);
};
//消息的监听器监听器放前面 可以监听后面的信息是否发送成功与失败
/**
* 1.监听那些消息成功了
* 2.监听那些消息失败了
* */
channel.addConfirmListener(ackCallback,nackCallback);//异步通知
//发消息
//开始时间
long begin = System.currentTimeMillis();
//批量发布消息 不用它来确认消息 只负责发布
for (int i = 0; i < MESSAGE_COUNT; i++) {
String message=i+"";
channel.basicPublish("",queueName,null,message.getBytes());
//1.此处记录所有要发送的消息-消息和
//括号内(信道下一次发布的值序号,信息值)
outstandingConfirms.put(channel.getNextPublishSeqNo(),message);
}
三种发布确定速度对比
单独发布消息
同步等待确认,简单,但吞吐量非常有限。
批量发布消息
批量同步等待确认,简单,合理的吞吐量,一旦出现问题但很难推断出是那条
消息出现了问题。
异步处理:(最常用)
最佳性能和资源使用,在出现错误的情况下可以很好地控制,但是实现起来稍微难些
交换机
之前的没写交换机的原因是 rabbitmq本身有一个默认交换机
我们假设的是工作队列背后,每个任务都恰好交付给一个消费者(工作进程)。
在这一部分中,我们将做一些完全不同的事情-我们将消息传达给多个消费者。这种模式
称为 ”发布/订阅
原理图
交换机的介绍
RabbitMQ 消息传递模型的核心思想是: 生产者生产的消息从不会直接发送到队列
相反,生产者只能将消息发送到交换机(exchange),交换机工作的内容非常简单,一方面它接收来
自生产者的消息,另一方面将它们推入队列
交换机类型
直接(direct), 主题(topic) ,标题(headers) , 扇出(fanout)
channel.basicPublish("","hello",null,message.getBytes());
第一个参数是交换机的名称。空字符串表示默认或无名称交换机:消息能路由发送到队列中其实
是由 routingKey(bindingkey)绑定 key 指定的,如果它存在的话
临时队列 (不持久化的)
一旦我们断开了消费者的连接,队列将被自动删除
创建临时队列的方式如下: 没有队列名称的
String queueName = channel.queueDeclare().getQueue()
fanout扇出交换机
扇出交换机 一发多接
消费者
/**
*消息接收
*/
public class ReceiveLogs01 {
// 交换机的名称
private static final String EXCHANGE_NAME = "logs";
public static void main(String[] argv) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
//声明一个交换机
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
//声明一个临时队列
/**
* 生成一个临时的队列 队列的名称是随机的
* 当消费者断开和该队列的连接时 队列自动删除
*/
String queueName = channel.queueDeclare().getQueue();
//把该临时队列绑定我们的 exchange 其中 routingkey(也称之为 binding key)为空字符串
/**
* 绑定交换机与队列
*
*/
channel.queueBind(queueName, EXCHANGE_NAME, "");
System.out.println("等待接收消息,把接收到的消息打印在屏幕.....");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println("ReceiveLogs01控制台打印接收到的消息"+message);
};
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { });
}
}
生产者
/**
*生产者
*/
public class EmitLog {
//交换机的名称
private static final String EXCHANGE_NAME = "logs";
public static void main(String[] argv) throws Exception {
try (Channel channel = RabbitMqUtils.getChannel()) {
/**
* 声明一个 exchange
* 1.exchange 的名称
* 2.exchange 的类型
*/
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
Scanner sc = new Scanner(System.in);
System.out.println("请输入信息");
while (sc.hasNext()) {
String message = sc.nextLine();
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
System.out.println("生产者发出消息" + message);
}
}
}
}
direct直接交换机
直接交换机 根据routingkey发消息
生产者
public class DirctLogs {
//交换机的名称
private static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] argv) throws Exception {
try (Channel channel = RabbitMqUtils.getChannel()) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入信息");
while (sc.hasNext()) {
String message = sc.nextLine();
//就改变error和下面的交换机相同即可
channel.basicPublish(EXCHANGE_NAME, "error", null, message.getBytes("UTF-8"));
System.out.println("生产者发出消息" + message);
}
}
}
}
消费者1
public class ReceiveLogsDirect01 {
private static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] argv) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
//声明交换机 direct直接交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
String queueName = "console";
//交换机 声明一个队列
channel.queueDeclare(queueName, false, false, false, null);
//绑定 后面是info 就是routingkey
channel.queueBind(queueName, EXCHANGE_NAME, "info");
channel.queueBind(queueName, EXCHANGE_NAME, "warning");
System.out.println("等待接收消息.....");
//接收信息
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println("ReceiveLogsDirect02接收绑定键:"+delivery.getEnvelope().getRoutingKey()+",消息:"+message);
};
//消费者取消时的消息回调
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
});
}
}
消费者2
public class ReceiveLogsDirect02 {
private static final String EXCHANGE_NAME = "direct_logs";
public static void main(String[] argv) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
//声明交换机 direct直接交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
String queueName = "disk";
//交换机 声明一个队列
channel.queueDeclare(queueName, false, false, false, null);
//绑定 后面是info 就是routingkey
channel.queueBind(queueName, EXCHANGE_NAME, "error");
System.out.println("等待接收消息.....");
//接收信息
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println("ReceiveLogsDirect02接收绑定键:"+delivery.getEnvelope().getRoutingKey()+",消息:"+message);
};
//消费者取消时的消息回调
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
});
}
}
Topic主题交换机
必须是单词列表 以点号分割eg: quick.orange.rabbit
*(星号)可以代替一个单词 eg: *.orange.*
#(井号)可以替代零个或多个单词 eg:(lazy.#)
更多的例子
quick.orange.rabbit 被队列 Q1Q2 接收到
lazy.orange.elephant 被队列 Q1Q2 接收到
quick.orange.fox 被队列 Q1 接收到
lazy.brown.fox 被队列 Q2 接收到
lazy.pink.rabbit 虽然满足两个绑定但只被队列 Q2 接收一次
quick.brown.fox 不匹配任何绑定不会被任何队列接收到会被丢弃
quick.orange.male.rabbit 是四个单词不匹配任何绑定会被丢弃
lazy.orange.male.rabbit 是四个单词但匹配 Q2
当队列绑定关系是下列这种情况时需要引起注意
当一个队列绑定键是#,那么这个队列将接收所有数据,就有点像 fanout 了
如果队列绑定键当中没有#和*出现,那么该队列绑定类型就是 direct 了
消费者C1
public class ReceiveLogsTopic01 {
//交换机名字
private static final String EXCHANGE_NAME = "topic_logs";
//接收消息
public static void main(String[] argv) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
//声明交换机 topic主题交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
String queueName = "Q1";
//声明一个队列
channel.queueDeclare(queueName, false, false, false, null);
//绑定 第三个就是routingkey
channel.queueBind(queueName, EXCHANGE_NAME, "*.orange.*");
System.out.println("等待接收消息.....");
//接收信息
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println("ReceiveLogsTopic01接收绑定键:"+delivery.getEnvelope().getRoutingKey()+",消息:"+message+"接收队列:"+queueName);
};
//消费者取消时的消息回调
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
});
}
}
消费者02
public class ReceiveLogsTopic02 {
//交换机名字
private static final String EXCHANGE_NAME = "topic_logs";
//接收消息
public static void main(String[] argv) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
//声明交换机 topic主题交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
String queueName = "Q2";
//声明一个队列
channel.queueDeclare(queueName, false, false, false, null);
//绑定 第三个就是routingkey
channel.queueBind(queueName, EXCHANGE_NAME, "*.*.rabbit");
channel.queueBind(queueName, EXCHANGE_NAME, "lazy.#");
System.out.println("等待接收消息.....");
//接收信息
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println("ReceiveLogsTopic02接收绑定键:"+delivery.getEnvelope().getRoutingKey()+",消息:"+message+"接收队列:"+queueName);
};
//消费者取消时的消息回调
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {
});
}
}
生产者
public class EmitLogTopic {
//交换机的名称
private static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] argv) throws Exception {
try (Channel channel = RabbitMqUtils.getChannel()) {
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
/**
* Q1-->绑定的是
* 中间带 orange 带 3 个单词的字符串(*.orange.*)
* Q2-->绑定的是
* 最后一个单词是 rabbit 的 3 个单词(*.*.rabbit)
* 第一个单词是 lazy 的多个单词(lazy.#)
*
*/
Map<String, String> bindingKeyMap = new HashMap<>();
bindingKeyMap.put("quick.orange.rabbit","被队列 Q1Q2 接收到");
bindingKeyMap.put("lazy.orange.elephant","被队列 Q1Q2 接收到");
bindingKeyMap.put("quick.orange.fox","被队列 Q1 接收到");
bindingKeyMap.put("lazy.brown.fox","被队列 Q2 接收到");
bindingKeyMap.put("lazy.pink.rabbit","虽然满足两个绑定但只被队列 Q2 接收一次");
bindingKeyMap.put("quick.brown.fox","不匹配任何绑定不会被任何队列接收到会被丢弃");
bindingKeyMap.put("quick.orange.male.rabbit","是四个单词不匹配任何绑定会被丢弃");
bindingKeyMap.put("lazy.orange.male.rabbit","是四个单词但匹配 Q2");
for (Map.Entry<String, String> bindingKeyEntry: bindingKeyMap.entrySet()){
String routingKey = bindingKeyEntry.getKey();
String message = bindingKeyEntry.getValue();
channel.basicPublish(EXCHANGE_NAME,routingKey, null, message.getBytes("UTF-8"));
System.out.println("生产者发出消息" + message);
}
}
}
}