Redis简介和Redis Template用法整理
Redis簡介
Redis使大規(guī)模互聯(lián)網(wǎng)應(yīng)用常用的內(nèi)存高速緩存數(shù)據(jù)庫,它的讀寫速度非常快。Redis是目前使用最廣泛的內(nèi)存數(shù)據(jù)存儲系統(tǒng)之一。它支持更豐富的數(shù)據(jù)結(jié)構(gòu),支持?jǐn)?shù)據(jù)持久化、事務(wù)、HA(高可用High Available)、雙機(jī)集群系統(tǒng)、主從庫。
Redis是key-value存儲系統(tǒng)。它支持的value類型包括String、List、Set、Zset(有序集合)和Hash。這些數(shù)據(jù)類型都支持push/pop、add/remove,以及取交集、并集、差集或更豐富的操作,而且這些操作都是原子性的。在此基礎(chǔ)上,Redis支持各種不同方式的排序和算法。
Redis會周期性地把更新后的數(shù)據(jù)寫入磁盤,后把修改操作寫入追加的記錄文件中(RDB和AOF兩種方式),并且在此基礎(chǔ)上實(shí)現(xiàn)了master-slave同步。機(jī)器重啟后,能通過持久化數(shù)據(jù)自動重建內(nèi)存。如果使用Redis作為Cache,則機(jī)器宕機(jī)后熱點(diǎn)數(shù)據(jù)不會丟失。
Redis為什么快?
純內(nèi)存操作;單線程操作,避免了頻繁的上下文切換;采用了非阻塞I/O多路復(fù)用機(jī)制。
文章目錄
- 1.Redis的使用場景
- 2. Redis的數(shù)據(jù)類型
- 3.用Redis Template操作Redis的5種數(shù)據(jù)類型
- 1)操作字符串
- 2)操作散列
- 3)操作列表
- 4) 操作集合
- 5)操作有序集合
1.Redis的使用場景
高并發(fā)的讀寫:Redis特別適合將方法的運(yùn)行結(jié)果放入緩存,以便后續(xù)在請求方法時直接去緩存中讀取。對執(zhí)行耗時,且結(jié)果不頻繁變動的SQL查詢的支持極好。在高并發(fā)的情況下,應(yīng)盡量避免請求直接訪問數(shù)據(jù)庫,這時可以使用Redis進(jìn)行緩沖操作,讓請求先訪問Redis。
計(jì)數(shù)器:電商網(wǎng)站商品的瀏覽量、視頻網(wǎng)站上視頻的播放數(shù)量等數(shù)據(jù)都會被統(tǒng)計(jì),以便用于運(yùn)營或產(chǎn)品分析。為了保證數(shù)據(jù)實(shí)時生效,每次瀏覽量都得+1,這會導(dǎo)致非常高的并發(fā)量。這時可以用Redis提供的incr命令來實(shí)現(xiàn)計(jì)數(shù)器功能,這一切在內(nèi)存中操作,所以性能非常好,非常適用于這些計(jì)數(shù)場景。
排行榜:可以利用Redis提供的有序集合數(shù)據(jù)類,實(shí)現(xiàn)各種復(fù)雜的排行榜應(yīng)用。如京東、淘寶的銷售榜單,商品按時間、銷量排行。
分布式會話:在集群模式下,一般都會搭建以Redies等內(nèi)存數(shù)據(jù)庫為中心的Session服務(wù),它不再有容器管理,而是由Session服務(wù)以及內(nèi)存數(shù)據(jù)庫管理。
互動場景:使用Redis提供的散列、集合等數(shù)據(jù)結(jié)構(gòu),可以很方便地實(shí)現(xiàn)網(wǎng)站中點(diǎn)贊、踩、關(guān)注共同好友等社交場景的基本功能。
最新列表:Redis可以通過LPUSH在列表頭部插入一個內(nèi)容ID作為關(guān)鍵字,LTRIM可用來限制列表的數(shù)量,這樣列表永遠(yuǎn)為N個ID,無需查詢最新的列表,直接根據(jù)ID查找對應(yīng)的內(nèi)容即可。
2. Redis的數(shù)據(jù)類型
Redis有5種數(shù)據(jù)類型
| string(字符串) | 可以時字符串、正數(shù)或者浮點(diǎn)數(shù) | 對整個字符串或字符串的一部分執(zhí)行操作;對對象和浮點(diǎn)數(shù)執(zhí)行自增或自減操作 |
| list(列表) | 一個鏈表,鏈表上的每個節(jié)點(diǎn)都包含了一個字符串 | 從鏈表的兩端推入或彈出元素;根據(jù)偏移量對鏈表進(jìn)行修剪(prim);讀取單個或多個元素;根據(jù)值來查找或移除元素 |
| set(集合) | 包含字符串的無序收集器,并且每個字符串各不相同 | 添加、獲取、移除單個元素;檢查一個元素是否存在于某個集合中;計(jì)算交集、并集、差集;從集合里隨機(jī)獲取元素 |
| hash(散列) | 包含鍵值對的無序散列表 | 添加、獲取、移除單個鍵值對;獲取所有鍵值對 |
| zset(有序集合) | 字符串成員與浮點(diǎn)數(shù)分值之間的有序映射,元素的排列順序由分值的大小決定 | 加、獲取、刪除單個元素;根據(jù)分值范圍或成員來獲取元素 |
1.字符串
Redis字符串可以包含任意類型的數(shù)據(jù)、字符、整數(shù)、浮點(diǎn)數(shù)等。一個字符串類型的值的容量時512MB,代表能存儲最大512MB的內(nèi)容。可以使用INCR(DECR,INCRBY)命令來把字符串當(dāng)作原子計(jì)數(shù)器使用。使用APPEND命令在字符串后面添加內(nèi)容。應(yīng)用場景:計(jì)數(shù)器。
2 列表
Redis列表是簡單的字符串列表,按照插入順序排序。可以通過LPUSH,RPUSH命令添加一個元素到列表的頭部或尾部。一個列表最多包含232?12^{32}-1232?1(約為42.95億)個元素。應(yīng)用場景:取最新N個數(shù)據(jù)的操作,消息隊(duì)列,刪除與過濾,實(shí)時分析正在發(fā)生的情況,數(shù)據(jù)統(tǒng)計(jì)與防止垃圾郵件。
3 集合
Redis集合是一個無序的、不允許相同成員存在的字符串集合。支持一些服務(wù)器端的命令從現(xiàn)有的集合出發(fā)去進(jìn)行集合運(yùn)算,如合并,求交集、差集,找出不同元素的操作。應(yīng)用場景:Unique操作,可以獲取某段時間內(nèi)所有數(shù)據(jù)的去重值,比如用于共同好友、二度好友、統(tǒng)計(jì)獨(dú)立IP、好友推薦等。
4 散列
Redis hash是字符串字段和字符串值之間的映射,主要用來表示對象,也能夠存儲許多元素。應(yīng)用場景:存儲、讀取、修改用戶屬性。
5 有序集合
Redis有序集合不包含相同字符串,每個有序集合的 成員都關(guān)聯(lián)著一個評分,這個評分用于把有序集合中的成員按最低分到最高分排列。使用有序集合,可以非常快捷地完成添加、刪除、更新元素的操作。元素是在插入時就排好序,所以很快地通過評分(Score)或位次(Position)獲得一個范圍的元素。應(yīng)用場景:排行榜應(yīng)用、取TOP N操作,需要精準(zhǔn)設(shè)定過期時間的應(yīng)用(時間戳作為Score)、帶有權(quán)重的元素(游戲用戶得分排行榜)、過期項(xiàng)目處理、按照時間排序等。
3.用Redis Template操作Redis的5種數(shù)據(jù)類型
Spring封裝了Redis Template來操作Redis,它支持所有的Redis原生的API。在RedisTemplate中定義了對5種數(shù)據(jù)結(jié)構(gòu)的操作方法。
- opsForValue():操作字符串
- opsForHash():操作散列
- opsForList():操作列表
- opsForSet():操作集合
- opsForZSet():操作有序集合
1)操作字符串
RedisTemplate提供以下操作String 的方法。
1.set void set(K key,V value);get V get(Object key);
具體使用見下面代碼
@Autowiredprivate RedisTemplate redisTemplate;@Testpublic void string(){redisTemplate.opsForValue().set("num",123);redisTemplate.opsForValue().set("nihao","I am lsz");redisTemplate.opsForValue().set("lishizheng","first time using Redis");Object s=redisTemplate.opsForValue().get("num");Object s2=redisTemplate.opsForValue().get("nihao");Object s3=redisTemplate.opsForValue().get("lishizheng");System.out.println(s);System.out.println(s2);System.out.println(s3);}運(yùn)行結(jié)果
123 I am lsz first time using Redis第一次使用Spring Boot使用redis,報(bào)錯如下
Unable to connect to 127.0.0.1:6379原因是:自己沒有安裝Redis也沒有啟動Redis服務(wù),自然無法連接。安裝并啟動Redis之后,可以正常連接Redis。另外這個界面需要一直打開以表示redis服務(wù)是開啟的,可能是還沒有配置的原因
2.set void set(K key,V value,long timeout,TimeUnit unit)
TimeUnit是java.util.concurrrent包下面的一個類,表示給定單元粒度的時間段,常用的顆粒度有
以下代碼設(shè)置3s失效。3s之內(nèi)查詢有結(jié)果,如果3s之后查詢則會返回null。
@Testpublic void string2(){//設(shè)置的是3s失效redisTemplate.opsForValue().set("num","dsafadfadaafaa",3, TimeUnit.SECONDS);try {Object s=redisTemplate.opsForValue().get("num");System.out.println(s);Thread.currentThread().sleep(2000);Object s2=redisTemplate.opsForValue().get("num");System.out.println(s2);Thread.currentThread().sleep(5000);Object s3=redisTemplate.opsForValue().get("num");System.out.println(s3);}catch (InterruptedException ie){ie.printStackTrace();}}控制臺輸出結(jié)果
dsafadfadaafaa dsafadfadaafaa null3.set void set(K key,V value,long offset)
給定key所存儲的字符串值,從偏移量offset開始。具體用法見以下代碼
這里遇到問題,報(bào)錯如下
Failed to deserialize payload. Is the byte array a result of corresponding serialization for DefaultDeserializer?; nested exception is java.io.StreamCorruptedException: invalid stream header: 00000000錯誤提示:序列號對象生成這個字節(jié)數(shù)組的方法是否與默認(rèn)的反序列化方法相對應(yīng)。
錯誤代碼
原因分析:因?yàn)楸疚牡臏y試只是在test文件夾下寫的,main文件夾下面沒有東西,看來需要重新開始。這次重新開了一個Spring Boot項(xiàng)目,項(xiàng)目結(jié)構(gòu)如下,在完成了這篇文章之后:SpringBoot使用Redis和MyBatis完成緩存數(shù)據(jù)的增刪改查,順手在test文件夾下新建controller,添加RedisTypeTest.java用來測試
這次注意了Redis序列化/發(fā)序列化的配置
配置Redis類
要想啟動Spring緩存支持,需要創(chuàng)建一個CacheManager的Bean。
RedisConfig.java
package com.example.demo.config;import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer;import java.lang.reflect.Method;@Configurationpublic class RedisConfig extends CachingConfigurerSupport{@Beanpublic KeyGenerator keyGenerator() {return new KeyGenerator() {@Overridepublic Object generate(Object target, Method method, Object... params) {StringBuilder sb = new StringBuilder();sb.append(target.getClass().getName());sb.append(method.getName());for (Object obj : params) {sb.append(obj.toString());}return sb.toString();}};}@SuppressWarnings("rawtypes")@Bean//緩存管理器public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {RedisCacheManager cacheManager = RedisCacheManager.create(connectionFactory);//設(shè)置緩存過期時間return cacheManager;}@Beanpublic RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {//配置連接工廠StringRedisTemplate template = new StringRedisTemplate(factory);//使用Jackson2JsonRedisSerializer來序列化和反序列化redis 的value值Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper om = new ObjectMapper();//指定要序列化的域,field,get和set,以及修飾符范圍om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);//om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);//指定序列化輸入的類型,類必須是非final類om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);jackson2JsonRedisSerializer.setObjectMapper(om);//序列化配置為String格式template.setValueSerializer(new StringRedisSerializer());//template.setKeySerializer(new StringRedisSerializer());template.afterPropertiesSet();return template;}}關(guān)于Redis Template中序列化和反序列化配置的解釋
關(guān)于數(shù)據(jù)的“序列化/反序列化”,提供了多種可選擇策略(RedisSerializer)
| JdkSerializationRedisSerializer | POJO對象的存取場景,使用JDK本身序列化機(jī)制,將pojo類通過ObjectInputStream/ObjectOutputStream進(jìn)行序列化操作,最終redis-server中將存儲字節(jié)序列。 |
| StringRedisSerializer | 適用于Key或者value為字符串的場景,根據(jù)指定的charset對數(shù)據(jù)的字節(jié)序列編碼成string |
| JacksonJsonRedisSerializer | 提供了javabean與json之間的轉(zhuǎn)換能力,可以將pojo實(shí)例序列化成json格式存儲在redis中,也可以將json格式的數(shù)據(jù)轉(zhuǎn)換成pojo實(shí)例。 |
| OxmSerializer | 提供了將javabean與xml之間的轉(zhuǎn)換能力,目前可用的三方支持包括jaxb,apache-xmlbeans;redis存儲的數(shù)據(jù)將是xml工具。 |
上面4種策略中:
JdkSerializationRedisSerializer和StringRedisSerializer是最基礎(chǔ)的策略,在設(shè)計(jì)時仍然不推薦直接使用后面兩種,即JacksonJsonRedisSerializer和OxmSerializer,因?yàn)闊o論是json還是xml,他們本身仍然是String。
如果數(shù)據(jù)需要被第三方工具解析,那么數(shù)據(jù)應(yīng)該使用StringRedisSerializer而不是JdkSerializationRedisSerializer。
如果數(shù)據(jù)格式必須為json或者xml,那么在編程級別,在redisTemplate配置中仍然使用StringRedisSerializer,在存儲之前或者讀取之后,使用“SerializationUtils”工具轉(zhuǎn)換轉(zhuǎn)換成json或者xml
經(jīng)過一番折騰
@Testpublic void string3(){//重寫給定key所存儲的字符串,從偏移量offset開始redisTemplate.opsForValue().set("key","hello world");System.out.println(redisTemplate.opsForValue().get("key"));redisTemplate.opsForValue().set("key","git",6);System.out.println(redisTemplate.opsForValue().get("key"));}結(jié)果為
hello world hello gitld可以看到,從偏移量6開始用git字符串覆蓋掉原字符串。
4.getAndSet V getAndSet(K key,V value)
設(shè)置鍵的字符串值,并返回其舊值。具體用法如下:
返回結(jié)果
C primer plus usage5.append Integer append(K key,String value)
如果key已經(jīng)存在,已經(jīng)是一個字符串,則該命令將該值追加到字符串的末尾。如果key不存在,則它將被創(chuàng)建并被設(shè)置為空字符串。
@Testpublic void string5() {redisTemplate.opsForValue().set("k", "test");redisTemplate.opsForValue().append("k","test");System.out.println(redisTemplate.opsForValue().get("k"));redisTemplate.opsForValue().append("k","forwhat");System.out.println(redisTemplate.opsForValue().get("k"));}測試結(jié)果
testtest testtestforwhat這里曾經(jīng)遇到過問題Redis Template使用append方法不起作用的解決辦法以及序列化/反序列化的解釋
6.size Long size(K key)
返回key所對應(yīng)的value值的長度
使用如下:下面將返回字符串howareu的長度,為7
測試結(jié)果
72)操作散列
Redis hash是一個string類型的field和value的映射表,hash特別適合用于存儲對象。value中存放的是結(jié)構(gòu)化的對象。利用這種數(shù)據(jù)結(jié)構(gòu),可以方便地操作其中的某個字段。比如在“點(diǎn)擊登錄”時,可以用這種數(shù)據(jù)結(jié)構(gòu)存儲用戶信息。以CookedId作為key,設(shè)置30分鐘為緩存過期時間,能很好地模擬出類似Session的效果。
1.void putAll(H key,Map<? extends HK,? extends HV> m)
用m中提供的多個散列字段設(shè)置到key對應(yīng)的散列表中。
舉例如下:這里的m是testMap,這里key 是Hash,可以看到結(jié)果,而key是hash,由于沒有設(shè)置因此為空。
@Testpublic void hash1(){Map<String,Object> testMap=new HashMap();testMap.put("name","shizheng");testMap.put("sex","male");redisTemplate.opsForHash().putAll("Hash",testMap);System.out.println(redisTemplate.opsForHash().entries("Hash"));System.out.println(redisTemplate.opsForHash().entries("hash"));}測試結(jié)果
{sex=male, name=shizheng} {}2.void put(H key,HK hashKey,HV value)
設(shè)置hashKey的值
@Testpublic void hash2() {redisTemplate.opsForHash().put("testName", "name", "shizheng");redisTemplate.opsForHash().put("testName", "age", "100");System.out.println(redisTemplate.opsForHash().entries("testName"));System.out.println(redisTemplate.opsForHash().values("testName"));}測試結(jié)果
{name=shizheng, age=100} [shizheng, 100]3.List< HV> values(H key)
根據(jù)密鑰獲取整個散列存儲的值,用法如上
4.Map< HK,HV> entries(H key)
根據(jù)密鑰獲取整個散列存儲,用法如上
5.Long delete(H key,Object… hashKeys)
刪除給定的hashKeys
@Testpublic void hash3() {redisTemplate.opsForHash().put("testName", "name", "shizheng");redisTemplate.opsForHash().put("testName", "age", "100");System.out.println(redisTemplate.opsForHash().delete("testName","name"));System.out.println(redisTemplate.opsForHash().entries("testName"));}測試結(jié)果
1 {age=100}6.Boolean hashKey(H key,Object hashKey)
確定hashKey是否存在
@Testpublic void hash4() {redisTemplate.opsForHash().put("testName", "name", "shizheng");redisTemplate.opsForHash().put("testName", "age", "100");System.out.println(redisTemplate.opsForHash().hasKey("testName","name"));System.out.println(redisTemplate.opsForHash().hasKey("testName","height"));}測試結(jié)果
true false7.HV get(H key,Object hashKey)
從散列獲取給定hashKey的值
具體用法:獲取name的值
@Test//從鍵中的哈希獲取給定hashKey的值public void hash7() {redisTemplate.opsForHash().put("testName", "name", "shizheng");redisTemplate.opsForHash().put("testName", "sex", "male");System.out.println(redisTemplate.opsForHash().get("testName", "name"));}測試結(jié)果
shizheng8.Set< HK> keys(H key)
獲取key所對應(yīng)的key的值
測試結(jié)果
[name, sex]9.Long size(H key)
獲取key所對應(yīng)的散列表的大小個數(shù)
@Test//獲取key所對應(yīng)的散列表的大小個數(shù)public void hash9() {redisTemplate.opsForHash().put("testName", "name", "shizheng");redisTemplate.opsForHash().put("testName", "sex", "male");System.out.println(redisTemplate.opsForHash().size("testName"));}測試結(jié)果
23)操作列表
1.Long leftPushAll(K key,V… values)
含義:leftPushAll表示把一個數(shù)組插入列表中
輸出結(jié)果
[3, 2, 1]2.Long size(K key)
含義:返回存儲在鍵中的列表的長度。如果鍵不存在,則將其解釋為空列表,并返回0。當(dāng)key存儲的值不是列表時返回錯誤。
測試結(jié)果
33 Long leftPush(K key,V value)
含義:將所有指定的值插入在鍵的列表的頭部。如果鍵不存在,則在執(zhí)行推送操作之前將其創(chuàng)建為空列表(從左邊插入)。
測試結(jié)果
1 2 34 Long rightPush(K key,V value)
含義:將所有指定的值插入存儲在鍵的列表的頭部。如果鍵不存在,則在執(zhí)行推送操作之前將其創(chuàng)建為空列表。(從右邊插入)
測試結(jié)果
1 2 35 Long rightPushAll(K key,V…values)
含義:通過rightPushAll方法向最右邊批量添加元素
測試結(jié)果
[1, 2, 3]6 void set(K key,long index,V value)
含義:在列表中index的位置設(shè)置value
測試結(jié)果
[1, 2, 3, 1, 2, 3] [1, 值, 3, 1, 2, 3]7 Long remove(K key,long court,Object value)
含義:從存儲在鍵中的列表,刪除給定”count“值的元素的第1個計(jì)數(shù)事件。其中,參數(shù)count的含義如下:
count=0:刪除等于value的所有元素
count>0:刪除等于從頭到尾移動的值的元素
count<0:刪除等于從尾到頭移動的值的元素
測試結(jié)果
[1, 2, 3] [1, 3]8 V index(K key,long index)
含義:根據(jù)下標(biāo)獲取列表中的值(下標(biāo)從0開始)
測試結(jié)果
[1, 2, 3] 39 V leftPop(K key)
含義:彈出最左邊的元素,彈出之后該值在列表中將不復(fù)存在
測試結(jié)果
[1, 2, 3] 3 [1, 2]10 V rightPop(K key)
含義:彈出最右邊的元素,彈出之后該值在列表中將不復(fù)存在
測試結(jié)果
[1, 2, 1, 2, 3] 3 [1, 2, 1, 2]4) 操作集合
1 Long add(K key,V…values)
含義:在無序集合中添加元素,返回添加個數(shù)
測試結(jié)果
2 Long remove(K key,Object… values)
含義:移除集合中一個或多個成員
3.V pop(K key)
含義:移除并返回集合中的一個隨機(jī)元素
4 Boolean move( K key,V value,K destKey)
含義:將member元素移動
5 Long size(K key)
含義:獲取無序集合的大小長度
6 Set members(K key)
含義:返回集合中的所有成員
7 Cursor< V > scan(K key,ScanOptions options)
含義:遍歷set
5)操作有序集合
1Long add(K key,Set< TypedTuple< V>> tuples)
含義:新增一個有序集合
2 Boolean add(K key,V value,double score)
含義:新增一個有序集合,如果存在則返回false,如果不存在則返回true
3 Long remove(K key,Object… values)
含義:從有序集合中移除一個或多個元素
4 Long rank(K key, Object o)
含義:返回有序集中指定成員的排名,按分?jǐn)?shù)值遞增排列
5 Set< V > range(K key,long start,long end)
含義:通過索引區(qū)間返回有序集合指定區(qū)間內(nèi)的成員,按分?jǐn)?shù)值遞增排列
6 Long court(K key,double min,double max)
含義:通過分?jǐn)?shù)返回有序集合指定區(qū)間內(nèi)的成員個數(shù)
7 Long size(K key)
含義:獲取有序集合的成員數(shù)
8 Double score(K key, Object o)
含義:獲取指定成員的score
9 Long removeRange(K key,long start,long end)
含義:移除指定索引位置的成員,有序集合成員按分?jǐn)?shù)值遞增排列
10 Cursor < TypedTuple < V > > scan(K key,ScanOptions options)
含義:遍歷zset
總結(jié)
以上是生活随笔為你收集整理的Redis简介和Redis Template用法整理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis Template使用appe
- 下一篇: 窝小芽推荐的一顿饭量是多少,如何判断多了