为什么有时候需要实现Serilizable接口,你真的清楚了吗?

/ 默认分类 / 0 条评论 / 721浏览

字节流和字符流问题

  1. 来说一下字节流,在计算机中,一切文件数据(文本、图片、视频等)在存储时,都是以二进制的形式保存,都是一个一个的字节,传输时也是这样的。所以,字节流可以传输和读取任意文件数据。在操作流的时候,我们要明确,无论是用什么样的流对象,底层传输的始终为二进制数据。

Java IO库有两个支系

为什么要序列化

实现了Serializable接口的类可以被ObjectOutputStream转换为字节流,同时也可以通过ObjectInputStream再将其解析为对象。例如,我们可以将序列化对象写入文件后,再次从文件中读取它并反序列化成对象,也就是说,可以使用表示对象及其数据的类型信息和字节在内存中重新创建对象。而这一点对于面向对象的编程语言来说是非常重要的,因为无论什么编程语言,其底层涉及IO操作的部分还是由操作系统其帮其完成的,而底层IO操作都是以字节流的方式进行的,所以写操作都涉及将编程语言数据类型转换为字节流,而读操作则又涉及将字节流转化为编程语言类型的特定数据类型。而Java作为一门面向对象的编程语言,对象作为其主要数据的类型载体,为了完成对象数据的读写操作,也就需要一种方式来让JVM知道在进行IO操作时如何将对象数据转换为字节流,以及如何将字节流数据转换为特定的对象,而Serializable接口就承担了这样一个角色。

序列化控制

默认的序列化机制已经很强大了,它可以自动将对象中的所有字段自动保存和恢复,但这种默认行为有时候不是我们想要的。比如,对于有些字段,它的值可能与内存位置有关,比如默认的hashCode()方法的返回值,当恢复对象后,内存位置肯定变了,基于原内存位置的值也就没有了意义。还有一些字段,可能与当前时间有关,比如表示对象创建时的时间,保存和恢复这个字段就是不正确的。

还有一些情况,如果类中的字段表示的是类的实现细节,而非逻辑信息,那默认序列化也是不适合的。为什么不适合呢?因为序列化格式表示一种契约,应该描述类的逻辑结构,而非与实现细节相绑定,绑定实现细节将使得难以修改,破坏封装。

Java提供了多种定制序列化的机制,主要的有三种,一种是transient关键字,另外一种是实现Externalizable接口代替实现Serializable,还有一种是实现writeObject和readObject方法。

将字段声明为transient,默认序列化机制将忽略该字段,不会进行保存和恢复。 比如上面的第一个实例中,假设我们在进行序列化和反序列化时不需要保存和恢复no字段的信息,那么我们可以在no字段前面加上一个transient修饰符。

其他

redis在缓存POJO的时候需要将POJO序列化为byte数组进行存储,spring-data-redis实现了类JdkSerializationRedisSerializer对POJO进行序列化。

spring-data-redis的序列化类有下面这几个:

1,使用JdkSerializationRedisSerializer序列化

用JdkSerializationRedisSerializer序列化的话,被序列化的对象必须实现Serializable接口。

在存储内容时,除了属性的内容外还存了其它内容在里面,总长度长,且不容易阅读。

对于最下面的代码,存到redis里的内容如下:

redis 127.0.0.1:6379> get users/user1
"\xac\xed\x00\x05sr\x00!com.oreilly.springdata.redis.User\xb1\x1c \n\xcd\xed%\xd8\x02\x00\x02I\x00\x03ageL\x00\buserNamet\x00\x12Ljava/lang/String;xp\x00\x00\x00\x14t\x00\x05user1"

2,使用JacksonJsonRedisSerializer序列化

如果需要保存对象为json的话推荐使用JacksonJsonRedisSerializer,它不仅可以将对象序列化,

还可以将对象转换为json字符串并保存到redis中,但需要和jackson配合一起使用。

用JacksonJsonRedisSerializer序列化的话,被序列化的对象不用实现Serializable接口。

Jackson是利用反射和getter和setter方法进行读取的,如果不想因为getter和setter方法来影响存储,就要使用注解来定义被序列化的对象。

Jackson序列化的结果清晰,容易阅读,而且存储字节少,速度快,推荐。

对于最下面的代码,存到redis里的内容如下:

redis 127.0.0.1:6379> get json/user1
"{\"userName\":\"user1\",\"age\":20}"
redis 127.0.0.1:6379> strlen json/user1
(integer) 29

3,使用StringRedisSerializer序列化

一般如果key-value都是string的话,使用StringRedisSerializer就可以了

代码:

@Test
public void testJacksonSerialiable()
{    
    RedisTemplate<String, Object> redis = new RedisTemplate<String, Object>();    
    redis.setConnectionFactory(connectionFactory);    
    redis.setKeySerializer(ApplicationConfig.StringSerializer.INSTANCE);    
    redis.setValueSerializer(new JacksonJsonRedisSerializer<User>(User.class));    
    redis.afterPropertiesSet();    
    
    
    ValueOperations<String, Object> ops = redis.opsForValue();    
    User user1 = new User();    
    user1.setUserName("user1");    
    user1.setAge(20);    
    User user11 = null;    
    String key1 = "json/user1";    
    long begin = System.currentTimeMillis();    
    for (int i = 0; i < 100; i++) {        
        ops.set(key1,user1);        
        user11 = (User)ops.get(key1);    
    }    
    long time = System.currentTimeMillis() - begin;    System.out.println("json time:"+time);    
    assertThat(user11.getUserName(),is("user1"));
}