练习

递减元素使数组呈锯齿状

给你一个整数数组 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 以前可能学过忘了,又死在这了,思路不难懂,就是不会写。

附上大神代码

大体思路

两个链表相加,位数不够补零

就把它们当成普通算数 只不过以前是先算个位 正着写

而链表就相当于把个位放开头了

另外答案按逆序写 拿出来还是那个数

所以用指针去找

  1. 定义一个指针cur = prew去向后移位,用来存储两数字之和
  2. 定义一个指针carry存进位,用来存储大于10的数
  3. 定义一个指针prew定起始位,用来返回最终结果
  4. 循环双节点,直到一方或双方节点为null时,赋值0(用来保持同位,总不能9+NULL吧)
  5. 每次相加都会返回sum,就可以算出carry 所以sum=l1+l2+carry,同时进位sum取余
  6. 每个节点存一个值,需要让cur创建新节点再赋值 移动cur指针到新节点
  7. 做判断,如果l1l2不为null 每次cur移动后 它们也要节点后移
  8. 最后判断 如果carry还有进位数 就创建一个新节点存carry
  9. 最后返回就是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第三天..

发布信息

发布信息原理




java 提前三天和提前一天提醒 提前3天是什么意思_Powered by 金山文档


发布确认 就是队列收到生产者发来的信息后 返回信息给生产者

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();

    }
异步确认发布

性价比最高 又可靠又效率 通过函数回调来保证是否投递成功

原理图:


java 提前三天和提前一天提醒 提前3天是什么意思_Powered by 金山文档_02


//异步发布确认
    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本身有一个默认交换机

我们假设的是工作队列背后,每个任务都恰好交付给一个消费者(工作进程)。

在这一部分中,我们将做一些完全不同的事情-我们将消息传达给多个消费者。这种模式

称为 ”发布/订阅

原理图


java 提前三天和提前一天提醒 提前3天是什么意思_赋值_03


交换机的介绍

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.#)


java 提前三天和提前一天提醒 提前3天是什么意思_java 提前三天和提前一天提醒_04


更多的例子

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);
            }
        }
    }
}