Redis客户端中文乱码
网上非常常见的乱码,百度一搜大部分都是这种情况。如果不是此种情况请看下一节。
redis-cli
redis 127.0.0.1:6379> set 'name' '中文'
OK
redis 127.0.0.1:6379> get 'name'
"\xd6\xd0\xce\xc4"
redis 127.0.0.1:6379>
客户端查看乱码,这个情况我们只要将修改客户端命令行就可以。redis-cli --raw
redis-cli --raw
redis 127.0.0.1:6379> get 'name'
中文
redis 127.0.0.1:6379>
Jedis 或 RedisTemplate获取乱码
- 直接使用Jedis 或 RedisTemplate实现存取没有乱码
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class RedisTest {
@Autowired
RedisTemplate<String, String> redisTemplate;
// @Autowired
// StringRedisTemplate redisTemplate;
@Test
public void testRedisTemplate(){
ValueOperations<String, String> valueOperation =
redisTemplate.opsForValue();
valueOperation.set("testname, "NO爷");
String testName = valueOperation.get("testname");
System.out.println(testName);//输出 NO爷
}
@Test
public void testJedis(){
Jedis jedis = new Jedis("192.168.130.247");
jedis.set("nickname", "NO爷");
String nickName = jedis.get("nickname");
System.out.println(nickName);//输出 NO爷
}
}
- 客户端直接存储,使用Jedis或者Redis读取数据乱码,这个地方直接使用程序直接读取上一节Redis客户端存储的“name”,这个时候获取到乱码“����”。
这种情况是由于redis安装环境和Java程序环境的编码格式不一致导致。
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class RedisTest2 {
@Autowired
RedisTemplate<String, String> redisTemplate;
// @Autowired
// StringRedisTemplate redisTemplate;
@Test
public void testRedisTemplate(){
ValueOperations<String, String> valueOperation =
redisTemplate.opsForValue();
String testName = valueOperation.get("name");
System.out.println(testName);//输出 ����,不同地环境可能对其他乱码。
}
@Test
public void testJedis(){
Jedis jedis = new Jedis("192.168.130.247");
String nickName = jedis.get("name");
System.out.println(nickName);//输出 ����,不同地环境可能对其他乱码。
}
}
- 针对乱码解决:改变获取数据方式,不要直接获取字符串,而是获取字节数组。
还有一种更加优雅的方式见下一节的源码分析。
直接获取字符串会直接破环原始字节数据,如果是原始数据格式ISO-8859-1,我们可以直接使用new String(name.getBytes(“ISO-8859-1”), “UTF-8”),这种方式解析。但是如果是GBK等其他编码格式就会搞出其他乱码,这个具体我也不清楚了。
我的Redis服务器编码格式GBK,Java运行环境是UTF-8。
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class RedisTest3 {
@Autowired
RedisTemplate<String, String> redisTemplate;
// @Autowired
// StringRedisTemplate redisTemplate;
@Test
public void testRedisTemplate() throws UnsupportedEncodingException {
RedisConnection connection =
redisTemplate.getConnectionFactory().getConnection();
byte[] name = connection.get("name".getBytes());
System.out.println(new String(name, "GBK"));// 输出 中文
}
@Test
public void testJedis() throws UnsupportedEncodingException {
Jedis jedis = new Jedis("192.168.130.247");
byte[] name = jedis.get("name".getBytes());
System.out.println(new String(name, "GBK"));// 输出 中文
}
}
源码分析
- 上文列举了Jedis和RedisTemplate两种方式,因为RedisTemplate的底层实现就是Jedis。
- 上文使用RedisTemplate模板类是使用RedisTemplate注入,这个地方可以直接使用StringRedisTemplate注入。
使用RedisTemplate注入需要指定泛型类型,需要指定序列化对象,否则使用默认序列化机制,这个更加容易出现乱码。
/**
* RedisTemplate 代码片段
* 可以看到默认序列化类是使用JdkSerializationRedisSerializer,
* 这个也会导致程序读取乱码。
*/
public void afterPropertiesSet() {
super.afterPropertiesSet();
boolean defaultUsed = false;
if (this.defaultSerializer == null) {
this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}
if (this.enableDefaultSerializer) {
if (this.keySerializer == null) {
this.keySerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.valueSerializer == null) {
this.valueSerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.hashKeySerializer == null) {
this.hashKeySerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.hashValueSerializer == null) {
this.hashValueSerializer = this.defaultSerializer;
defaultUsed = true;
}
}
if (this.enableDefaultSerializer && defaultUsed) {
Assert.notNull(this.defaultSerializer, "default serializer null and not all serializers initialized");
}
if (this.scriptExecutor == null) {
this.scriptExecutor = new DefaultScriptExecutor(this);
}
this.initialized = true;
}
/**
* StringRedisTemplate 源码
* 在初始构造时使用了StringRedisSerializer类来实现序列化,
* 一般Redis如果存储的数据没有特殊的话都是使用这个。
*/
public class StringRedisTemplate extends RedisTemplate<String, String> {
public StringRedisTemplate() {
RedisSerializer<String> stringSerializer = new StringRedisSerializer();
this.setKeySerializer(stringSerializer);
this.setValueSerializer(stringSerializer);
this.setHashKeySerializer(stringSerializer);
this.setHashValueSerializer(stringSerializer);
}
public StringRedisTemplate(RedisConnectionFactory connectionFactory) {
this();
this.setConnectionFactory(connectionFactory);
this.afterPropertiesSet();
}
protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
return new DefaultStringRedisConnection(connection);
}
}
- 常用的序列化还有json、xml等格式选择。
- 我们再看一下StringRedisSerializer源码,在构造时直接指定编码格式为UTF-8,同时提供了构造重载设置编码格式。那么我们再试试,使用“GBK”编码的StringRedisSerializer是否还会有乱码?
/**
* StringRedisSerializer 源码
*/
public class StringRedisSerializer implements RedisSerializer<String> {
private final Charset charset;
public StringRedisSerializer() {
this(Charset.forName("UTF8"));
}
public StringRedisSerializer(Charset charset) {
Assert.notNull(charset, "Charset must not be null!");
this.charset = charset;
}
public String deserialize(byte[] bytes) {
return bytes == null ? null : new String(bytes, this.charset);
}
public byte[] serialize(String string) {
return string == null ? null : string.getBytes(this.charset);
}
}
/**
* 使用带有GBK编码的构造实例化StringRedisSerializer,
* 正如所料,正确读取到中文数据。
*/
@Test
public void testRedisTemplate(){
RedisSerializer<String> stringSerializer = new StringRedisSerializer(Charset.forName("GBK"));
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setValueSerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
redisTemplate.setHashValueSerializer(stringSerializer);
ValueOperations<String, String> valueOperation = redisTemplate.opsForValue();
String name = valueOperation.get("name");
System.out.println(name);//输出 中文
}