redis-04 五大基本类型之Hash
redis-04 五大基本类型之Hash
概述
Redis 中的散列(hash)也是一种键值结构,它存储了字段(field)和字段值的映射,但是字段值只能是字符串,不能嵌套其它数据类型。一个散列类型最多只能包含 232 - 1 个字段。
Redis 的集合数据类型都不支持嵌套,散列里面不能存储散列等其它元素,集合里面不能存储列表而只能存储字符串。
命令
增和查
单个键值对的增加和查询和多个键值对的查询,包含的方法有 hset、hget(能够替代 hmset)、hmset(从 4.0 版本开始被弃用)、hmget:
@Test
public void setAndGet() {
Long effected = j.hset("car", "price", "500");
assertEquals(1L, effected.longValue());
effected = j.hset("car", "color", "red");
assertEquals(1L, effected.longValue());
String carPrice = j.hget("car", "price");
assertEquals("500", carPrice);
String carColor = j.hget("car", "color");
assertEquals("red", carColor);
Map<String, String> map = new HashMap<>();
map.put("color", "green");
map.put("weight", "1.3");
effected = j.hset("apple:1", map);
assertEquals(2L, effected.longValue());
map.replace("color", "red");
map.replace("weight", "3.1");
j.hmset("apple:2", map);
assertEquals(2L, effected.longValue());
String apple1Color = j.hget("apple:1", "color");
assertEquals("green", apple1Color);
String apple2Weight = j.hget("apple:2", "weight");
assertEquals("3.1", apple2Weight);
List<String> apple2Attributes = j.hmget("apple:2", "color", "weight");
assertEquals("red", apple2Attributes.get(0));
assertEquals("3.1", apple2Attributes.get(1));
Map<String, String> apple1All = j.hgetAll("apple:1");
assertEquals("green", apple1All.get("color"));
assertEquals("1.3", apple1All.get("weight"));
}
删和改
主要是
- hexists:判断 hash 的某个字段是否存在;
- hsetnx:如果 hash 的某个字段不存在则设置成指定的值;
- hincrBy:将 hash 某个字段的值增加指定的值;
- hincrByFloat:将 hash 某个字段的值增加指定的浮点数值;
- hdel :删除 hash 的制定字段
和 String 类型的基本相同,但是前面多了一个字母 h。
@Test
public void otherHashCommands() {
long effected = j.hset("apple", "color", "red");
assertEquals(1L, effected);
Boolean isExist = j.hexists("apple", "color");
assertEquals(true, isExist);
effected = j.hsetnx("apple", "weight", "3");
assertEquals(1L, effected);
Long incrWeight = j.hincrBy("apple", "weight", 1L);
assertEquals(4L, incrWeight.longValue());
double incrWeightFloat = j.hincrByFloat("apple", "weight", 0.3);
assert incrWeightFloat - 4.3 < 0.01;
effected = j.hdel("apple", "color", "weight");
assertEquals(2L, effected);
List<String> list = j.hmget("apple", "color", "weight");
list.forEach(Assert::assertNull);
}
获取键值对信息
- hstrlen:获取字段值的长度信息;
- hkeys:获取一个 hash 里面的全体字段;
- hvals:获取一个 hash 里面的全体字段值;
- hlen:获取一个 hash 里面的键值对总个数。
@Test
public void getOnlyKeysOrValue() {
long effected = j.hset("apple", "color", "red");
assertEquals(1L, effected);
effected = j.hset("apple", "weight", "3.14");
assertEquals(1L, effected);
long colorLen = j.hstrlen("apple", "color");
assertEquals(3L, colorLen);
Set<String> keys = j.hkeys("apple");
assertTrue(keys.contains("color"));
assertTrue(keys.contains("weight"));
List<String> values = j.hvals("apple");
assertTrue(values.contains("red"));
assertTrue(values.contains("3.14"));
long len = j.hlen("apple");
assertEquals(2L, len);
}
实践
存储对象
hash 里存储键值对的设计似乎就是为对象存储量身定制的,传统的序列化对象存储方式无论实在可扩展性还是操作复杂程度上都不尽人意,比如在当我只想要对象的某个字段的时候,如果采用序列化的方式进行存储的话,我得把整个对象都反序列化,然后取出想要的部分。
改进序列化做法的一种方式就是使用多个字符串类型来存储各自的值,比如这样:
看起来不错!各个属性之间通过具有统一前缀的键名关联在一起,可扩展性和操作性比较高。但是在查询一篇文章的所有信息的时候难免有些复杂和冗余,需要分开查询(删除也一样)
@Test
public void setAndGetObjectSeparately() {
Apple apple = new Apple();
String color = apple.getColor();
float weight = apple.getWeight();
long serialVersionUID = apple.getSerialVersionUID();
String status = j.set("apple:color", color);
assertEquals("OK", status);
status = j.set("apple:weight", String.valueOf(weight));
assertEquals("OK", status);
status = j.set("apple:serialVersionUID", String.valueOf(serialVersionUID));
assertEquals("OK", status);
String newColor = j.get("apple:color");
float newWeight = Float.parseFloat(j.get("apple:weight"));
long newSerialVersionUID = Long.parseLong(j.get("apple:serialVersionUID"));
Apple newApple = new Apple();
newApple.setColor(newColor);
newApple.setSerialVersionUID(newSerialVersionUID);
newApple.setWeight(newWeight);
assertEquals(newApple, apple);
}
有了 hash,问题就变得简单了起来,有一种牵一发而动全身的感觉,在存储上也更加具有对象的结构: