redis数据结构

39 阅读4分钟

支持的数据结构

确实是字符串,list/set,map,都支持。

然后也提供了对应数据结构的写和读的命令。

但是key/value的存储,不管是什么数据结构,都是字节数组。也就是说,在redis里面存储的都是字节数组。那序列化和反序列化是在哪里完成?客户端。

下面分别以几个数据结构举例子,并且加上代码,就比较容易理解。

字符串

key始终是字符串类型,因为key只有字符串类型,没有其他类型。

重点看value是什么类型,到底是字符串?还是对象?

而且,更重要的是,这里说的value到底是什么类型,是站在java客户端的角度去说的,在redis服务器端根本不存在什么对象类型,只有字符串类型。

value是字符串

value的写和读,都是字符串,没有什么特殊的。

value是对象

需要先在客户端序列化(本质其实是把对象编码为字节数组)为字符串(从编码之后的字节数组得到字符串)。

有点拗口。

字节数组和字符串,本质是一回事。可以互相转换。

但是序列化和反序列化是需要编码和解码的,编码的目的就是为了在存储的时候把对象序列化为字节数组——本质是为了方便通信和存储,解码的目的是为了能够把字节数组还原为java对象。


看代码就知道了:redis工具类

public void set(String key, Object obj) {
    executeSync(commands -> {
        commands.set(key, obj instanceof String ? (String) obj : JsonUtil.writeJson(obj));
        return null;
    });
}

如果是字符串类型,那么不需要序列化。如果是对象,就需要先序列化为字符串——然后再写入redis。


再来看下序列化的工具类

自定义工具类:com.itranswarp.util.JsonUtil#writeJson(java.lang.Object)

public static String writeJson(Object obj) {
    try {
        return OBJECT_MAPPER.writeValueAsString(obj);
    } catch (JsonProcessingException e) {
        throw new RuntimeException(e);
    }
}

底层其实是jackson框架:com.fasterxml.jackson.databind.ObjectMapper#writeValueAsString

public String writeValueAsString(Object value) throws JsonProcessingException {
  SegmentedStringWriter sw = new SegmentedStringWriter(this._jsonFactory._getBufferRecycler());

  try {
    this._writeValueAndClose(this.createGenerator((Writer)sw), value);
  } catch (JsonProcessingException var4) {
    throw var4;
  } catch (IOException var5) {
    throw JsonMappingException.fromUnexpectedIOE(var5);
  }

  return sw.getAndClear();
}

list

其实和字符串数据结构一样,只不过现在的value是集合。但是集合里的元素仍然是只有字符串类型。也就是说,如果客户端侧的元素是字符串类型,那么不需要序列化;如果元素是对象,就需要先序列化。


代码:redis客户端-lettuce

io.lettuce.core.api.sync.RedisListCommands#lpush

/**
 * Prepend one or multiple values to a list.
 *
 * @param key the key.
 * @param values the value.
 * @return Long integer-reply the length of the list after the push operations.
 */
Long lpush(K key, V... values);

map

也差不多,主要来看一下元素的数据类型。也是站在客户端侧来看元素的数据类型到底是字符串还是对象。


代码:redis工具类

public void hset(String key, Object field, Object obj) {
    executeSync(commands -> {
        commands.hset(key, field.toString(), obj instanceof String ? (String) obj : JsonUtil.writeJson(obj));
        return null;
    });
}

因为是map数据结构,所以value是map。而map的每个元素都是key/value,和list一样,元素的value可以是字符串或者对象,和list不一样的是,每个元素还多了一个key,元素的key也可以是字符串或者对象——虽然key可以是对象,但是一般情况下的应用场景都是字符串。

所以,每个元素的key/value都可以是字符串类型或者对象类型,如果是字符串类型,就不需要序列化,如果是对象类型,就需要先序列化。

总结

除了数据结构这个维度,另外要注意的一个维度就是,客户端侧需要区分元素的数据类型是字符串还是对象,如果是字符串,就不需要序列化,如果是对象,就需要先序列化为字符串。

为什么字符串不需要序列化/编码?因为字符串本身就是字节数组,字符串和字节数组本来就可以直接互相转换。但是对象是需要先序列化/编码的,因为对象是java代码里的概念,redis并没有这个东西,redis只能存储字节数组,要想存储到redis,就需要在客户端侧先序列化,然后从redis读出来的数据也是字节数组,所以客户端侧读出来之后就需要反序列化为java对象。