list
数据存储需求:存储多个数据,并对数据进入存储空间的顺序进行区分
需要的存储数据:一个存储空间保存多个数据,且通过数据可以体现进入顺序
list类型:保存多个数据,底层使用双向链表存储结构实现
业务场景-最新消息的展示
- twitter、新浪微博、腾讯微博中个人用于的关注列表需要按照用户的关注顺序进行展示,粉丝列表需要将最近关注的粉丝列在前面
list类型数据操作注意事项
list 中保存的数据都是string类型的,数据总容量式是有限的,最多232-1个元素(4294967295)
list具有索引的概念,但是操作数据时候通常以队列的形式进行入队出队操作,或以栈的形式进入栈出栈的操作
获取全部数据操作结束索引设置为-1
list 可以对数据进行分页操作,通过第一页的信息来自list,第2页及更多的信息通过数据库的形式加载
List类型应用实践
Redis的list类型相当于java中的LinkedList,其原理就就是一个双向链表。支持正向、反向查找和遍历等操作,插入删除速度比较快。经常用于实现热销榜,最新评论等的设计。
lpush(压栈:先进后出)
在key对应list的头部添加字符串元素
redis 127.0.0.1:6379> lpush mylist "world"
(integer) 1
redis 127.0.0.1:6379> lpush mylist "hello"
(integer) 2
redis 127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "world"
redis 127.0.0.1:6379>
其中,Redis Lrange 返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推
rpush(先进先出)
在key对应list的尾部添加字符串元素
redis 127.0.0.1:6379> rpush mylist2 "hello"
(integer) 1
redis 127.0.0.1:6379> rpush mylist2 "world"
(integer) 2
redis 127.0.0.1:6379> lrange mylist2 0 -1
1) "hello"
2) "world"
redis 127.0.0.1:6379>
del(删除集合)
redis 127.0.0.1:6379> del mylist
linsert
在key对应list的特定位置之前或之后添加字符串元素
redis 127.0.0.1:6379> rpush mylist3 "hello"
(integer) 1
redis 127.0.0.1:6379> rpush mylist3 "world"
(integer) 2
redis 127.0.0.1:6379> linsert mylist3 before "world" "there"
(integer) 3
redis 127.0.0.1:6379> lrange mylist3 0 -1
1) "hello"
2) "there"
3) "world"
redis 127.0.0.1:6379>
lrem(-n代表删除n个)
从key对应list中删除count个和value相同的元素,count>0时,按从头到尾的顺序删除,count<0时,按从尾到头的顺序删除,count=0时,删除全部
ltrim
保留指定key 的值范围内的数据(只保留范围内的,其余全部删除,从左到右)
lpop
从list的头部删除元素,并返回删除元素
redis 127.0.0.1:6379> lrange mylist2 0 -1
1) "hello"
2) "world"
redis 127.0.0.1:6379> rpop mylist2
"world"
redis 127.0.0.1:6379> lrange mylist2 0 -1
1) "hello"
redis 127.0.0.1:6379>
llen
返回key对应list的长度:
redis 127.0.0.1:6379> lrange mylist5 0 -1
1) "three"
2) "foo"
redis 127.0.0.1:6379> lindex mylist5 0
"three"
redis 127.0.0.1:6379> lindex mylist5 1
"foo"
redis 127.0.0.1:6379>
rpoplpush
从第一个list的尾部移除元素并添加到第二个list的头部,最后返回被移除的元素值,整个操作是原子的.如果第一个list是空或者不存在返回nil:
rpoplpush lst1 lst1
rpoplpush lst1 lst2
小节面试分析
如何基于redis实现一个队列结构?(lpush/rpop)
如何基于redis实现一个栈结构?(lpush/lpop)
如何基于redis实现一个阻塞式队列?(lpush/brpop)
如何实现秒杀活动的公平性?(先进先出-FIFO)
通过list结构实现一个消息队列(顺序)吗?(可以,FIFO->lpush,rpop)
用户注册时的邮件发送功能如何提高其效率?(邮件发送是要调用三方服务,底层通过队列优化其效率,队列一般是list结构)
如何动态更新商品的销量列表?(卖的好的排名靠前一些,linsert)
商家的粉丝列表使用什么结构实现呢?(list结构)
list使用场景
业务描述
在设计一个秒杀或抢购系统时,为了提高系统的响应速度,通常会将用户的秒杀或抢购请求先存储到一个redis队列,这里我们就基于redis实现一个先进先出队列,例如:
代码实现
package com.jt.redis;
import redis.clients.jedis.Jedis;
import java.util.List;
/**
* 演示抢购活动中的秒杀队列
* 数据逻辑结构:list
* 算法:FIFO (公平特性)
* 数据存储结构:redis中的list类型进行存储
*
* 在抢购活动中会执行这样的操作:
* 1)生产者(购买商品的用户):创建请求并将请求存储到队列
* 2)消费者(处理购买请求的底层对象):从队列取请求,然后处理请求
*/
public class SecondsKillDemo01 {
/**
* 入队操作
*/
public static void enque(String request){
Jedis jedis=new Jedis("192.168.126.129", 6379);
jedis.lpush("queue-1",request);
jedis.close();
}
/**
* 出队操作
*/
public static String deque(){
Jedis jedis=new Jedis("192.168.126.129", 6379);
//非阻塞式取数据
//return jedis.rpop("queue-1");
//阻塞式取数据
List<String> list = jedis.brpop(60, "queue-1");
jedis.close();
return list!=null?list.get(1):null;//0为key
}
public static void main(String[] args) {
//1.构建生产对象(购买商品的用户)
Thread t1=new Thread(){
@Override
public void run() {
int i=1;
for(;;){
enque("request-"+i++);
try { Thread.sleep(1000);
}catch (Exception e){}
}
}
};
//2.构建消费者对象(处理请求的对象)
Thread t2=new Thread(){
@Override
public void run() {
for(;;){
String request=deque();
if(request==null)continue;
System.out.println("process "+request);
}
}
};
//3.开启抢购活动
t1.start();
t2.start();
}
}
2.set数据类型
2.1使用场景:
- 新的存储需求:存储大量的数据,在查询方面提供更高的效率
- 需要的存储结构:能够保存大量的数据,高效的内部存储机制,便于查询
- set类型:与hash存储结构完全相同,仅存储键,不存储值(nil),并且值式不允许重复的。也就是只有键没有值的hash
- set集合数据不重复的特征,依赖set集合hash存储结构特征完成数据过滤与快速查询
业务场景-共同好友
2.2set常用命令集合
Redis的Set类似Java中的HashSet,是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。Redis中Set集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
sadd
添加元素,重复元素添加失败,返回0
127.0.0.1:6379> sadd name tony
(integer) 1
127.0.0.1:6379> sadd name hellen
(integer) 1
127.0.0.1:6379> sadd name rose
(integer) 1
127.0.0.1:6379> sadd name rose ##重复了返回0表示失败
(integer) 0
smembers:获取内容
127.0.0.1:6379> smembers name
- “hellen”
- “rose”
- “tony”
spop:随机移除一个
移除并返回集合中的一个随机元素
127.0.0.1:6379> smembers internet
1) "amoeba"
2) "redis"
3) "rabbitmq"
4) "nginx"
127.0.0.1:6379> spop internet
"rabbitmq"
127.0.0.1:6379> spop internet
"nginx"
127.0.0.1:6379> smembers internet
1) "amoeba"
2) "redis"
scard
获取成员个数
127.0.0.1:6379> scard name
(integer) 3
smove :smove "A"集合 "B"集合 A集合中的一个值
移动一个元素到另外一个集合
sunion:并集
127.0.0.1:6379> sunion internet bigdata
1) "redis"
2) "nginx"
3) "rabbitmq"
4) "amoeba"
5) "hadopp"
6) "spark"
小节面试分析
- 朋友圈的点赞功能你如何实现?(sadd,srem,smembers,scard)
- 如何实现一个网站投票统计程序?
- 你知道微博中的关注如何实现吗?
3.Set实际使用业务场景Demo练习
投票系统Demo
业务描述
在很多系统中设计中,都会有一个活动设计,开启一个活动之前,可以对这个活动的支持力度先进行一个调查,例如基于这个活动设计一个投票系统,例如:
代码实现
package com.jt.redis;
import redis.clients.jedis.Jedis;
import java.util.Set;
/**
* 基于某个活动的投票系统的简易设计
* 需求:
* 1)基于用户id对活动进行投票
* 2)一个用户id只能参与一次投票
* 3)可以查询投票总数以及参数投票的用户id
* 设计:
* 1)数据存储结构:set结构(不允许重复)
*/
public class VoteDemo01 {
static final String IP="192.168.126.129";
static final int PORT=6379;
/**
* 检查用户是否参与过此活动的投票
* @param activityId
* @param userId
*/
public static Boolean isVote(String activityId,String userId){
//1.创建jedis对象
Jedis jedis=new Jedis(IP, PORT);
//2.检查是否投票
Boolean flag = jedis.sismember(activityId, userId);
//3.释放资源
jedis.close();
//4.返回结果
return flag;
}
/**
* 执行投票操作
* @param activityId
* @param userId
*/
public static void doVote(String activityId,String userId){
//1.创建Jedis对象
Jedis jedis=new Jedis(IP, PORT);
//2.执行投票
jedis.sadd(activityId, userId);
//3.释放资源
jedis.close();
}
/**
* 查看活动的总投票数量
* @param activityId
*/
public static Long doGetVotes(String activityId){
//1.创建Jedis对象
Jedis jedis=new Jedis(IP, PORT);
//2.查看活动的投票数
Long count = jedis.scard(activityId);
//3.释放资源
jedis.close();
//4.返回投票数量
return count;
}
/**
* 获取参与投票当前活动的用户
* @param activityId
*/
public static Set<String> doGetUsers(String activityId){
//1.创建Jedis对象
Jedis jedis=new Jedis(IP, PORT);
//2.查看活动的投票数
Set<String> smembers = jedis.smembers(activityId);
//3.释放资源
jedis.close();
//4.返回投票数量
return smembers;
}
public static void main(String[] args) {
//1.定义活动id,用户id
String activityId="10001";
String user1="301";
String user2="302";
//2.进行投票检查
Boolean flag=isVote(activityId, user1);
System.out.println("flag="+flag);
//3.进行投票
doVote(activityId, user1);
doVote(activityId, user2);
//4.获取投票的总数
Long aLong = doGetVotes(activityId);
System.out.println(activityId +"活动的总投票数为 "+aLong);
//5.获取参与投票的用户
Set<String> users = doGetUsers(activityId);
System.out.println("参与"+activityId+"活动的投票用户为"+users);
}
}