主頁 > 後端開發 > 面試必問:RabbitMQ 有哪幾種訊息模式?

面試必問:RabbitMQ 有哪幾種訊息模式?

2023-05-25 07:28:52 後端開發

原文:juejin.cn/post/6998363970037874724

前言

Rabbitmq 是使用 Erlang 語言開發的開源訊息佇列系統,基于 AMQP 實作,是一種應用程式對應用程式的通信方法,應用程式通過讀寫出入佇列的訊息來通信,而無需專用連接來鏈接它們,訊息傳遞指的是應用程式之間通過在訊息中發送資料進行通信,而不是通過直接呼叫彼此通信,直接呼叫通常是指遠程程序呼叫的技術,

核心組成

  • Server:又稱 Broker,接收客戶端的連接,實作 AMQP 物體服務,安裝 rabbitmq-server
  • Connection:連接,應用程式與Broker的網路連接TCP/IP/三次握手和四次揮手
  • Channel:網路信道,幾乎所有操作都在 Channel 中進行,Channel 是進行訊息讀寫的通道,客戶端可以建立多個 Channel,每個 Channel 代表一個會話任務,
  • Message:訊息,服務與應用程式之間傳送的資料,由 Properties 和 Body 組成,Properties 可以對訊息進行修飾,比如訊息的優先級,延遲等高級特性,Body 則是訊息體的內容,
  • Virtual Host:虛擬地址,用于進行邏輯隔離,最上層的訊息路由,一個虛擬主機可以有若干個 exchange 和 queue,同一個虛擬主機里面不能有相同名稱的 exchange
  • Exchange:交換機,接收訊息,根據路由鍵發送訊息到系結的佇列(不具備訊息存盤能力)
  • Bindings:exchange 和 queue 之間的虛擬連接,binding 中可以保存多個 routing key
  • Routing key:是一個路由規則,虛擬機可以用它來確定如何路由一個特定訊息
  • Queue:佇列,也稱為 Message Queue,訊息佇列,保存訊息并將它們轉發給消費者

Rabbitmq 訊息模式

3.1 Simple 模式

Simple 模式是最簡單的一個模式,由一個生產者,一個佇列,一個消費者組成,生產者將訊息通過交換機(此時,圖中并沒有交換機的概念,如不定義交換機,會使用默認的交換機)把訊息存盤到佇列,消費者從佇列中取出訊息進行處理,

用 Java demo 實作此模式,推薦一個開源免費的 Spring Boot 最全教程:

https://github.com/javastacks/spring-boot-best-practice

Productor

public class Send {
    private final static String QUEUE_NAME = "queue1";

    public static void main(String[] args) {
        // 1、創建連接工程
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.96.109");
        factory.setVirtualHost("/");

        Connection connection = null;
        Channel channel = null;

        try {
            // 2、創建連接、通道
            connection = factory.newConnection();
            channel = connection.createChannel();
            // 3、宣告佇列
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            // 訊息內容
            String message = "Hello world";
            // 4、發送訊息到指定佇列
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
            System.out.println(" [x] Sent '" + message + "'");
        } catch (TimeoutException | IOException e) {
            e.printStackTrace();
        } finally {
            // 關閉通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            // 關閉連接
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Customer

public class Recv {
    private final static String QUEUE_NAME = "queue1";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 1、創建連接工程
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.96.109");
        factory.setVirtualHost("/");

        // 2、獲取 Connection和 Channel
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        // 3、宣告佇列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [x] Received '" + message + "'");
        };
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {
        });
    }
}

觀察可視化界面,會看到訊息先會被寫入到佇列中,隨后又被消費者消費了,

3.2 Fanout 模式

Fanout——發布訂閱模式,是一種廣播機制,

此模式包括:一個生產者、一個交換機 (exchange)、多個佇列、多個消費者,生產者將訊息發送到交換機,交換機不存盤訊息,將訊息存盤到佇列,消費者從佇列中取訊息,如果生產者將訊息發送到沒有系結佇列的交換機上,訊息將丟失,

用 Java demo 實作此模式

Productor

public class Productor {
   private static final String EXCHANGE_NAME = "fanout_exchange";

   public static void main(String[] args) {
       // 1、創建連接工程
       ConnectionFactory factory = new ConnectionFactory();
       factory.setHost("192.168.96.109");
       factory.setUsername("admin");
       factory.setPassword("admin");
       factory.setVirtualHost("/");

       Connection connection = null;
       Channel channel = null;
       try {
           // 2、獲取連接、通道
           connection = factory.newConnection();
           channel = connection.createChannel();
           // 訊息內容
           String message = "hello fanout mode";
           // 指定路由key
           String routeKey = "";
           String type = "fanout";
           // 3、宣告交換機
           channel.exchangeDeclare(EXCHANGE_NAME, type);
           // 4、宣告佇列
           channel.queueDeclare("queue1", true, false, false, null);
           channel.queueDeclare("queue2", true, false, false, null);
           channel.queueDeclare("queue3", true, false, false, null);
           channel.queueDeclare("queue4", true, false, false, null);
           // 5、系結 channel 與 queue
           channel.queueBind("queue1", EXCHANGE_NAME, routeKey);
           channel.queueBind("queue2", EXCHANGE_NAME, routeKey);
           channel.queueBind("queue3", EXCHANGE_NAME, routeKey);
           channel.queueBind("queue4", EXCHANGE_NAME, routeKey);
           // 6、發布訊息
           channel.basicPublish(EXCHANGE_NAME, routeKey, null, message.getBytes("UTF-8"));
           System.out.println("訊息發送成功!");
       } catch (IOException | TimeoutException e) {
           e.printStackTrace();
           System.out.println("訊息發送例外");
       }finally {
           // 關閉通道和連接......
       }
   }
}

Customer

public class Customer {
    private static Runnable runnable = new Runnable() {
        @Override
        public void run() {
            // 創建連接工廠
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("192.168.96.109");
            factory.setUsername("admin");
         factory.setPassword("admin");
         factory.setVirtualHost("/");

            final String queueName = Thread.currentThread().getName();
            Connection connection = null;
            Channel channel = null;
            try {
                // 獲取連接、通道
                connection = factory.newConnection();
                channel = connection.createChannel();

                Channel finalChannel = channel;
                finalChannel.basicConsume(queueName, true, new DeliverCallback() {
                    @Override
                    public void handle(String consumerTag, Delivery delivery) throws IOException {
                        System.out.println(delivery.getEnvelope().getDeliveryTag());
                        System.out.println(queueName + ":收到訊息是:" + new String(delivery.getBody(), "UTF-8"));
                    }
                }, new CancelCallback() {
                    @Override
                    public void handle(String consumerTag) throws IOException {
                    }
                });
                System.out.println(queueName + ":開始接收訊息");
            } catch (IOException |
                    TimeoutException e) {
                e.printStackTrace();
            } finally {
                // 關閉通道和連接......
            }
        }

    };

    public static void main(String[] args) throws IOException, TimeoutException {
     // 創建執行緒分別從四個佇列中獲取訊息
        new Thread(runnable, "queue1").start();
        new Thread(runnable, "queue2").start();
        new Thread(runnable, "queue3").start();
        new Thread(runnable, "queue4").start();
    }
}

執行完 Productor 發現四個佇列中分別增加了一條訊息,而執行完 Customer 后四個佇列中的訊息都被消費者消費了,

3.3 Direct 模式

Direct 模式是在 Fanout 模式基礎上添加了 routing key,Fanout(發布/訂閱)模式是交換機將訊息存盤到所有系結的佇列中,而 Direct 模式是在此基礎上,添加了過濾條件,交換機只會將訊息存盤到滿足 routing key 的佇列中,

在上圖中,我們可以看到交換機系結了兩個佇列,其中佇列 Q1系結的 routing key 為 “orange” ,佇列Q2系結的routing key 為 “black” 和 “green”,在這樣的設定中,發布 routing key 為 “orange” 的訊息將被路由到 Q1,routing key 為 “black” 或 “green” 的訊息將被路由到 Q2

在 rabbitmq 中給佇列系結 routing_key,routing_key 必須是單詞串列

用 Java demo 實作此模式

Productor

public class Productor {
    private static final String EXCHANGE_NAME = "direct_exchange";

    public static void main(String[] args) {
        // 1、創建連接工程
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.96.109");
        factory.setUsername("admin");
        factory.setPassword("admin");
        factory.setVirtualHost("/");

        Connection connection = null;
        Channel channel = null;
        try {
            // 2、獲取連接、通道
            connection = factory.newConnection();
            channel = connection.createChannel();
            // 訊息內容
            String message = "hello direct mode";
            // 指定路由key
            String routeKey = "email";
            String type = "direct";
            // 3、宣告交換機
            channel.exchangeDeclare(EXCHANGE_NAME, type);
            // 4、宣告佇列
            channel.queueDeclare("queue1", true, false, false, null);
            channel.queueDeclare("queue2", true, false, false, null);
            channel.queueDeclare("queue3", true, false, false, null);
            // 5、系結 channel 與 queue
            channel.queueBind("queue1", EXCHANGE_NAME, "email");
            channel.queueBind("queue2", EXCHANGE_NAME, "sms");
            channel.queueBind("queue3", EXCHANGE_NAME, "vx");
   // 6、發布訊息
            channel.basicPublish(EXCHANGE_NAME, routeKey, null, message.getBytes("UTF-8"));
            System.out.println("訊息發送成功!");
        } catch (IOException | TimeoutException e) {
            e.printStackTrace();
            System.out.println("訊息發送例外");
        } finally {
            // 關閉通道和連接......
        }
    }
}

可以通過可視化頁面查看,各佇列系結的 routing_key

由于設定的 routing_key為 “email”,所以,應該只有 queue1 存盤了一條訊息,

Customer 與上述 fanout 示例一致,

3.4 Topic 模式

Topic 模式是生產者通過交換機將訊息存盤到佇列后,交換機根據系結佇列的 routing key 的值進行通配符匹配,如果匹配通過,訊息將被存盤到該佇列,如果 routing key 的值匹配到了多個佇列,訊息將會被發送到多個佇列;如果一個佇列也沒匹配上,該訊息將丟失,

routing_key 必須是單詞串列,用點分隔,其中 * 和 # 的含義為:

  • *:1個單詞
  • #:0個或多個單詞

用Java demo 實作此模式

Productor

public class Productor {
    private static final String EXCHANGE_NAME = "topic_exchange";

    public static void main(String[] args) {
        // 1、創建連接工程
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.96.109");
        factory.setUsername("admin");
        factory.setPassword("admin");
        factory.setVirtualHost("/");

        Connection connection = null;
        Channel channel = null;
        try {
           // 2、獲取連接、通道
            connection = factory.newConnection();
            channel = connection.createChannel();
            // 訊息內容
            String message = "hello topic mode";
            // 指定路由key
            String routeKey = "com.order.test.xxx";
            String type = "topic";
            // 3、宣告交換機
            channel.exchangeDeclare(EXCHANGE_NAME, type);
            // 4、宣告佇列
            channel.queueDeclare("queue5",true,false,false,null);
            channel.queueDeclare("queue6",true,false,false,null);
            // 5、系結 channel 與 queue
            channel.queueBind("queue5", EXCHANGE_NAME, "*.order.#");
            channel.queueBind("queue6", EXCHANGE_NAME, "#.test.*");
            // 6、發布訊息
            channel.basicPublish(EXCHANGE_NAME, routeKey, null, message.getBytes("UTF-8"));
            System.out.println("訊息發送成功!");
        } catch (IOException | TimeoutException e) {
            e.printStackTrace();
            System.out.println("訊息發送例外");
        } finally {
            // 關閉通道和連接......
        }
    }
}

執行完 Productor 后,通過可視化頁面查看到,queue 系結的 routing_key

由于上述例子中,routing_key為:“com.order.test.xxx”,那么 queue5 和 queue6 都將接收到訊息,

Customer 與上述實體一樣,執行完 Customer 后,再次查看佇列資訊,queue5 和 queue6 的訊息都被消費了,

3.5 Work 模式

當有多個消費者時,如何均衡訊息者消費訊息的多少,主要有兩種模式:

  • 輪詢模式分發:按順序輪詢分發,每個消費者獲得相同數量的訊息
  • 公平分發:根據消費者消費能力公平分發,處理快的處理的多,處理慢的處理的少,按勞分配

3.5.1 輪詢分發

在這種模式下,rabbitmq 采用輪詢的方式將任務分配給多個消費者,但可能出現一種情況,當分配給某一個消費者的任務很復雜時,而有些消費者接收的任務較輕量,會出現有的消費者很忙,而有的消費者處于空閑的狀態,而 rabbitmq 不會感知到這種情況的發生,rabbitmq 不考慮消費者未確認訊息的數量,只是盲目的分配任務,

用 Java demo 實作此模式

Productor

public class Productor {
    public static void main(String[] args) {
        // 1、創建連接工程
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.96.109");
        factory.setUsername("admin");
        factory.setPassword("admin");
        factory.setVirtualHost("/");

        Connection connection = null;
        Channel channel = null;
        try {
            // 2、獲取連接、通道
            connection = factory.newConnection();
            channel = connection.createChannel();

            // 3、向 Queue1 發布20個訊息
            for (int i = 0; i < 20; i++) {
                String msg = "feiyangyang: " + i;
                channel.basicPublish("", "queue1", null, msg.getBytes(StandardCharsets.UTF_8));
            }
            System.out.println("訊息發送成功!");
        } catch (IOException | TimeoutException e) {
            e.printStackTrace();
            System.out.println("訊息發送例外");
        } finally {
            // 關閉通道和連接......
        }
    }
}

Worker1

public class Worker1 {
    public static void main(String[] args) {
        // 1、創建連接工廠
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.96.109");
        factory.setUsername("admin");
        factory.setPassword("admin");
        factory.setVirtualHost("/");

        Connection connection = null;
        Channel channel = null;
        try {
            // 獲取連接、通道
            connection = factory.newConnection();
            channel = connection.createChannel();
            Channel finalChannel = channel;
            finalChannel.basicConsume("queue1", true, new DeliverCallback() {
                @Override
                public void handle(String consumerTag, Delivery delivery) throws IOException {
                    System.out.println("Worker1" + ":收到訊息是:" + new String(delivery.getBody(), "UTF-8"));
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String consumerTag) throws IOException {
                }
            });
            System.out.println("Worker1 開始接收訊息");
            System.in.read();
        } catch (IOException |
                TimeoutException e) {
            e.printStackTrace();
        } finally {
            // 關閉通道和連接......
        }
    }
}

Worker2 與 Worker1 相同

我們看下訊息分發結果:

Worker1 開始接收訊息
Worker1:收到訊息是:feiyangyang: 0
Worker1:收到訊息是:feiyangyang: 2
Worker1:收到訊息是:feiyangyang: 4
Worker1:收到訊息是:feiyangyang: 6
Worker1:收到訊息是:feiyangyang: 8
Worker1:收到訊息是:feiyangyang: 10
Worker1:收到訊息是:feiyangyang: 12
Worker1:收到訊息是:feiyangyang: 14
Worker1:收到訊息是:feiyangyang: 16
Worker1:收到訊息是:feiyangyang: 18

Worker2 開始接收訊息
Worker2:收到訊息是:feiyangyang: 1
Worker2:收到訊息是:feiyangyang: 3
Worker2:收到訊息是:feiyangyang: 5
Worker2:收到訊息是:feiyangyang: 7
Worker2:收到訊息是:feiyangyang: 9
Worker2:收到訊息是:feiyangyang: 11
Worker2:收到訊息是:feiyangyang: 13
Worker2:收到訊息是:feiyangyang: 15
Worker2:收到訊息是:feiyangyang: 17
Worker2:收到訊息是:feiyangyang: 19

可以看出,輪詢分發模式就是將訊息均衡的分配所有消費者,

3.5.2 公平分發

為了解決 Work 輪詢分發模式 這個問題,rabbitmq 使用帶有 perfetchCount = 1 設定的 basicQos 方法,當消費者接受處理并確認前一條訊息前,不向此消費者發送新訊息,會分配給其他空閑的消費者,

Productor 代碼與上述輪詢模式相同,而 Customer 中稍作修改

Worker1

// Channel 使用 Qos 機制
finalChannel.basicQos(1);
finalChannel.basicConsume("queue1", false, new DeliverCallback() {
    @Override
    public void handle(String consumerTag, Delivery delivery) throws IOException {
        System.out.println("Worker1" + ":收到訊息是:" + new String(delivery.getBody(), "UTF-8"));
        try {
            Thread.sleep(1000);
            // 改成手動應答
            finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}, new CancelCallback() {
    @Override
    public void handle(String consumerTag) throws IOException {
    }
});

上述實體相較于輪詢分發模式,添加了 Qos 機制,設定值為1,代表消費者每次從佇列中獲取幾條訊息,將 Worker1 的 sleep 時間設定為 1s,將 Worker2 的 sleep 時間設定為 2s,查看訊息分發結果

Worker1 開始接收訊息
Worker1:收到訊息是:feiyangyang: 0
Worker1:收到訊息是:feiyangyang: 2
Worker1:收到訊息是:feiyangyang: 4
Worker1:收到訊息是:feiyangyang: 5
Worker1:收到訊息是:feiyangyang: 7
Worker1:收到訊息是:feiyangyang: 8
Worker1:收到訊息是:feiyangyang: 10
Worker1:收到訊息是:feiyangyang: 11
Worker1:收到訊息是:feiyangyang: 13
Worker1:收到訊息是:feiyangyang: 14
Worker1:收到訊息是:feiyangyang: 16
Worker1:收到訊息是:feiyangyang: 17
Worker1:收到訊息是:feiyangyang: 19
Worker2 開始接收訊息
Worker2:收到訊息是:feiyangyang: 1
Worker2:收到訊息是:feiyangyang: 3
Worker2:收到訊息是:feiyangyang: 6
Worker2:收到訊息是:feiyangyang: 9
Worker2:收到訊息是:feiyangyang: 12
Worker2:收到訊息是:feiyangyang: 15
Worker2:收到訊息是:feiyangyang: 18

當使用 Work 公平分發模式時,要設定消費者為手動應答,并且開啟 Qos 機制,

防止訊息丟失機制

4.1 訊息確認

消費者完成一項任務可能需要幾秒鐘,如果其中一個消費者開始了一項長期任務并且只完成了部分任務而死亡,如果將 autoAck 設定為 true ,一旦 RabbitMQ 將訊息傳遞給消費者,它會立即將其標記為洗掉,在這種情況下,我們將丟失所有已分派給該特定消費者但尚未處理的訊息,

如果其中一個消費者宕了,rabbitmq 可以將其訊息分配給其他消費者,為了確保訊息不會丟失,rabbitmq 采用訊息確認,消費者發回確認訊息,告訴 rabbitmq 訊息已經被接收并處理,此時,rabbitmq 可以放心的洗掉這條訊息,

如果消費者在沒有發送 ack 的情況下宕了,rabbitmq 將理解為該條訊息未被消費者處理完,如果有其他消費者在線,將迅速重新交付給其他消費者,這樣就可以確保不會丟失訊息了,

默認情況下rabbitmq 會啟用手動訊息確認,也就是 autoAck 默認為 false,一旦我們完成了一項任務,需要手動的進行訊息確認,所以 autoAck 需要保持為默認值 false,并使用如下方法進行手動應答,

channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);

4.2 持久化

rabbitmq 的訊息確認機制可以保證訊息不會丟失,但是如果 rabbitmq 服務器停止,我們的任務仍然會丟失,

當 rabbitmq 退出或崩潰時,如果不進行持久化,佇列和訊息都會消失,需要做兩件事來確保訊息不會丟失,將佇列和訊息都標記為持久的,

  1. 設定佇列持久
boolean durable = true;
channel.queueDeclare("hello", durable, false, false, null);
  1. 設定訊息持久
channel.basicPublish("", "task_queue", MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());

將訊息標記為持久性并不能完全保證訊息不會丟失,當 rabbitmq 接收到訊息并且還沒保存時,仍然有很短的時間視窗會使訊息丟失,如果需要更強的保證,可以使用發布者確認機制,

使用場景

解耦、削峰、異步

解耦

在微服務架構體系中,微服務A需要與微服務B進行通信,傳統的做法是A呼叫B的介面,但這樣做如果系統B無法訪問或連接超時,系統A需要等待,直到系統B做出回應,并且A與B存在嚴重的耦合現象,如果引入訊息佇列進行系統AB的通信,流程是這樣的:

  • 系統A將訊息存盤到訊息佇列中,回傳成功資訊
  • 系統B從佇列中獲取訊息,進行處理操作

系統A將訊息放到佇列中,就不用關心系統B是否可以獲取等其他事情了,實作了兩個系統間的解耦,

使用場景:

  • 短信、郵件通知

削峰

系統A每秒請求100個,系統可以穩定運行,但如果在秒殺活動中,每秒并發達到1w個,但系統最大處理能力只能每秒處理 1000 個,所以,在秒殺活動中,系統服務器會出現宕機的現象,如果引入 MQ ,可以解決這個問題,每秒 1w個請求會導致系統崩潰,那我們讓用戶發送的請求都存盤到佇列中,由于系統最大處理能力是每秒1000個請求,讓系統A每秒只從佇列中拉取1000個請求,保證系統能穩定運行,在秒殺期間,請求大量進入到佇列,積壓到MQ中,而系統每秒只從佇列中取1000個請求處理,這種短暫的高峰期積壓是沒問題的,因為高峰期一旦過去,每秒請求數迅速遞減,而系統每秒還是從佇列中取1000個請求進行處理,系統會快速將積壓的訊息消費掉,

使用場景:

  • 秒殺活動
  • 團搶活動

異步

用戶注冊,需要發送注冊郵件和注冊短信,傳統的做法有兩種:串行、并行,

  • 串行方式:將注冊資訊寫庫后(50ms),發送郵件(50ms),再發送短信(50ms),任務完成后,回傳客戶端,共耗時(150ms)
  • 并行方式:將注冊資訊寫庫后(50ms),開啟子執行緒讓發送郵件和發送短信同時進行(50ms),回傳客戶端,共耗時(100ms)
  • 引入MQ,將注冊資訊寫庫(50ms),將發送郵件和短信的操作寫入佇列(5s),回傳客戶端,而消費者什么時候從佇列中取訊息進行處理,不用關心,共耗時(55ms)

使用場景:

  • 將不是必須等待回應結果的業務邏輯進行異步處理

近期熱文推薦:

1.1,000+ 道 Java面試題及答案整理(2022最新版)

2.勁爆!Java 協程要來了,,,

3.Spring Boot 2.x 教程,太全了!

4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!

5.《Java開發手冊(嵩山版)》最新發布,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/553254.html

標籤:Java

上一篇:Markdown標題自動添加編號

下一篇:返回列表

標籤雲
其他(159587) Python(38165) JavaScript(25446) Java(18112) C(15231) 區塊鏈(8268) C#(7972) AI(7469) 爪哇(7425) MySQL(7208) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5873) 数组(5741) R(5409) Linux(5340) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4576) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2433) ASP.NET(2403) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1976) 功能(1967) Web開發(1951) HtmlCss(1942) C++(1922) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1878) .NETCore(1861) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • 面試必問:RabbitMQ 有哪幾種訊息模式?

    原文:juejin.cn/post/6998363970037874724 ## **前言** Rabbitmq 是使用 Erlang 語言開發的開源訊息佇列系統,基于 AMQP 實作,是一種應用程式對應用程式的通信方法,應用程式通過讀寫出入佇列的訊息來通信,而無需專用連接來鏈接它們。訊息傳遞指的是 ......

    uj5u.com 2023-05-25 07:28:52 more
  • Markdown標題自動添加編號

    用`markdown`寫檔案很方便,但是有個困擾的地方,就是標題的編號問題。 寫檔案的時候,經常會在中間插入新的標題和內容,所以手動管理編號的話,如果新的標題插在前面,則要調整后面所有的編號。 如果在檔案完成后再手動加上編號的話,不僅容易忘記, 而且有時候我們是在其他編輯器里編輯檔案再匯出`mark ......

    uj5u.com 2023-05-25 07:21:50 more
  • JAVA8新特性

    # Lambda運算式 ## 1.基本格式 ~~~java (引數串列)->{代碼} ~~~ ## 2.省略規則 - 引數型別可以省略 - 方法體只有一句代碼時大括號return和唯一一句代碼的分號可以省略 - 方法只有一個引數時小括號可以省略 # Stream流 ## 1.創建流 - 單列集合:集 ......

    uj5u.com 2023-05-24 08:21:31 more
  • 什么是 Spring?為什么學它?

    歡迎來到本篇文章!在這里,我將帶領大家快速學習 Spring 的基本概念,并解答兩個關鍵問題:什么是 Spring,以及為什么學習 Spring。

    廢話少說,下面,我們開始吧! ......

    uj5u.com 2023-05-24 08:21:24 more
  • 爬蟲——服務器渲染和客戶端渲染

    # xxxx爬蟲——服務器渲染和客戶端渲染 [toc] ## 服務器渲染 - 專業解釋 服務器渲染(Server-Side Rendering,SSR)是一種在服務器端完成頁面渲染的網頁處理技術。具體來說,就是服務器在回應客戶端請求時,會生成頁面的HTML代碼,并將其回傳給客戶端。這種方式的優點包括 ......

    uj5u.com 2023-05-24 08:21:17 more
  • 【2023.03.20】P4710 「物理」平拋運動

    題目傳送門: >[【洛谷】P4710 [物理]平拋運動](https://www.luogu.com.cn/problem/P4710 "【洛谷】P4710 [物理]平拋運動") ## Step 1:前置芝士 您需要知道并了解以下芝士: 1. 數學: - 三角函式; 2. 物理: - 加速度公式; ......

    uj5u.com 2023-05-24 08:21:14 more
  • flask_SQLAlchemy 出現了 Lost connection to MySQL server duri

    使用python flask框架 flask_sqlalchemy 時出現了 Lost connection to MySQL server during query Mysql主機連接超時的問題 由于Mysql會定時處理長時間未連接使用的連接池 具體時長可通過 查看 show variables ......

    uj5u.com 2023-05-24 08:21:09 more
  • Spring原始碼:Bean生命周期(終章)

    本系列前面講解了Spring的bean定義、bean實體化、bean初始化等生命周期階段。這些步驟使我們能夠了解bean從創建到準備好使用所經歷的程序。但是,除了這些步驟,bean的銷毀也是非常重要的一步。在本系列的最后,我們將深入探討bean的銷毀程序,包括在什么情況下會發生銷毀、銷毀的順序以及如... ......

    uj5u.com 2023-05-24 08:21:02 more
  • PHP 獲取無限級下級ID 無層級

    PHP 獲取無限級下級ID 無層級 非遞回 洗掉會員處有誤,修復后上傳記錄, PHP 獲取無限級下級ID 無層級 非遞回 洗掉會員處有誤,修復后上傳記錄, PHP 獲取無限級下級ID 無層級 非遞回 洗掉會員處有誤,修復后上傳記錄, PHP 獲取無限級下級ID 無層級 非遞回 洗掉會員處有誤,修復后 ......

    uj5u.com 2023-05-24 08:20:57 more
  • java IO流

    # Java IO流 ## 什么是流? 概念:記憶體和存盤設備之間傳輸資料的通道。 資料借助流傳輸。 流分類: - 按照方向:輸入流(將存盤設備中的內容讀入到記憶體中)和輸出流(將記憶體中的內容寫入到存盤設備中) - 按照單位:位元組流(以位元組為單位,可以讀寫所有資料)和字符流(以字符為單位,只能讀取文本數 ......

    uj5u.com 2023-05-24 08:20:43 more