第二节: Redis之Set类型和SortedSet类型的介绍和案例应用
一. Set类型基础
1. 类型说明
1个key→多个value,value的值不重复!
Set一种无序且元素内容不重复的集合,不用做重复性判断了,和我们数学中的集合概念相同,可以对多个集合求交集、并集、差集,key可以理解成集合的名字。
注:set 用哈希表来保持字符串的唯一性,没有先后顺序,是按照自己的一个存储方式来遍历,因为没有保存插入顺序。
2. 常用指令Api说明
3. 常用Api说明
(1).SetAdd:添加数据,可以单独1个key-1个value,也可以1个key-多个value添加
(2).SetLength:求key集合的数量
(3).SetContains:判断key集合中是否包含指定值
(4).SetRandomMember:随机获取指定key集合中的一个值或n个值
(5).SetMembers:获取key中的所有值,数据类型要一致,便于存储
(6).SetRemove:删除key集合中的指定value(1个或多个)
(7).SetPop:随机删除指定key集合中的一个值或n个值,并返回这些值。
(8).SetCombine:求多个元素的交并差
a.SetOperation.Intersect:交集
b.SetOperation.Union:并集
c.SetOperation.Difference:差集
(9).SetCombineAndStore:把多个元素的交并差,放到一个新的元素集合中
代码分享:
//1.添加,value不重复,添加多个Marren1也只有一个
//单个
db.SetAdd("Keen", "Marren1");
db.SetAdd("Keen", "Marren2");
db.SetAdd("Keen", "Marren3");
db.SetAdd("Keen", "Marren2");
db.SetAdd("Keen", "Marren1");
//多个
string[] arryList = { "Marren4", "Marren5", "Marren6", "Marren3" };
RedisValue[] valueList = arryList.Select(u => (RedisValue)u).ToArray();
db.SetAdd("Keen", valueList); //2. 获取key集合值的数量
long d1 = db.SetLength("Keen"); //3. 判断key集合中是否包含指定值
bool d2 = db.SetContains("Keen", "Marren2"); //4. 随机获取key集合中的一个值
var d3 = db.SetRandomMember("Keen");
//随机获取key集合中的3个值
var d33 = db.SetRandomMembers("Keen", ).Select(u => (string)u).ToList(); //5. 获取key中的所有值,数据类型要一致
var rValue = db.SetMembers("Keen");
List<string> d4 = new List<string>();
foreach (var item in rValue)
{
d4.Add(item);
} //6. 删除key集合中的指定value
//单个value
bool d5 = db.SetRemove("Keen", "Marren1");
//多个value
string[] dDelList = { "Marren2", "Marren3", };
RedisValue[] DelList = dDelList.Select(u => (RedisValue)u).ToArray();
long d6 = db.SetRemove("Keen", DelList); //7. 随机删除key集合中的一个值,并返回该值
var d7 = db.SetPop("Keen");
//随机删除key集合中的2个值,并返回这2个值
var d77 = db.SetPop("Keen", ).Select(u => (string)u).ToList(); //8. 获取几个集合的交集、并集、差集(重点)
//准备数据
db.SetAdd("ypf", "h1");
db.SetAdd("ypf", "h2");
db.SetAdd("ypf", "h3");
db.SetAdd("ypf", "h4");
db.SetAdd("maru", "h4");
db.SetAdd("maru", "h5");
db.SetAdd("maru", "h6");
//下面求两个元素的交并差,也可以求多个
string[] arry = { "maru", "ypf" };
RedisKey[] keyList = arry.Select(u => (RedisKey)u).ToArray();
//交集(共同的部分 h4)
var d8 = db.SetCombine(SetOperation.Intersect, keyList).Select(u => (string)u).ToList();
//并集(加到一起,去重, h1-h6)
var d9 = db.SetCombine(SetOperation.Union, keyList).Select(u => (string)u).ToList();
//差集(差集有两个,上面的是h5 h6, 如果颠倒maru和ypf顺序,差集是h1 h2 h3)
var d10 = db.SetCombine(SetOperation.Difference, keyList).Select(u => (string)u).ToList(); //获取交集并存到key=ypf1,返回集合元素的个数
long d11 = db.SetCombineAndStore(SetOperation.Intersect, "ypf1", keyList);
//获取并集并存到key=ypf2 ,返回集合元素的个数
long d12 = db.SetCombineAndStore(SetOperation.Union, "ypf2", keyList);
//获取差集并存到key=ypf3 ,返回集合元素的个数
long d13 = db.SetCombineAndStore(SetOperation.Union, "ypf3", keyList);
二. Set类型案例
1.抽奖
(1) 背景:用户参与抽奖,抽奖大致分两类:
A:只抽1次,1次抽n个人。
B:抽多次,比如三等奖抽3名,二等奖抽2名,一等奖抽1名。
(2) 技术分析:
主要利用Set结构元素的不重复性和获取随机数的方法来实现,以“specialPrize”为key,参与用户的id当做value
A:用户点击参与抽奖则执行SetAdd方法。
B:可以获取所有参与用户SetMembers 和 判断某个用户是否参与抽奖了SetContains
C:随机抽一次奖用SetRandomMember或SetRandomMembers,因为不需要删除
多次抽奖用SetPop,因为抽完要删掉。
(3) 代码分析
//1.模拟用户参与抽奖
for (int i = ; i <= ; i++)
{
db.SetAdd("specialPrize", i.ToString());
}
//2. 获取所有的参与抽奖的用户
var data = db.SetMembers("specialPrize").Select(u => (string)u).ToList();
string idList = "";
foreach (var item in data)
{
idList = idList + "," + item;
}
Console.WriteLine($"参与抽奖的用户有:{idList}");
//3. 判断用户103是否参与抽奖了
var data2 = db.SetContains("specialPrize", "");
if (data2 == true)
{
Console.WriteLine($"用户103参与了抽奖");
}
else
{
Console.WriteLine($"用户103没有参与抽奖");
}
//4. 抽奖
//4.1 只抽一次奖,抽奖人数为两名
{
var d1 = db.SetRandomMembers("specialPrize", ).Select(u => (string)u).ToList();
foreach (var item in d1)
{
Console.WriteLine($"获奖用户为:{item}");
}
}
//4.2 抽三次奖
{
var d1 = db.SetPop("specialPrize", ).Select(u => (string)u).ToList();
foreach (var item in d1)
{
Console.WriteLine($"三等奖用户为:{item}");
}
var d2 = db.SetPop("specialPrize", ).Select(u => (string)u).ToList();
foreach (var item in d2)
{
Console.WriteLine($"二等奖用户为:{item}");
}
var d3 = db.SetPop("specialPrize", ).Select(u => (string)u).ToList();
foreach (var item in d3)
{
Console.WriteLine($"一等奖用户为:{item}");
}
}
2. 微信或微博中消息的点赞(或者某篇文章的收藏)
(1). 背景
微信朋友圈用户A的某条消息的点赞功能,要实现点赞、取消点赞、获取点赞列表、获取点赞用户数量、判断某用户是否点赞过。
(2). 技术分析
利用Set结构, 以用户Id-消息id作为key,点赞过该消息的用户id作为value。
A:点赞 SetAdd方法
B:取消点赞 SetRemove方法
C:获取点赞列表 SetMembers方法
D:获取点赞用户数量 SetLength方法
E:判断某用户是否点赞过 SetContains方法
该案例容易理解,此处不写代码了。
3.关注模型
(1).背景
比如微博关注或者共同好友的问题,以微博关注为例,要实现:同时关注、关注的和、关注A的用户中也关注B的、当A进入B页面,求可能认识的人。
(2). 技术分析
利用Set结构,一个博主对应一个Set结构,博主的id作为key,关注该博主的用户id作为value。
A:关注和取消关注: SetAdd方法 和 SetRemove方法
B:同时关注:求交集
C:关注的和:求并集
D:关注A的用户中也关注B的:遍历A中的用户,利用SetContains判断是否B中也存在
E:当A进入B页面,求可能认识的人:这里指的是关注B中的用户 扣去 里面也关注A的用户,就是A可能认识的人。
求差集:B-A
(3). 代码分享
//关注lmr的用户有:
db.SetAdd("lmr", "小1");
db.SetAdd("lmr", "小2");
db.SetAdd("lmr", "小3");
db.SetAdd("lmr", "小4");
db.SetAdd("lmr", "小5");
db.SetAdd("lmr", "rbp"); //关注ypf的用户有:
db.SetAdd("ypf", "小4");
db.SetAdd("ypf", "小5");
db.SetAdd("ypf", "小6");
db.SetAdd("ypf", "小7");
db.SetAdd("ypf", "小8");
db.SetAdd("ypf", "rbp"); //同时关注lmr和ypf的用户有:
string[] arry1 = { "lmr", "ypf" };
RedisKey[] keyList1 = arry1.Select(u => (RedisKey)u).ToArray();
var d1 = db.SetCombine(SetOperation.Intersect, keyList1).Select(u => (string)u).ToList(); //交集
foreach (var item in d1)
{
Console.WriteLine("同时关注lmr和ypf的用户有:" + item);
} //关注lmr和ypf的用户有(需要去重):
string[] arry2 = { "lmr", "ypf" };
RedisKey[] keyList2 = arry2.Select(u => (RedisKey)u).ToArray();
var d2 = db.SetCombine(SetOperation.Union, keyList2).Select(u => (string)u).ToList(); //并集
foreach (var item in d2)
{
Console.WriteLine("关注lmr和ypf的用户有:" + item);
} //关注lmr的人中也关注ypf的有:
var d3 = db.SetMembers("lmr").Select(u => (string)u).ToList();
foreach (var item in d3)
{
var isExist = db.SetContains("ypf", item);
if (isExist)
{
Console.WriteLine("关注lmr的人中也关注ypf的有:" + item);
}
} //当ypf进入lmr的页面,显示可能认识的人(应该显示:小1,小2,小3)
string[] arry4 = { "lmr", "ypf" }; // lmr-ypf
RedisKey[] keyList4 = arry4.Select(u => (RedisKey)u).ToArray();
var d4 = db.SetCombine(SetOperation.Difference, keyList4).Select(u => (string)u).ToList(); //差集 lmr-ypf
foreach (var item in d4)
{
Console.WriteLine("当ypf进入lmr的页面,显示可能认识的人:" + item);
}
4. 利用唯一性,可以统计访问网站的所有IP
三. SortedSet类型基础
1. 类型说明
将Set中的元素增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列, 同样元素名称也不能重复, 三个字段:key-member-score, key键,member值,score:权重或者打分值
2. 常用Api
(1).SortedSetAdd:增加,可以一次增加一个member,也可以一次增加多个member
(2).SortedSetIncrement 和 SortedSetDecrement:Score值自增或自减,如果不存在这member值,则执行增加操作,并返回当前Score值。
(3).获取相关
SortedSetRangeByRank:根据索引获取member值,默认是升序,可以获取指定索引内的member值
SortedSetRangeByScore:根据score获取member值,默认是升序,可以获取指定score开始和结束的member值,后面的skip和take用于分页
SortedSetRangeByValue:根据member获取member值,默认是升序,可以获取指定member开始和结束的值,后面的skip和take用于分页
SortedSetRangeByRankWithScores:获取member和score值,可以只返回 start-stop 这个索引排序内的值(默认升序),后面的skip和take用于分页
SortedSetScore:获取指定key指定member的score值
SortedSetLength:获取集合的数量
(4).删除相关
SortedSetRemove:删除指定key和指定member,member可以是1个或多个
SortedSetRemoveRangeByRank:删除指定索引开始到结束
SortedSetRemoveRangeByScore:删除指定分开始到结束 (5分-8分)
SortedSetRemoveRangeByValue::删除指定起始值和结束值(这里指定是member)
代码分享:
//1.增加
//1.1 SortedSetAdd(RedisKey key, RedisValue member, double score),如果member之前存在,则会覆盖前面的score
db.SortedSetAdd("一年级", "ypf1", );
for (int i = ; i < ; i++)
{
db.SortedSetAdd("一年级", "ypf" + i, i);
}
//db.SortedSetAdd("一年级", "ypf1", 120); 会覆盖前面的score //1.2 score自增(刚开始如果没有这个member,会默认添加进去)
var dd1 = db.SortedSetIncrement("一年级", "ypf", );
//1.3 Score自减(刚开始如果没有这个member,会默认添加进去)
var dd2 = db.SortedSetDecrement("一年级", "ypf", ); //2.获取
{
//2.1 SortedSetRangeByRank:获取的是member值,可以只返回 start-stop 这个排序内的值
//2.1.1 默认是升序获取所有member值
var d1 = db.SortedSetRangeByRank("一年级");
string[] d1Arry1 = d1.Select(u => (string)u).ToArray(); //2.1.2 降序获取所有member值
var d2 = db.SortedSetRangeByRank("一年级", , -, Order.Descending);
string[] d1Arry2 = d2.Select(u => (string)u).ToArray(); //2.1.3 降序获取排名前4的member值
var d3 = db.SortedSetRangeByRank("一年级", , , Order.Descending);
string[] d1Arry3 = d3.Select(u => (string)u).ToArray();
}
{
//2.2 SortedSetRangeByScore:获取的是member值,可以只返回 start-stop 这个score内的值,后面的skip和take用于分页
//SortedSetRangeByScore(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1, CommandFlags flags = CommandFlags.None);
//2.2.1 默认是升序获取所有member值
var d1 = db.SortedSetRangeByScore("一年级");
string[] d1Arry1 = d1.Select(u => (string)u).ToArray(); //2.2.2 降序获取所有member值
var d2 = db.SortedSetRangeByScore("一年级", double.NegativeInfinity, double.PositiveInfinity, Exclude.None, Order.Descending);
string[] d1Arry2 = d2.Select(u => (string)u).ToArray(); //2.2.3 降序获取score在【2,6】分内的值
var d3 = db.SortedSetRangeByScore("一年级", , , Exclude.None, Order.Descending);
string[] d1Arry3 = d3.Select(u => (string)u).ToArray();
}
{
//2.3 SortedSetRangeByValue:获取的是member值,可以只返回 member开始到结束的值,后面的skip和take用于分页
//2.3.1 默认是升序获取所有score值
var d1 = db.SortedSetRangeByValue("一年级");
string[] d1Arry1 = d1.Select(u => (string)u).ToArray(); //2.3.2 降序获取member在【ypf2,ypf6】分内的值
var d3 = db.SortedSetRangeByValue("一年级", "ypf2", "ypf6", Exclude.None, Order.Descending);
string[] d1Arry3 = d3.Select(u => (string)u).ToArray();
} {
//2.4 SortedSetRangeByRankWithScores:获取member和score值,可以只返回 start-stop 这个索引排序内的值,后面的skip和take用于分页
//2.4.1 默认是升序
SortedSetEntry[] d1 = db.SortedSetRangeByRankWithScores("一年级");
Dictionary<string, double> dic = new Dictionary<string, double>();
foreach (var item in d1)
{
dic.Add(item.Element, item.Score);
}
//2.4.2 降序获取所有
SortedSetEntry[] d2 = db.SortedSetRangeByRankWithScores("一年级", , -, Order.Descending);
Dictionary<string, double> dic2 = new Dictionary<string, double>();
foreach (var item in d2)
{
dic2.Add(item.Element, item.Score);
}
//2.4.3 降序获取排名前4的member和score值
SortedSetEntry[] d3 = db.SortedSetRangeByRankWithScores("一年级", , , Order.Descending);
Dictionary<string, double> dic3 = new Dictionary<string, double>();
foreach (var item in d3)
{
dic3.Add(item.Element, item.Score);
}
}
{
//2.5 获取指定key指定member的score值
var d1 = db.SortedSetScore("一年级", "ypf2");
}
//3. 获取集合的数量
long l1 = db.SortedSetLength("一年级"); //4. 删除
//{
// //4.1.SortedSetRemove:删除指定key和指定member,member可以是1个或多个
// bool num1 = db.SortedSetRemove("一年级", "ypf1");
// string[] arry1 = { "ypf2", "ypf3", "ypf4" };
// RedisValue[] newArry1 = arry1.Select(u => (RedisValue)u).ToArray();
// long num2 = db.SortedSetRemove("一年级", newArry1);
//} //{
// //4.2.SortedSetRemoveRangeByRank:删除指定索引开始到结束
// long num = db.SortedSetRemoveRangeByRank("一年级", 0, 2);
//} //{
// //4.3.SortedSetRemoveRangeByScore:删除指定分开始到结束 (5分-8分)
// long num = db.SortedSetRemoveRangeByScore("一年级", 5, 8);
//} //{
// //4.4.SortedSetRemoveRangeByValue:删除指定起始值和结束值(这里指定是member)
// long num = db.SortedSetRemoveRangeByValue("一年级", "ypf3", "ypf6");
//}
四. SortedSet案例
1. 热词搜索
(1). 需求:
统计某个网站热词搜索,并实时显示前5名的词及其搜索次数。
PS:实时显示无非就是每隔几秒查询一次,大部分情况我们不建议直接去Redis里查排名,可以把前五名的相关数据存储到Redis的String结构中, 设置5分钟过期。
下面的案例不考虑上面的PS情况,不考虑IP限制问题(前面访问量案例做过),仅仅做最简单的热词搜索,刷新显示
(2).技术分析:
利用Redis中SortedSet(key-member-score)这一数据结构,利用SortedSetIncrement方法的原子性,每搜索一个词,Score值加1(member不存在则执行的是增加操作), 然后再利用SortedSetRangeByRankWithScores方法获取Score值前五的memeber和Score数据
代码分享:
/// <summary>
/// 热词搜索页面
/// </summary>
/// <returns></returns>
public IActionResult Index()
{
//获取排名前5的数据
var data = _redis.SortedSetRangeByRankWithScores("hotWord", , , Order.Descending);
Dictionary<string, double> dic3 = new Dictionary<string, double>();
foreach (var item in data)
{
dic3.Add(item.Element, item.Score);
}
ViewBag.data = dic3; return View();
} /// <summary>
/// 查询接口
/// </summary>
/// <param name="word"></param>
/// <returns></returns>
public string HotSearch(string word)
{
try
{
_redis.SortedSetIncrement("hotWord", word, );
return "ok";
}
catch (Exception ex)
{
return "error";
}
}
2.宝宝投票
分析:和上面的热词原理一样,利用SortedSet的1个key对应多个不重复的 member-score,key用来存储一个标记,比如叫做“AllChildren”,代表该标记下存储宝宝的投票情况,member可以存储
宝宝的标记id,score存储该宝宝的投票数量.
同样原理:利用SortedSetIncrement进行自增存储,利用SortedSetRangeByRankWithScores获取排名情况
3.高积分用户排行榜
类似案例:主播-粉丝刷礼物的排行榜(同时看主播的排行和每个主播下面粉丝的排行), 原理和上面都一样
总结:
SortedSet和String利用其自增自减(并返回当前值)原子性均可以实现计数器的作用,String是针对单个,Sorted是针对某个类别下的多个或每一个,并且实现排序功能。 Hash类型也能实现某个类别下多个物品的计数,但它不具有排序功能。
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 本人才疏学浅,用郭德纲的话说“我是一个小学生”,如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
第二节: Redis之Set类型和SortedSet类型的介绍和案例应用的更多相关文章
- 第一节: Redis之String类型和Hash类型的介绍和案例应用
一. String类型基础 1.类型介绍 典型的Key-Value集合,如果要存实体,需要序列化成字符串,获取的时候需要反序列化一下. 2. 指令Api说明 3.常用Api说明 (1).StringS ...
- 【redis】02string类型和hash类型
Redis的数据类型 Redis主要分为五个数据类型,一个是string,最简单的一个数据类型,hash,list, 还有set集合,还有zset有序集合,这是咱们redis的五种基础类型, 接下 ...
- 【redis】04set类型和zset类型
sets类型 sets类型及操作 Set类型是一个集合,他是string类型的无序集合,也就是说咱们的set是没有顺序的, Set是通过hash table实现的,添加.删除和查找的复杂度都是 ...
- 学JAVA第二十三天,List类型和Set类型
数组,是我们最常用的,但是有时候,我们要用数组,但是又不知道数组的类的长度的时候, 我们java就有一个很好用的工具Collection,这都是java的爸爸的用心良苦,Collection中包含Li ...
- DATETIME类型和BIGINT 类型互相转换
项目中使用BIGINT来存放时间,以下代码用来转换时间类型和BIGINT类型 SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ========= ...
- Timestame类型和String 类型的转化
Timestame类型和String 类型的转化 String转化为Timestamp: SimpleDateFormat df = new SimpleDateFormat("yyyy-M ...
- Python3.x中bytes类型和str类型深入分析
Python 3最重要的新特性之一是对字符串和二进制数据流做了明确的区分.文本总是Unicode,由str类型表示,二进制数据则由bytes类型表示.Python 3不会以任意隐式的方式混用str和b ...
- Date类型和Long类型的相互转换
Date类型和Long类型的相互转换: import java.text.SimpleDateFormat; import java.util.Date; public class T { publi ...
- java中XMLGregorianCalendar类型和Date类型之间的相互转换
import java.text.SimpleDateFormat;import java.util.Date;import java.util.GregorianCalendar;import ja ...
随机推荐
- Java Serializable:明明就一个空的接口嘛
对于 Java 的序列化,我一直停留在最浅显的认知上——把那个要序列化的类实现 Serializbale 接口就可以了.我不愿意做更深入的研究,因为会用就行了嘛. 但随着时间的推移,见到 Serial ...
- C# 通过反射调用 Func 委托
C# 通过反射调用 Func 委托 Intro 最近我的 NPOI 扩展库增加了,自定义输出的功能,可以自定义一个 Func 委托来设置要导出的内容,详细介绍请查看 https://www.cnblo ...
- 使用 Powershell 远程连接 windows server
使用 Powershell 远程连接 windows server Intro 最近我们的开发环境增加了一个 windows 服务器,没有界面的,不能直接远程桌面连上去管理,需要使用 Powershe ...
- MySQL(6)---变量
MySQL(6)-变量 这里学习变量主要是为后面学习存储过程和函数做铺垫. 变量的分类 系统变量: 全局变量 会话变量 自定义变量: 用户变量 局部变量 一.系统变量 1.概述 说明:变量由系统定义, ...
- 交互式shell脚本web console
官网:http://web-console.org/ 这个脚本可以实现web下交互,也就是有了这玩意后可以不用反弹shell了. <?php // Web Console v0.9.7 (201 ...
- swift个人总结
最近iOS10 已经开始正式使用,研究使用了swift3.0,将一些总结记录于此,以便以后查阅.持续更新中. swift中一般将一些功能相近的方法写在同一个延展中,便于代码的规范,找起来也方便.区别o ...
- 剑指offer 16:反转链表
题目描述 输入一个链表,反转链表后,输出新链表的表头. 解题思路 单链表原地反转是面试手撕代码环节非常经典的一个问题.针对一般单链表,反转的时候需要操作的是当前节点及与之相邻的其他两个节点.因而需要定 ...
- vue+hammer.js完美实现长按、左滑,右滑等触控事件
移动端使用手指直接操作的方式大受用户欢迎,这其中就包括了单点.多点.长按.双击等方式. 这么多触控事件,如果开发者自己实现,会浪费大量的时间和精力,快来看看 hammer.js 让我们轻松了多少吧. ...
- emacs 帮助相关命令
emacs 帮助相关命令 如下表: No. 键盘操作 键盘操作对应的函数 回答的问题 01 ctrl-h c describe-key-briefly 这个按键组合将运行哪个函数 02 ctrl-h ...
- [PHP] 持续交付Jenkins安装
1.下载并运行 Jenkins下载 Jenkins.http://mirrors.jenkins.io/war-stable/latest/jenkins.war 打开终端进入到下载目录.运行命令 j ...