
Redis的数据类型
Redis的数据类型共有五种:string,list,hash,set,zset;
String 字符串相对来说做平常,key-value,类似是hashmap的用法;
List 队列,可以双向的存值,设计时,也可以简单用来当队列模式;
Hash 字典,一个key 对应多个值;
Set 无序的集合;
Zset 有序的集合;
列表 list
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)
列表 list—— 基本命令
lpush
语法:lpush key value [value„]
作用:将一个或多个值 value 插入到列表 key 的表头(最左边),从左边开始加入值,从左到右的顺序依次插入到表头
返回值:数字,新列表的长度
rpush
语法:rpush key value [value„]
作用:将一个或多个值 value 插入到列表 key 的表尾(最右边),各个 value 值按从左到右 的顺序依次插入到表尾
返回值:数字,新列表的长度
lrange
语法:lrange key start stop
作用:获取列表 key 中指定区间内的元素,0 表示列表的第一个元素,以 1 表示列表的第二个元素;
start ,
stop 是列表的下标值,也可以负数的下标, -1 表示列表的最后一个元素, -2 表示列表的倒 数第二个元素,以此类推。
start ,stop 超出列表的范围不会出现错误。
返回值:指定区间的列表
lindex
语法:lindex key index
作用:获取列表 key 中下标为指定 index 的元素,列表元素不删除,只是查询。
0 表示列表的第一个元素,以 1 表示列表的第二个元素;
start ,
stop 是列表的下标值,也可以负数的下标, -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
返回值:指定下标的元素;index 不在列表范围,返回nil
llen
语法:llen key
作用:获取列表 key 的长度 返回值:数值,列表的长度; key 不存在返回0
lrem
语法:lrem key count value
作用:根据参数count的值,移除列表中与参数value相等的元素,
count>0,从列表的左侧向右开始移 除;
count<0从列表的尾部开始移除;
count=0 移除表中所有与value相等的值。
返回值:数值,移除的元素个数
lset
语法:lset key index value
作用:将列表 key 下标为 index 的元素的值设置为 value。
返回值:设置成功返回 ok ; key 不存在或者 index 超出范围返回错误信息
linsert
语法:linsert key BEFORE(前)|AFTER(后) pivot value
作用:
将值value插入到列表key当中位于值pivot之前或之后的位置。
key不存在,pivot不在列表中, 不执行任何 *** 作。
返回值:命令执行成功,返回新列表的长度。没有找到 pivot 返回 -1, key 不存在返回 0。
RPOP key
移除列表的最后一个元素,返回值为移除的元素。
RPOPLPUSH source destination
移除列表的最后一个元素,并将该元素添加到另一个列表并返回
LPOP key
移除列表的第一个元素,返回值为移除的元素。
使用场景
1 消息队列
队列模式的情况下,可以使用,左进右出的原则,但不建议使用,因为现在市面上有很多成熟的消息中间件,没有必要造轮子;
2.排行榜
某一段时间统计数据的排行榜可以放在list里面,需要分页的话,也可以使用lrange start stop实现;
3 list类型的lpush命令和lrange命令能实现最新列表的功能,每次通过lpush命令往列表里插入新的元素,然后通过lrange命令读取最新的元素列表,如朋友圈的点赞列表、评论列表。
但是,并不是所有的最新列表都能用list类型实现,因为对于频繁更新的列表,list类型的分页可能导致列表元素重复或漏掉,举个例子,当前列表里由表头到表尾依次有(E,D,C,B,A)五个元素,每页获取3个元素,用户第一次获取到(E,D,C)三个元素,然后表头新增了一个元素F,列表变成了(F,E,D,C,B,A),此时用户取第二页拿到(C,B,A),元素C重复了。只有不需要分页(比如每次都只取列表的前5个元素)或者更新频率低(比如每天凌晨更新一次)的列表才适合用list类型实现
哈希类型hash
redis hash是一个 string 类型的 field 和 value 的映射表,hash特别适合用于存储对象,每个 hash 可以存储 232 - 1键值对(40多亿);
哈希类型 hash—— 基本命令
hset /hget /hmset /hmget /hgetall /hkeys /hvals /hexists
hset
语法:hset hash 表的key field value
作用:将哈希表 key 中的域 field 的值设为value ,如果 key 不存在,则新建 hash 表,执行赋值,如果有 field ,则覆盖值。
返回值: ①如果 field 是 hash 表中新field,且设置值成功,返回 1 ②如果 field 已经存在,旧值覆盖新值,返回0
hget
语法:hget key field
作用:获取哈希表 key 中给定域 field 的值
返回值:field 域的值,如果 key 不存在或者 field 不存在返回nil
hmset
语法:hmset key field value [field value„]
说明:同时将多个field-value(域-值)设置到哈希表key中,此命令会覆盖已经存在的field, hash表key不存在,创建空的hash表,执行hmset
返回值:设置成功返回ok, 如果失败返回一个错误
hmget
语法:hmget key field [field„]
作用:获取哈希表key中一个或多个给定域的值
返回值:返回和field顺序对应的值,如果field不存在,返回nil
hgetall
语法:hgetall key
作用:获取哈希表key中所有的域和值
返回值:以列表形式返回hash中域和域的值 ,key不存在,返回空hash
hdel
语法:hdel key field [field„]
作用:删除哈希表 key 中的一个或多个指定域 field,不存在 field 直接忽略
返回值:成功删除的 field 的数量
hkeys
语法:hkeys key
作用:查看哈希表 key 中的所有 field 域
返回值:包含所有 field 的列表,key 不存在返回空列表
hvals
语法:hvals key
作用:返回哈希表中所有域的值 返回值:包含哈希表所有域值的列表,key 不存在返回空列表
hexists
语法:hexists key field
作用:查看哈希表 key 中,给定域 field 是否存在
返回值:如果 field 存在,返回 1, 其他返回0
使用场景
1、购物车
以用户id为key,商品id为field,商品数量为value,恰好构成了购物车的3个要素,如下图所示。
2、hash还是比较适合存储对象(key field value)或者是字典表(type,key,vlaue),刚好符合对象的要素,但string + json也可以存储,两则比较有什么区别?
String + json Hash
效率很 高 高
容量 低 低
灵活性 低 高
序列化 简单 复杂
这个你可以通过对应的Key值来找寻啊 这个时间复杂度为常数
Hashtable hash = new Hashtable();
object o = hash[Key];//通过Key值可以找到Value,如果该Key不存在则返回为null
Hashtable 类基于 IDictionary 接口,因此该集合中的每一元素是键和值对。
Hashtable 由包含集合元素的存储桶组成。存储桶是 Hashtable 中各元素的虚拟子组,与大多数集合中进行的搜索和检索相比,它可令搜索和检索更简单、更快速。每一存储桶都与一个哈希代码关联,该哈希代码是使用哈希函数生成的并基于该元素的键。
哈希函数是基于键返回数值哈希代码的算法。键是正被存储的对象的某一属性的值。哈希函数必须始终为相同的键返回相同的哈希代码。一个哈希函数能够为两个不同的键生成相同的哈希代码,但从哈希表检索元素时,为每一唯一键生成唯一哈希代码的哈希函数将令性能更佳。
在 Hashtable 中用作元素的每一对象必须能够使用 ObjectGetHashCode 方法的实现为其自身生成哈希代码。但是,还可以通过使用 Hashtable 构造函数(该构造函数将 IHashCodeProvider 实现作为其参数之一接受),为 Hashtable 中的所有元素指定一个哈希函数。
在将一个对象添加到 Hashtable 时,它被存储在存储桶中,该存储桶与匹配该对象的哈希代码的哈希代码关联。当在 Hashtable 内搜索一个值时,为该值生成哈希代码,并且搜索与该哈希代码关联的存储桶。
例如,一个字符串的哈希函数可以采用该字符串中每一字符的 ASCII 代码并它们添加到一起来生成一个哈希代码。字符串“picnic”将具有与字符串“basket”的哈希代码不同的哈希代码;因此,字符串“picnic”和“basket”将处于不同的存储桶中。与之相比,“stressed”和“desserts”将具有相同的哈希代码并将处于相同的存储桶中。
using System;
using SystemCollections;
public class SamplesHashtable
{
public static void Main()
{
// Creates and initializes a new Hashtable
Hashtable myHT = new Hashtable();
myHTAdd( "First ", "3 ");
myHTAdd( "Second ", "2 ");
myHTAdd( "Third ", "1 ");
// Displays the properties and values of the Hashtable
ConsoleWriteLine( "myHT " );
ConsoleWriteLine( " Count: {0} ", myHTCount );
ConsoleWriteLine(myHT[ "First "]);//也可以这它!
ConsoleWriteLine( " Keys and Values: " );
PrintKeysAndValues( myHT );
}
public static void PrintKeysAndValues( Hashtable myList )
{
IDictionaryEnumerator myEnumerator = myListGetEnumerator();
ConsoleWriteLine( "\t-KEY-\t-VALUE- " );
while ( myEnumeratorMoveNext() )
ConsoleWriteLine( "\t{0}:\t{1} ", myEnumeratorKey, myEnumeratorValue);
}
}
<meta charset="utf-8">
想想一下,我们有一个数组,数组长度是100个,现在的需求是:给出这个数组是否包含一个对象obj?
如果这是个无序的数组,那么我们只能用遍历的方法来查找是否包含这个对象obj了。这是我们的时间复杂度就是O(n)。
这种查找效率是很低的,所以hash表应运而生。
hash表其实也是一个数组,区别数组的地方是它会建立 存储的值 到 存储的下标 索引的一个映射,也就是散列函数。
我们来举一个通俗易懂的例子:
现在我们有个hash表,表长度count = 16,现在我们依次把3,12,24,30依次存入hash表中。
首先我们来约定一个简单的映射关系:存储的索引下表(index) = 存储值(value) % hash表长度(count);
算下来hash表的存储分布是这样的:hash[3] = 3、hash[12] = 12、hash[8] = 24、hash[14] = 30
还是一样的需求,当我们给出24的时候,求出hash表中是否存有24?
此时,按照原先约定的映射关系:index = 24 % 16 = 8,然后我们在hash[8]查询等于24。这样,通过数组需要O(n)的时间复杂度,通过hash表只需要O(1);
上面提到的hash表在存入3,12,24,30后,如果要面临存入19呢?
此时index = 19 % 16 = 3,而之前hash[3] 已经存入了3这个值了!这种情况就是发送了散列碰撞。
此时,我们可以改进一下我们的hash表,让它存储的是一个链表。这样发送散列碰撞的元素就可以以链表的形式共处在hash表的某一个下标位置了。
所以,只要发生了散列碰撞,我们查找的时间复杂度就不能像O(1)这么小了,因为还要考虑链表的查找时间复杂度O(n)。
哈希表还有一个重要的属性: 负载因子(load factor),它用来衡量哈希表的 空/满 程度
当存储的元素个数越来越多,在hash表长度不变的前提下,发生散列碰撞的概率就会变大,查找性能就变低了。所以当负载因子达到一定的值,hash表会进行自动扩容。
哈希表在自动扩容时,一般会扩容出一倍的长度。元素的hash值不变,对哈希表长度取模的值也会改变,所以元素的存储位置也要相对应重新计算,这个过程也称为重哈希(rehash)。
哈希表的扩容并不总是能够有效解决负载因子过大而引起的查询性能变低的问题。假设所有 key 的哈希值都一样,那么即使扩容以后他们的位置也不会变化。虽然负载因子会降低,但实际存储在每个箱子中的链表长度并不发生改变,因此也就不能提高哈希表的查询性能。所以,设计一个合理有效的散列函数显得相当的有必要,这个合理有效应该体现在映射之后各元素均匀的分布在hash表当中。
说回NSDictionary
字典是开发中最常见的集合了。当我们调用
我们来探究下字典存储键值对的过程,有两个方法对hash存储起着关键的影响:
demo1
@interface KeyType : NSObject<NSCopying>
@property (nonatomic, copy) NSString keyName;
@end
@implementation KeyType
//直接**父类hash方法
//直接调用父类isEqual方法
@end
@implementation ViewController
@end
控制台打印:
for value
1
2
for key
hash func
copy func
2
分析:
diccount = 1,说明{key1 : @"object1"}已经存储进去了。然而通过这个key去获取竟然返回null?
从打印也可以看出来,现在isEqual函数开始被调用了。
分析:
//我们可以强制重写KeyType的isEqual:返回YES,demo2的返回值就不是null了
由此可见,当一个类需要作为字典的key,重写hash和isEqual:方法显得很有必要。
重写hash方法
为什么要重写hash方法?
我们先来看看NSObject的hash方法返回什么:
KeyType key1 = [[KeyType alloc] initWithKeyName:@"key1"];
NSLog(@"%p",key1);
NSLog(@"%lx",[key1 hash]);
控制台打印:
0x600000640610
600000640610
由此可见,NSObject是把对象的内存地址作为hash值返回。
以内存地址作为hash可以保证唯一性,但是这样好不好?
这样不好!
来看下这个场景:
@interface KeyType : NSObject<NSCopying>
@property (nonatomic, copy) NSString keyName;
@end
@implementation KeyType
//强制返回YES
@end
@implementation ViewController
很明显,最后打印是null。
但是在一般的业务场景,因为key1和key2的keyName属性都一样,所以应该被看为同一个key。
所以我们要重新hash方法。
如何重写hash方法
一个合理的hash方法要尽量让hash表中的元素均匀分布,来保证较高的查询性能。
如果两个对象可以被视为同一个对象,那么他们的hash值要一样。
mattt在文章Equality 中给出了一个普遍的算法:
Instagram在开源IGListKit的同时,鼓励这么写hash方法:
如何写一个合理高效的判等方法?
首先对内存地址进行判断,地址相等return YES;
进行判空处理,self == nil || object == nil ,return NO;
类型判断,![object isKindOfClass:[self class]] , return NO;
对对象的其他属性进行判断
根据这四个步骤,我们可以发现,我们都是先判断时间开销最少的属性。所以对于第4个步骤,如果对象有很多属性,我们也要依照这个原则来!比如[selfarray isEqual:otherarray] && selfintVal == otherintVal这种写法是不合理的,因为array的判等会去遍历元素,时间开销大。如果intVal不相等的话就可以直接return NO了,没必要进行数组的判等。应该这么写: selfintVal == otherintVal && [selfarray isEqual:otherarray]
示例如下:
以上就是关于redis中list和hash的基本命令和使用场景全部的内容,包括:redis中list和hash的基本命令和使用场景、c# 怎么读取hashtable某一个键的值、hashtable是什么等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)