Loading

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());
}
posted @ 2021-09-29 00:59  槐下  阅读(97)  评论(0编辑  收藏  举报