Redis的set是一个在开发过程中常用的数据接口,在许多介绍Redis的文章中,简单的介绍了redis set的去重效果,那么在java环境下(例如使用spring data redis),将对象放入redis时,redis set的重复判断又是如何进行的呢?
先猜想是不是用过java对象的equal/hash方法来判断是否相等,请看下面的程序,如果调用两次,redis的set会出现几个数据?
public String hellow(Long id,String name){
RedisSetTestDTO dto=new RedisSetTestDTO();
dto.setId(id);
dto.setName(name);
optimusCacheService.getRedisTemplate().opsForSet().add(cacheKey, dto);
Long size=optimusCacheService.getRedisTemplate().opsForSet().size(cacheKey);
return JSON.toJSONString(size);
}
使用相同的参数,调用了两次方法,结果redis的set里面只有一条数据,那么说明,redis并不是依靠dto本身的equal方法来判断
再次猜想,是否是使用java Serializable的序列化以后来对比对象的呢?
javadoc中对这两个类的描述中对java的序列化机制进行了详细的描述:
The default serialization mechanism for an object writes the class of the object, the class signature, and the values of all non-transient and non-static fields. References to other objects (except in transient or static fields) cause those objects to be written also. Multiple references to a single object are encoded using a reference sharing mechanism so that graphs of objects can be restored to the same shape as when the original was written.
默认的序列化机制写到流中的数据有:
1、对象所属的类
2、类的签名
3、所有的非transient和非static的属性
4、对其他对象的引用也会造成对这些对象的序列化
5、如果多个引用指向一个对象,那么会使用sharing reference机制
意思即是,对类做上述的5种修改,都会影响序列化的结果 ,经过尝试后,set的size发生了增长(这里要注意,在java代码中不能使用member命令来获取set中的值,会发生反序列化的错误)
那么我们先来看一下使用spring-data-redis来操作redis set的源码
public void hellow(Long id,String name){
String cacheKey="Redis_Set_Duplicate_Set:Test";
RedisSetTestDTO dto=new RedisSetTestDTO();
dto.setId(id);
dto.setName(name);
dto.setCreateTm(DateUtil.getCurrentTime());
optimusCacheService.getRedisTemplate().opsForSet().add(cacheKey, dto);
System.out.println("-----------------");
}
public Long add(K key, V... values) {
final byte[] rawKey = rawKey(key);
final byte[][] rawValues = rawValues(values);
return execute(new RedisCallback<Long>() {
public Long doInRedis(RedisConnection connection) {
return connection.sAdd(rawKey, rawValues);
}}, true);
}
在代码块2的第二和第三行中,对key和values进行转换,key转换为一个byte数组,value转换为一个二维byte数组,下面来详细看一下是如何转换的。先是key,虽然和去重没什么关系,姑且也看一下
byte[] rawKey(Object key) {
Assert.notNull(key, "non null key required");
if (keySerializer() == null && key instanceof byte[]) {
return (byte[]) key;
}
return keySerializer().serialize(key);
}
这里出现了一个叫keySerializer().serialize的东西,keySerializer是我们在配置Spring-data-redis时设置的属性
StringRedisSerializer实现了serialize方法
public byte[] serialize(String string) {
return (string == null ? null : string.getBytes(charset));
}
来看一下关键的JdkSerializationRedisSerializer的serialize方法,下面的方法展示了value值序列化的过程,简单来说就是将一个java对象按照byte进行读取,最后将byte数组放入redis
请注意最后一段的注释,实现Serializable实现接口的java对象会按照jdk序列化的方式被读取成byte
public Long add(K key, V... values) {
final byte[] rawKey = rawKey(key);
final byte[][] rawValues = rawValues(values);
return execute(new RedisCallback<Long>() {
public Long doInRedis(RedisConnection connection) {
return connection.sAdd(rawKey, rawValues);
}
}, true);
}
byte[][] rawValues(Object... values) {
final byte[][] rawValues = new byte[values.length][];
int i = 0;
for (Object value : values) {
rawValues[i++] = rawValue(value);
}
return rawValues;
}
byte[] rawValue(Object value) {
if (valueSerializer() == null && value instanceof byte[]) {
return (byte[]) value;
}
return valueSerializer().serialize(value);
}
public byte[] serialize(Object object) {
if (object == null) {
return SerializationUtils.EMPTY_ARRAY;
}
try {
return serializer.convert(object);
} catch (Exception ex) {
throw new SerializationException("Cannot serialize", ex);
}
}
public byte[] convert(Object source) {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream(1024);
try {
this.serializer.serialize(source, byteStream);
return byteStream.toByteArray();
}
catch (Throwable ex) {
throw new SerializationFailedException("Failed to serialize object using " +
this.serializer.getClass().getSimpleName(), ex);
}
}
/**
* Writes the source object to an output stream using Java serialization.
* The source object must implement {@link Serializable}.
* @see ObjectOutputStream#writeObject(Object)
*/
public void serialize(Object object, OutputStream outputStream) throws IOException {
if (!(object instanceof Serializable)) {
throw new IllegalArgumentException(getClass().getSimpleName() + " requires a Serializable payload " +
"but received an object of type [" + object.getClass().getName() + "]");
}
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(object);
objectOutputStream.flush();
}
看到这里,即验证了,使用Spring-data-redis在配置JdkSerializationRedisSerializer的情况下,向redis set中存放元素会先使用Serializable接口序列化为byte数组,然后再放入redis,若byte数组中的内容相同,则会被认为set中的元素重复。所以在redis set中原本有数据的情况下,如果java 类本身发生了变更,是无法与原有数据兼容的。