redis-03 五大基本类型之String
redis-03 五大基本类型之String
概述
字符串类型是 Redis 中最基本的数据类型,掌握好它以及相关命令的使用是一切的一切的基础。
Redis 的 String 类型可以是字符串(简单的字符串、复杂的字符串(例如 JSON、XML))、数字(整数、浮点数),甚至是二进制(图片、音频、视频),但是值最大不能超过512MB;
命令
赋值与取值
最基本的 set 和 get 命令,但是要注意这样的代码块会在高并发的时候出现 race condition,后续可以使用原子操作来解决这个问题:
@Test
public void setAndGetTest() {
String status = j.set("views", "123");
assertEquals("OK", status);
String value = j.get("views");
assertEquals("123", value);
int newViews = Integer.parseInt(value) + 1;
status = j.set("views", String.valueOf(newViews));
assertEquals("OK", status);
assertEquals("124", j.get("views"));
}
数字递增
当存储的字符串是整数形式时,redis 提供了一个原子的自增操作:
@Test
public void incrAndIncrBy() {
String status = j.set("view", "123");
assertEquals("OK", status);
Long view = j.incr("view");
assert (124L == view);
view = j.incrBy("view", 2L);
assert (126L == view);
}
实践扩展
Redis 键命名
Redis 键命名没有统一规范,但最好使用:
对象类型:对象ID:对象属性
apple:1:color
room:2:chairs
对于多个单词,不推荐使用驼峰命名或者是下划线连接,而是使用.
连接,比如:
post:123:page.view
用来表示 ID 为 123 的文章的 page_view 或者是 pageView。
主键自增策略
看到递增就不免让人想到递增主键,在 MySQL 中只需要将字段设置成 AUTO_INCREMENT 就能在插入的时候实现自增。在使用 redis 时,我们只需要存储一个名为 object_names:count 的键来存储当前类型对象的数量,每增加一个对象的时候都使用 incr 命令增加它的值。
当 incr 命令所执行的键没有实际对应时(不存在这个键),redis 会自动创建这个键,设初值为 0 并且马上自增 1,返回的结果也是 1。
存储序列化内容
开篇就介绍过了,String 类型能够存储几乎所有的内容,前提时要经过序列化。这里我演示一个序列化对象的例子:
public class Apple implements Serializable {
long serialVersionUID = 1L;
String color = "red";
float weight = 3.14f;
@Override
public boolean equals(Object obj) {
Apple target = (Apple) obj;
return this.color.equals(target.color)
&& this.weight == target.weight
&& this.serialVersionUID == target.serialVersionUID;
}
}
@Test
public void setAndGetObject() {
// 序列化对象得到字符串,使用 base64 编码
// may occur that invalid header for outPutStream/InputStream
Apple oldApple = new Apple();
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
String oldAppleStr = null;
try {
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(oldApple);
oldAppleStr = Base64.getEncoder().encodeToString(bos.toByteArray());
} catch (IOException e) {
e.printStackTrace();
}
String status = j.set("apple", oldAppleStr);
assertEquals("OK", status);
String newAppleStr = j.get("apple");
assertEquals(oldAppleStr, newAppleStr);
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
bis = new ByteArrayInputStream(Base64.getDecoder().decode(newAppleStr));
ois = new ObjectInputStream(bis);
Apple newApple = (Apple) ois.readObject();
assertEquals(oldApple, newApple);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
try {
oos.close();
bos.close();
ois.close();
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
如果测试通过,可以看到将对象序列化并且编码成字符串后可以存储到 redis 中,并且能够通过相反的过程重新反序列化回来。这个过程就有两点值得注意,一个是序列化,一个是编码。
命令拾遗钩沉
递减和按值递减
结果同递增相反,其它部分都相像:
@Test
public void decrAndDecrBy() {
Long view = j.decr("view");
assertEquals(-1L, view.longValue());
view = j.decrBy("view", 4L);
assertEquals(-5L, view.longValue());
String viewStr = j.get("view");
assertNotNull(viewStr);
}
按照指定浮点数递增加
减少的话改成负的就可以了:
@Test
public void incrByFloat() {
Double weight = j.incrByFloat("weight", 45.15D);
assert weight - 45.15D < 0.001;
weight = j.incrByFloat("weight", 10.35D);
assert weight - 55.50D < 0.001;
weight = j.incrByFloat("weight", -10L);
assert weight - 45.50D < 0.01;
}
字符串长度、追加、批量操作
对应 append、strlen、mset、mget 命令:
@Test
public void appendANdLen() {
String status = j.set("key", "hello");
assertEquals("OK", status);
Long key = j.append("key", " world!");
assertEquals(12L, key.longValue());
String keyStr = j.get("key");
assertEquals(keyStr, "hello world!");
Long strLen = j.strlen("key");
assertEquals(12L, strLen.longValue());
}
@Test
public void mSetAndMGet() {
String status = j.mset("a", "1", "b", "2", "c", "3");
assertEquals("OK", status);
String b = j.get("b");
assertEquals("2", b);
List<String> list = j.mget("a", "c");
assertEquals("1", list.get(0));
assertEquals("3", list.get(1));
}
位操作
众所周知,我们所设计的数据库中往往存在许多标称属性,如果用 int 去存储的话一般需要 4 个字节(我经常这样干,手动狗头 🐶),也就是 32 bit;如果使用位存储,4 bit 就能存储 16 种对应的标称属性(24 = 16),很高效!就比如我么最常存储的性别属性,一个 bit 就能解决的事情,何必要用 1 个字节的布尔类型呢(C++)?
Redis 对位操作的支持很广,以下测试就包含了有获取、设置指定 index 的 bit 位,统计指定范围内 1 或者 0 的个数。
还有最基本的与或非位运算,在 Redis 中凡事碰到了位串过短、下标超过位串长度等问题,Redis 统统采用扩展补 0 的方法来解决。
@Test
public void bitOperation1() {
// bar 对应的 ascii 是 98、97、114
// 二进制是 0110_0010, 0110_0001, 0111_0010
// 三个字符按照我们认为的顺序并排连接
String status = j.set("foo", "bar");
assertEquals("OK", status);
Boolean foo_0_bit = j.getbit("foo", 0L);
assertEquals(false, foo_0_bit);
Boolean foo_8_bit = j.getbit("foo", 6L);
assertEquals(true, foo_8_bit);
// 超出范围,默认为 0
Boolean foo_max_bit = j.getbit("foo", 100L);
assertEquals(false, foo_max_bit);
Boolean statusSet = j.setbit("foo", 6L, false);
assertEquals(true, statusSet);
statusSet = j.setbit("foo", 7L, true);
assertEquals(false, statusSet);
Long fooBitCount = j.bitcount("foo");
assertEquals(10L, fooBitCount.longValue());
// aa 中 i bit 的个数
fooBitCount = j.bitcount("foo", 0L, 1L);
assertEquals(6L, fooBitCount.longValue());
}
@Test
public void bitOperation2() {
// redis 内置的 与或非操作
String status = j.set("foo1", "bar");
assertEquals("OK", status);
status = j.set("foo2", "aar");
assertEquals("OK", status);
// 结果存储在 destKey 中,返回结果字符串的字节长度
Long result = j.bitop(BitOP.OR, "foo1", "foo2");
assertEquals(3L, result.longValue());
status = j.set("foo", "bar");
assertEquals("OK", status);
// 查找字符串第一个 0 bit 的位置
Long pos = j.bitpos("foo", true);
assertEquals(1L, pos.longValue());
// 1 和 2 代表查询的范围是从第 1 到第 2 个字节,如果超出范围则补 0
pos = j.bitpos("foo", true, new BitPosParams(1L, 2L));
assertEquals(9L, pos.longValue());
}