前言

呵呵 最近同事问到了这样的一个问题 

因为在实际的场景中似乎是出现了乱码问题, 他问道 "redis 里面字符串默认是按照什么编码存放的? "

呵呵 我一愣, 字节 经过 编码 转换后就是我们常说的 字符 

然后 映像中 sds 中也没有提到 编码相关阿? 

呵呵 因此当时 记录下了 这个问题, 今天来看一看  

这个问题 也可以扩展到 其他的 客户端 和 服务器 之间的数据交互 

我们这里将着重从 服务端的编码 和 多种不同的客户端 来看一下 客户端的编码处理 

"你好" 的字节序列

utf8 编码之后为 \xe4\xbd\xa0\xe5\xa5\xbd 

gbk 编码之后为 \xc4\xe3\xba\xc3

redis-server 交互的编码 

redis 中字符串对象是存储于 sds 中, 其存储核心数据的方式是 char[] 

在 c/c++ 中 char 是 1 字节, 那么在服务器这边是 单字节 编码, 数据如果是出现了乱码, 那只能说明 客户端存取的编码不一致 

redis-cli 交互的编码 

redis-cli 这边是通过 read 函数来读取的命令行的输入, 那么这个具体的编码方式是取决于操作系统 

我们可以 验证一下 

在 mac 下面, 默认的 字符编码集 是 utf8 

master:redis-6.2.0 jerry$ ./src/redis-cli 
127.0.0.1:6379> set name 你好
OK
127.0.0.1:6379> get name
"\xe4\xbd\xa0\xe5\xa5\xbd"
127.0.0.1:6379> 

在 linux 上面 默认的 字符编码集 是 utf8, 这里以一个 docker 容器中的 redis-cli 来进行交互 

master:redis jerry$ docker exec -it redis /bin/sh
# cat /etc/issue
Debian GNU/Linux 11 \n \l
# redis-cli -h 192.168.0.246
192.168.0.246:6379> set name 你好
OK
192.168.0.246:6379> get name
"\xe4\xbd\xa0\xe5\xa5\xbd"

在 windows 上面 默认的 字符编码集 是 gbk, 这里下载了一个 windows 版本的 redis 使用其中的 redis-cli 来进行交互 

PS C:\Users\Jerry.X.He\Desktop\redis-64.3.0.503> ./redis-cli -h 192.168.0.246
192.168.0.246:6379> get name
"\xe4\xbd\xa0\xe5\xa5\xbd"
192.168.0.246:6379> set name ᅣ ̄채
OK
192.168.0.246:6379> get name
"\xc4\xe3\xba\xc3"

直接在网上查询, 搜索也能获取到上面的信息, 但是 往往 还是更加确切一些的东西, 会更加有说服力一些, 使用 类似的代码 来读取, 然后 查看具体的内存的信息 

这里不同的编码集可以使用是一个 case 来获取编码之后的结果 

//
// Created by Jerry.X.He on 2021/11/6.
//
#include "stdio.h"

int main(int argc, char **argv) {
    char ch[10];
    printf("please input ch : ");
    scanf("%s", &ch);
    printf("output ch : %s\n", ch);
    printf("output ch : %x %x %x %x %x %x\n", ch[0], ch[1], ch[2], ch[3], ch[4], ch[5]);

    return 0;
}

mac 上面演示如下 

jedis 交互的编码 

首先来一个简单的 case, 执行能够获取到 name 对应的 "你好" 

import redis.clients.jedis.Jedis;

/**
 * Test18JedisSetNihao
 *
 * @author Jerry.X.He <970655147@qq.com>
 * @version 1.0
 * @date 2021-11-06 16:53
 */
public class Test18JedisSetNihao {

    // Test18JedisSetNihao
    public static void main(String[] args) {

        Jedis jedis = new Jedis("127.0.0.1", 6379);
//        jedis.auth("123456");

        String oldName = jedis.get("name");
//        jedis.set("name", "你好");
        System.out.println(oldName);
        int x = 0;

    }

}

跟踪一下对应的编码解码部分, 可以看到 默认使用的是一个 Protocol. CHARSET 

点进去, 是一个 final 的字符串, 编码为 "utf8"

因为这个 charset 没有配置的地方, 因此如果 客户端使用 其他编码存储, 使用 jedis 读取出来会是乱码 

另外因为序列本身 也不符合 utf8 的编码规范, 所以在解码的过程中 原始的字节序列都还原不了了 

redisson 交互的编码

同样先来一段 case, 可以看到的是 这里是可以指定 编码方式的  

/**
 * Test18JedisSetNihao
 *
 * @author Jerry.X.He <970655147@qq.com>
 * @version 1.0
 * @date 2021-11-06 16:53
 */
public class Test18RedissonSetNihao {

    // Test18JedisSetNihao
    public static void main(String[] args) throws Exception {

        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
        RedissonClient redissonClient = Redisson.create(config);

        StringCodec defaultCodec = StringCodec.INSTANCE;
        StringCodec gbkCodec = new StringCodec(Charset.forName("gbk"));
        RBucket<String> nameObjBucket = redissonClient.getBucket("name", defaultCodec);
        Object nameObj = nameObjBucket.get();
        int x = 0;

    }

}

它的编码解码 取决于传入的 codec, 默认的 charset 为 utf8 

所以 他是可以适配任意字符集的 

windows 的 redis-cli 写入数据, 只要他指定 charset 为 gbk, 就可以正确的读取   

linux 的 redis-cli 写入数据, 只要他指定 charset 为 utf8, 就可以正确的读取  

jedis 写入的数据, 只要他指定 charset 为 utf8, 就可以正确的读取  

redisson 按照 指定编码集 concreteCharset 写入的数据, 只要他指定 charset 为 concreteCharset, 就可以正确的读取  

redisTemplate 交互的编码

 同样来一段 case, 可以看到的是 这里是可以指定 编码方式的  

/**
 * Test18JedisSetNihao
 *
 * @author Jerry.X.He <970655147@qq.com>
 * @version 1.0
 * @date 2021-11-06 16:53
 */
public class Test18RedisTemplateSetNihao {

    // Test18JedisSetNihao
    public static void main(String[] args) throws Exception {

        RedisStandaloneConfiguration jedisConfig = new RedisStandaloneConfiguration();
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(jedisConfig);
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(jedisConnectionFactory);

        stringRedisTemplate.setValueSerializer(new StringRedisSerializer(Charset.forName("utf8")));
//        stringRedisTemplate.setValueSerializer(new StringRedisSerializer(Charset.forName("gbk")));

        BoundValueOperations<String, String> nameOpts = stringRedisTemplate.boundValueOps("name");
//        nameOpts.set("你好");
        Object nameObj = nameOpts.get();
        int x = 0;

    }

}

它的编码解码 取决于配置的 valueSerializer 

所以 他是可以适配任意字符集的 

windows 的 redis-cli 写入数据, 只要他指定 charset 为 gbk, 就可以正确的读取   

linux 的 redis-cli 写入数据, 只要他指定 charset 为 utf8, 就可以正确的读取  

jedis 写入的数据, 只要他指定 charset 为 utf8, 就可以正确的读取  

redisson 按照 指定编码集 concreteCharset 写入的数据, 只要他指定 charset 为 concreteCharset, 就可以正确的读取  

rdisTemplate 按照 指定编码集 concreteCharset 写入的数据, 只要他指定 charset 为 concreteCharset, 就可以正确的读取  

其他 

这个问题 也可以扩展到 其他的 客户端 和 服务器 之间的数据交互 

呵呵 这个就要你多去思考了, 更加细节的了解 为什么会产生乱码 

完 

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐