Java中實現(xiàn)訂單超時自動取消功能(最新推薦)
在開發(fā)中,我們會遇到需要延時任務(wù)的業(yè)務(wù)場景,例如:用戶下單之后未在規(guī)定的時間內(nèi)支付成功,該訂單會自動取消; 用戶注冊成功15分鐘后,發(fā)消息通知用戶;還有比如到期自動收貨,超時自動退款等都是類似的延時任務(wù)的業(yè)務(wù)問題。
這里主要介紹一下幾種方法:
- 1、定時任務(wù)
- 2、JDK延遲隊列 DelayQueue
- 3、redis過期監(jiān)聽
- 4、Redisson分布式延遲隊列
- 5、RocketMQ延遲消息
- 6、RabbitMQ死信隊列
1、定時任務(wù)
寫一個定時任務(wù),定期掃描數(shù)據(jù)庫中的訂單,如果時間過期,就取消這個訂單。這種實現(xiàn)方法成本低、實現(xiàn)容易。這里使用@Scheduled注解實現(xiàn),也可以用Quartz框架實現(xiàn)定時任務(wù)。
@Scheduled(cron = "30 * * * * ?") public void scanOrder(){ orderService.scanOrder(); //每30秒掃描數(shù)據(jù)庫 找出過期未支付的訂單,取消該訂單 }
優(yōu)點:實現(xiàn)容易,成本低,不依賴其他組件。
缺點:
- 時間不夠精確。因為掃描是有間隔的,但卻隨時會產(chǎn)生過期的訂單,所以可能會導(dǎo)致有些訂單已經(jīng)過期了一段時間后才被掃描到。
- 增加了數(shù)據(jù)庫的壓力。頻繁的訪問數(shù)據(jù)庫,當(dāng)數(shù)據(jù)越來越多時,訪問數(shù)據(jù)庫的成本也會增加。
2、JDK延遲隊列 DelayQueue
DelayQueue是JDK提供的一個無界隊列,它的本質(zhì)是封裝了一個PriorityQueue(優(yōu)先隊列), PriorityQueue內(nèi)部使用完全二叉堆來實現(xiàn)隊列排序,在向隊列中插入元素時,需要給出這個元素的Delay時間,也就是過期時間,隊列中最小的元素會被放在隊首,隊列中的元素只有到了Delay時間才允許從隊列中取出。
具體的實現(xiàn)思路就是:首先創(chuàng)建一個實體類實現(xiàn)Delay接口,然后將它放入DelayQueue隊列中。
(1)定義實現(xiàn)Delayed接口的實體類
需要實現(xiàn)Delayed接口的兩個方法:getDelay()和compareTo()
import com.fasterxml.jackson.annotation.JsonFormat; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; @Data @AllArgsConstructor @NoArgsConstructor public class MyDelay implements Delayed { private String orderNumber; //訂單編號 @JsonFormat(locale = "zh", timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") private Long time; //過期時間 @Override public long getDelay(TimeUnit timeUnit) { return time - System.currentTimeMillis(); } @Override public int compareTo(Delayed delayed) { MyDelay myDelay = (MyDelay)delayed; return this.time.compareTo(myDelay.getTime()); } }
(2)將延時任務(wù)放入隊列
package com.demo; import com.demo.config.MyDelay; import java.util.concurrent.DelayQueue; public class demo { public static void main(String[] args) throws InterruptedException { MyDelay myDelay1 = new MyDelay("0001", 5L); MyDelay myDelay2 = new MyDelay("0002", 10L); MyDelay myDelay3 = new MyDelay("0003", 15L); DelayQueue<MyDelay> delayDelayQueue = new DelayQueue<MyDelay>(); delayDelayQueue.add(myDelay1); delayDelayQueue.add(myDelay2); delayDelayQueue.add(myDelay3); while (delayDelayQueue.size()!=0) { /** * 取隊列頭部元素是否過期 */ //DelayQueue的put/add方法是線程安全的,因為put/add方法內(nèi)部使用了ReentrantLock鎖進(jìn)行線程同步。 // DelayQueue還提供了兩種出隊的方法 poll() 和 take() , // poll() 為非阻塞獲取,沒有到期的元素直接返回null; // take() 阻塞方式獲取,沒有到期的元素線程將會等待。 MyDelay order = delayDelayQueue.poll(); if(order!=null) { System.out.println("訂單編號:"+order.getOrderNumber()+",超時取消!"); } Thread.sleep(1000); } } }
優(yōu)點:不依賴任何第三方組件,實現(xiàn)方便。
缺點:因為DelayQueue是基于JVM的,如果放入的訂單過多,會造成JVM溢出。如果JVM重啟了,那所有的數(shù)據(jù)就丟失了。
3、redis過期監(jiān)聽
redis是一個高性能的key,value數(shù)據(jù)庫,除了用作緩存之外,它還提供了過期監(jiān)聽的功能。
在redis.conf中配置
配置notify-keyspace-events "Ex" 即可開啟此功能。
springboot 項目集成redis配置過期監(jiān)聽
在pom中引入依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
在yml中配置redis源
redis: #數(shù)據(jù)庫索引 database: 0 host: 127.0.0.1 port: 6379 password: 123456 jedis: pool: #最大連接數(shù) max-active: 15 #最大阻塞等待時間(負(fù)數(shù)表示沒限制) max-wait: -1 #最大空閑 max-idle: 15 #最小空閑 min-idle: 0 #連接超時時間 timeout: 10000
編寫redis配置類
package com.example.study_demo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.listener.RedisMessageListenerContainer; /** * Redis配置 */ @Configuration public class RedisConfig { @Autowired private RedisConnectionFactory redisConnectionFactory; @Bean public RedisMessageListenerContainer redisMessageListenerContainer() { RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer(); redisMessageListenerContainer.setConnectionFactory(redisConnectionFactory); return redisMessageListenerContainer; } @Bean public KeyExpiredListener keyExpiredListener() { return new KeyExpiredListener(this.redisMessageListenerContainer()); } }
編寫redis工具類
package com.example.study_demo.utils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Component; import java.util.*; import java.util.concurrent.TimeUnit; @Component public class RedisCache { @Autowired public RedisTemplate redisTemplate; /** * 緩存基本的對象,Integer、String、實體類等 * * @param key 緩存的鍵值 * @param value 緩存的值 */ public <T> void setCacheObject(final String key, final T value) { redisTemplate.opsForValue().set(key, value); } /** * 緩存基本的對象,Integer、String、實體類等 * * @param key 緩存的鍵值 * @param value 緩存的值 * @param timeout 時間 * @param timeUnit 時間顆粒度 */ public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) { redisTemplate.opsForValue().set(key, value, timeout, timeUnit); } /** * 設(shè)置有效時間 * * @param key Redis鍵 * @param timeout 超時時間 * @return true=設(shè)置成功;false=設(shè)置失敗 */ public boolean expire(final String key, final long timeout) { return expire(key, timeout, TimeUnit.SECONDS); } /** * 設(shè)置有效時間 * * @param key Redis鍵 * @param timeout 超時時間 * @param unit 時間單位 * @return true=設(shè)置成功;false=設(shè)置失敗 */ public boolean expire(final String key, final long timeout, final TimeUnit unit) { return redisTemplate.expire(key, timeout, unit); } /** * 獲得緩存的基本對象。 * * @param key 緩存鍵值 * @return 緩存鍵值對應(yīng)的數(shù)據(jù) */ public <T> T getCacheObject(final String key) { ValueOperations<String, T> operation = redisTemplate.opsForValue(); return operation.get(key); } /** * 刪除單個對象 * * @param key */ public boolean deleteObject(final String key) { return redisTemplate.delete(key); } /** * 刪除集合對象 * * @param collection 多個對象 * @return */ public long deleteObject(final Collection collection) { return redisTemplate.delete(collection); } /** * 緩存List數(shù)據(jù) * * @param key 緩存的鍵值 * @param dataList 待緩存的List數(shù)據(jù) * @return 緩存的對象 */ public <T> long setCacheList(final String key, final List<T> dataList) { Long count = redisTemplate.opsForList().rightPushAll(key, dataList); return count == null ? 0 : count; } /** * 獲得緩存的list對象 * * @param key 緩存的鍵值 * @return 緩存鍵值對應(yīng)的數(shù)據(jù) */ public <T> List<T> getCacheList(final String key) { return redisTemplate.opsForList().range(key, 0, -1); } /** * 緩存Set * * @param key 緩存鍵值 * @param dataSet 緩存的數(shù)據(jù) * @return 緩存數(shù)據(jù)的對象 */ public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) { BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key); Iterator<T> it = dataSet.iterator(); while (it.hasNext()) { setOperation.add(it.next()); } return setOperation; } /** * 獲得緩存的set * * @param key * @return */ public <T> Set<T> getCacheSet(final String key) { return redisTemplate.opsForSet().members(key); } /** * 緩存Map * * @param key * @param dataMap */ public <T> void setCacheMap(final String key, final Map<String, T> dataMap) { if (dataMap != null) { redisTemplate.opsForHash().putAll(key, dataMap); } } /** * 獲得緩存的Map * * @param key * @return */ public <T> Map<String, T> getCacheMap(final String key) { return redisTemplate.opsForHash().entries(key); } /** * 往Hash中存入數(shù)據(jù) * * @param key Redis鍵 * @param hKey Hash鍵 * @param value 值 */ public <T> void setCacheMapValue(final String key, final String hKey, final T value) { redisTemplate.opsForHash().put(key, hKey, value); } /** * 獲取Hash中的數(shù)據(jù) * * @param key Redis鍵 * @param hKey Hash鍵 * @return Hash中的對象 */ public <T> T getCacheMapValue(final String key, final String hKey) { HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash(); return opsForHash.get(key, hKey); } /** * 刪除Hash中的數(shù)據(jù) * * @param key * @param hkey */ public void delCacheMapValue(final String key, final String hkey) { HashOperations hashOperations = redisTemplate.opsForHash(); hashOperations.delete(key, hkey); } /** * 獲取多個Hash中的數(shù)據(jù) * * @param key Redis鍵 * @param hKeys Hash鍵集合 * @return Hash對象集合 */ public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) { return redisTemplate.opsForHash().multiGet(key, hKeys); } /** * 獲得緩存的基本對象列表 * * @param pattern 字符串前綴 * @return 對象列表 */ public Collection<String> keys(final String pattern) { return redisTemplate.keys(pattern); } }
編寫監(jiān)控類
在代碼中繼承KeyspaceEventMessageListener ,實現(xiàn)onMessage就可以監(jiān)聽過期的數(shù)據(jù)量
package com.example.study_demo.config; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.listener.KeyExpirationEventMessageListener; import org.springframework.data.redis.listener.RedisMessageListenerContainer; @Slf4j public class KeyExpiredListener extends KeyExpirationEventMessageListener { public KeyExpiredListener(RedisMessageListenerContainer listenerContainer) { super(listenerContainer); } @Override public void onMessage(Message message, byte[] pattern) { String expiredKey = message.toString(); log.info("訂單{}過期了", expiredKey); } }
測試
package com.demo; import com.demo.config.MyDelay; import java.util.concurrent.DelayQueue; public class demo { public static void main(String[] args) throws InterruptedException { long expire = 5L; //設(shè)置過期時間 String key = "0001"; RedisCache redisCache = new RedisCache(); redisCache.setCacheObject(key,"訂單過期了"); redisCache.expire(key,expire); } }
優(yōu)點:由于redis的高性能,所以在設(shè)置以及消費key時的速度可以保證。
缺點: 由于redis的key過期策略的原因,當(dāng)一個key過期時,無法立刻保證將其刪除,自然我們監(jiān)聽事件也無法第一時間消費到這個key,所以會存在一定的延遲。 此外,在redis5.0之前,訂閱發(fā)布消息并沒有被持久化,自然也沒有所謂的確認(rèn)機(jī)制,所以一旦消費信息過程中我們的客戶端發(fā)生了宕機(jī),這條消息就徹底丟失了。
4、Redisson分布式延遲隊列
Redisson是一個基于redis實現(xiàn)的Java駐內(nèi)存數(shù)據(jù)網(wǎng)絡(luò),它不僅提供了一系列的分布式Java常用對象,還提供了許多分布式服務(wù)。Redisson除了提供我們常用的分布式鎖外,還提供了一個分布式延遲隊列RDelayedQueue ,它是一種基于zset結(jié)構(gòu)實現(xiàn)的延遲隊列,其實現(xiàn)類是RedissonDelayedQueue,在springboot中整合使用Redisson分布式延遲隊列的步驟如下:
引入pom依賴,yml中配置redis連接
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.10.5</version> </dependency>
創(chuàng)建延時隊列生產(chǎn)者
import org.redisson.api.RDelayedQueue; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * 延遲隊列生產(chǎn)者 */ @Service public class RDelayQueueProducer { @Autowired private RedissonClient redissonClient; public void addTask(String taskId, long delayTime){ //創(chuàng)建一個延遲隊列 RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(redissonClient.getQueue("my_delayQueue")); //將任務(wù)添加到延遲隊列,指定延遲時間 delayedQueue.offer(taskId,delayTime,java.util.concurrent.TimeUnit.SECONDS); } }
創(chuàng)建延時隊列消費者
import org.redisson.api.RDelayedQueue; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * 延遲隊列消費者 */ @Service public class RDelayQueueConsumer { @Autowired private RedissonClient redissonClient; public void consumeTask(){ RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(redissonClient.getQueue("my_delayQueue")); while (true){ String poll = delayedQueue.poll(); if(poll!=null){ //收到消息進(jìn)行處理 System.out.println("收到消息:"+poll); } } } }
測試
@PostMapping("/test") public void test(){ rDelayQueueProducer.addTask("0001",5); rDelayQueueProducer.addTask("0002",10); rDelayQueueProducer.addTask("0003",15); }
優(yōu)點:使用簡單,并且其實現(xiàn)類中大量使用lua腳本保證其原子性,不會有并發(fā)重復(fù)問題。
缺點:需要依賴redis
5、RocketMQ延遲消息
RocketMQ是阿里巴巴開源的一款分布式消息中間件,基于高可用分布式集群技術(shù),提供低延遲的、可靠的消息發(fā)布與訂閱服務(wù)。下面是在springboot中集成RocketMQ延遲消息的步驟:
安裝并啟動 RocketMQ 服務(wù)
可參考RocketMQ 官方文檔進(jìn)行安裝和啟動
引入依賴
<dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency>
配置RocketMQ
spring: rocketmq: name-server: 127.0.0.1:9876 # RocketMQ NameServer地址 producer: group: my-group # 生產(chǎn)者組名
創(chuàng)建消息生產(chǎn)者
import org.apache.rocketmq.spring.core.RocketMQTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class RocketMQProducerService { @Autowired private RocketMQTemplate rocketMQTemplate; public void sendMessage(String topic, String message,long delay) { // 發(fā)送延遲消息,延遲級別為16,對應(yīng)延遲時間為delay rocketMQTemplate.syncSend(topic, message, delay, 16); } }
創(chuàng)建消息消費者
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; import org.apache.rocketmq.spring.core.RocketMQListener; import org.springframework.stereotype.Service; @Service @RocketMQMessageListener(topic = "test-topic", consumerGroup = "my-consumer-group") public class RocketMQConsumerService implements RocketMQListener<String> { @Override public void onMessage(String message) { System.out.println("接收到消息: " + message); //檢查訂單是否支付 } }
測試
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class RocketMQTestController { @Autowired private RocketMQProducerService producerService; @GetMapping("/sendMessage") public String sendMessage() { String topic = "test-topic"; String message = "0001"; //發(fā)送訂單編號到rocketMQ long delay = 3000; producerService.sendMessage(topic, message, delay); return "消息發(fā)送成功"; } }
優(yōu)點:系統(tǒng)之間完全解耦,只需要關(guān)注生產(chǎn)及消費即可。其吞吐量極高。
缺點:RocketMQ是重量級的組件,引入后,隨之而來的消息丟失等問題都增加了系統(tǒng)的復(fù)雜度。
6、RabbitMQ死信隊列
當(dāng)RabbitMQ中的一條正常信息,因為過了存活時間(ttl過期)、隊列長度超限等原因無法被消費時,就會被當(dāng)成一條死信消息,投遞到死信隊列。基于這樣的機(jī)制,我們可以給消息設(shè)置一個ttl ,等消息過期就會進(jìn)入死信隊列,我們再消費死信隊列即可,這樣,就可以達(dá)到和RocketMQ一樣的效果。springboot集成rabbitMQ的步驟如下:
安裝并啟動 RabbitMQ 服務(wù)
可參考RabbitMQ官方文檔進(jìn)行安裝和啟動
引入依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
配置RabbitMQ
spring: rabbitmq: host: localhost port: 5672 username: guest password: guest
配置 RabbitMQ 隊列和交換機(jī)
import org.springframework.amqp.core.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; @Configuration public class RabbitMQConfig { public static final String ORDER_EXCHANGE = "order.exchange"; public static final String ORDER_QUEUE = "order.queue"; public static final String ORDER_ROUTING_KEY = "order.routing.key"; public static final String DEAD_LETTER_EXCHANGE = "dead.letter.exchange"; public static final String DEAD_LETTER_QUEUE = "dead.letter.queue"; public static final String DEAD_LETTER_ROUTING_KEY = "dead.letter.routing.key"; // 死信交換機(jī) @Bean public DirectExchange deadLetterExchange() { return new DirectExchange(DEAD_LETTER_EXCHANGE); } // 死信隊列 @Bean public Queue deadLetterQueue() { return new Queue(DEAD_LETTER_QUEUE); } // 綁定死信隊列和死信交換機(jī) @Bean public Binding deadLetterBinding() { return BindingBuilder.bind(deadLetterQueue()).to(deadLetterExchange()).with(DEAD_LETTER_ROUTING_KEY); } // 正常交換機(jī) @Bean public DirectExchange orderExchange() { return new DirectExchange(ORDER_EXCHANGE); } // 正常隊列,設(shè)置死信交換機(jī)和路由鍵,以及消息TTL為30分鐘(1800000毫秒) @Bean public Queue orderQueue() { Map<String, Object> args = new HashMap<>(); args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE); args.put("x-dead-letter-routing-key", DEAD_LETTER_ROUTING_KEY); args.put("x-message-ttl", 1800000); return new Queue(ORDER_QUEUE, true, false, false, args); } // 綁定正常隊列和正常交換機(jī) @Bean public Binding orderBinding() { return BindingBuilder.bind(orderQueue()).to(orderExchange()).with(ORDER_ROUTING_KEY); } }
創(chuàng)建消息生產(chǎn)者
import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class OrderMessageProducer { @Autowired private RabbitTemplate rabbitTemplate; public void sendOrderMessage(String message) { rabbitTemplate.convertAndSend(RabbitMQConfig.ORDER_EXCHANGE, RabbitMQConfig.ORDER_ROUTING_KEY, message); } }
創(chuàng)建消息消費者
import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Service; @Service public class OrderMessageConsumer { @RabbitListener(queues = RabbitMQConfig.DEAD_LETTER_QUEUE) public void receiveOrderMessage(String message) { System.out.println("收到訂單: " + message); // 模擬檢查訂單支付狀態(tài) } }
測試
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class OrderMessageController { @Autowired private OrderMessageProducer orderMessageProducer; @GetMapping("/sendOrderMessage") public String sendOrderMessage() { String message = "0001"; //訂單編號 orderMessageProducer.sendOrderMessage(message); return "訂單消息已發(fā)送,30分鐘后處理"; } }
優(yōu)點:同RocketMQ一樣可以使業(yè)務(wù)解耦。
缺點:RabbitMQ 的 TTL 是基于隊列的,而不是基于單個消息的精確時間控制。當(dāng)隊列中有多個消息時,即使某個消息的 TTL 已經(jīng)過期,也需要等待前面的消息被處理完才能進(jìn)入死信隊列,導(dǎo)致消息的實際處理時間可能會有一定的延遲,無法保證精確的延遲時間。
到此這篇關(guān)于Java中如何實現(xiàn)訂單超時自動取消功能的文章就介紹到這了,更多相關(guān)Java訂單超時自動取消內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實現(xiàn)批量查找與替換Excel文本的思路詳解
在 Java 中,可以通過find和replace的方法來查找和替換單元格的數(shù)據(jù),下面小編將以Excel文件為例為大家介紹如何實現(xiàn)Excel文件內(nèi)容的批量替換,感興趣的朋友跟隨小編一起看看吧2023-10-10SpringBoot Actuator未授權(quán)訪問漏洞的排查和解決方法
Spring Boot Actuator 是開發(fā)和管理生產(chǎn)級 Spring Boot 應(yīng)用程序的重要工具,它可以幫助你確保應(yīng)用程序的穩(wěn)定性和性能,本文給大家介紹了SpringBoot Actuator未授權(quán)訪問漏洞的排查和解決方法,需要的朋友可以參考下2024-05-05Spring + Spring Boot + MyBatis + MongoDB的整合教程
這篇文章主要給大家介紹了關(guān)于Spring + Spring Boot + MyBatis + MongoDB的整合教程,文中通過圖文以及示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧。2017-12-12Mybatis通過攔截器實現(xiàn)單數(shù)據(jù)源內(nèi)多數(shù)據(jù)庫切換
這篇文章主要為大家詳細(xì)介紹了Mybatis如何通過攔截器實現(xiàn)單數(shù)據(jù)源內(nèi)多數(shù)據(jù)庫切換,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-12-12Java對Map進(jìn)行按value排序的幾種常見方法
在日常開發(fā)中,Map 是我們經(jīng)常使用的數(shù)據(jù)結(jié)構(gòu)之一,盡管 Map 是按鍵 (key) 存儲和檢索數(shù)據(jù)的,但有時我們需要根據(jù) value 進(jìn)行排序,這篇博客將詳細(xì)探討如何在 Java 中對 Map 進(jìn)行按 value 排序的幾種常見方法,并分析它們的優(yōu)缺點,需要的朋友可以參考下2025-03-03