数据处理---Java数据处理之序列化

既然上一篇已经引出了序列化的问题,这里我们就来专门说说这个。

序列化的目的:

        实现简单的持久化,说到持久化,我们很多时候想到的是保存到数据库中,没错,对象也可以保存到数据库中或者是文件中,这样可以保存对象的状态等等信息。

        实现对象的远程传输,从而完成远程调用。在RMI中,服务端与客户端之间传递的Java对象必须是可序列化的对象,不可序列化的对象不能在对象流中进行传递,否则会抛出异常(上篇我们已经看到了这点)。在对象序列化过程中,支持把对象编码以及将通过它们可访问到的对象编码变成字节流;同时也支持流中对象图形的互补重构造。序列化用于轻型持久性和借助于套接字或远程方法调用(RMI)进行的通信。

序列化的实现:

         一般来说序列化包括两部分:序列化和反序列化 。序列化是首先将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化就是打开字节流并重构对象。ObjectOutputStream中的序列化过程与字节流连接,包括对象类 型和版本信息。反序列化时,JVM用头信息生成对象实例,然后将对象字节流中的数据复制到对象数据成员中。

        由于Java提供了良好的默认支持,实现基本的对象序列化是件比较简单的事。待序列化的Java类只需要实现Serializable接口即可。Serializable仅是一个标记接口,并不包含任何需要实现的具体方法。实现该接口只是为了声明该Java类的对象是可以被序列化的。实际的序列化和反序列化工作是通过ObjectOuputStream和ObjectInputStream来完成的。ObjectOutputStream的writeObject方法可以把一个Java对象写入到流中,ObjectInputStream的readObject方法可以从流中读取一个Java对象。在写入和读取的时候,虽然用的参数或返回值是单个对象,但实际上操纵的是一个对象图,包括该对象所引用的其它对象,以及这些对象所引用的另外的对象。Java会自动帮你遍历对象图并逐个序列化。除了对象之外,Java中的基本类型和数组也是可以通过 ObjectOutputStream和ObjectInputStream来序列化的。

          贴上JDK(1.7) ObjectOutputStream.java 中 writeObject0方法代码片段,readObject0()方法类似。

          // remaining cases
            if (obj instanceof String) {
                writeString((String) obj, unshared);
            } else if (cl.isArray()) {
                writeArray(obj, desc, unshared);
            } else if (obj instanceof Enum) {
                writeEnum((Enum) obj, desc, unshared);
            } else if (obj instanceof Serializable) {
                writeOrdinaryObject(obj, desc, unshared);
            } else {
                if (extendedDebugInfo) {
                    throw new NotSerializableException(
                        cl.getName() + "\n" + debugInfoStack.toString());
                } else {
                    throw new NotSerializableException(cl.getName());
                }
            }
        } finally {
            depth--;
            bout.setBlockDataMode(oldMode);
        }

      实际使用的时候,可以这样做,例如序列化到文件:

FileOutputStream fos = new FileOutputStream("t.tmp");

ObjectOutputStream oos = new ObjectOutputStream(fos);

oos.writeObject(new Date());

oos.close();

在默认的序列化实现中,Java对象中的非静态和非瞬时域都会被包括进来,而与域的可见性声明没有关系。对于不需要序列化的域可以声明为瞬时的,即使用transient关键词。另外一种做法是添加一个serialPersistentFields 域来声明序列化时要包含的域。虽然Serializable只是一个标记接口,但它其实是包含有不少隐含的要求。下面的代码给出了 serialPersistentFields的声明示例,即只有firstName这个域是要被序列化的。

private static final ObjectStreamField[] serialPersistentFields = {
    new ObjectStreamField("firstName", String.class)
}; 

    如果有事有特别的要求,可以自定义方法来实现序列化。可以在需要序列化的类中实现writeObject和对应的 readObject方法。这两个方法属于前面提到的序列化机制的隐含契约的一部分。在通过ObjectOutputStream的 writeObject方法写入对象的时候,如果这个对象的类中定义了writeObject方法,就会调用该方法,并把当前 ObjectOutputStream对象作为参数传递进去。writeObject方法中一般会包含自定义的序列化逻辑,比如在写入之前修改域的值,或是写入额外的数据等。对于writeObject中添加的逻辑,在对应的readObject中都需要反转过来,与之对应。

     另外也可以通过java.io.Externalizable(which extends java.io.Serializable)来实现。实现过程也不复杂,可以试试。

     当然一个完整的序列化还包括很多问题,比如:

  •      版本(不同版本是否兼容,serialVersionUID)
  •     对象重建(有些字段没有序列化,反序列化后部分域没有初始化)
  •      数据加密(数据既然可以传输,就要保证安全性)
  •      数据压缩(序列化可能数据比较大,为保证效率,可以对数据进行压缩)

        对于这些问题,有兴趣的可以自己试试找到答案。其实说了这么多,在这里还是想引出来再搭建Hive文件格式的博文的时候说到的Avro之类的对象序列化的方法。


Protocol Buffer    

摘抄一段网络上的介绍:

Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件。他们用于 RPC 系统和持续数据存储系统。

Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 等多种语言的 API。

 相比较一些其他的XML技术而言,该技术的一个明显特点就是更加节省空间(以二进制流存储)、速度更快以及更加灵活。

     通常,编写一个protocol buffers应用需要经历如下三步:

     1、定义消息格式文件,最好以proto作为后缀名

     2、使用Google提供的protocol buffers编译器来生成代码文件,一般为.h和.cc文件,主要是对消息格式以特定的语言方式描述

     3、使用protocol buffers库提供的API来编写应用程序 

Avro

         Avro是Apache的一个项目,它是一个数据序列化系统。支持:

         丰富数据结构

         紧缩的快速的二进制数据格式

         存储持久数据的容器文件

         远程过程调用

         和动态语言的简单集成。读写数据文件或者使用RPC的时候不需要代码生成(Code Generation).代码生成作为一个优化选项,仅仅在有意义的实现类型语言统计时候使用。

        与ProtocolBuffers(出自Google) Thrift(出自Facebook)相比,Avro有几个主要的不同:

        动态类型,Avro不需要代码生成,这点有利于构建通用的数据处理系统和语言

        Untagged data,数据在读的时候就可以了解其模式,相应地在数据中需要更少的类型信息,而系列化的数据更少。

        不需要手动分配字段ID。通过在数据中的字段名来解决数据模式

Maven中使用
<dependency>
  <groupId>org.apache.avro</groupId>
  <artifactId>avro</artifactId>
  <version>1.7.7</version>
</dependency>
     

需要代码生成的时候,可以Avro Maven plugin
<plugin>
  <groupId>org.apache.avro</groupId>
  <artifactId>avro-maven-plugin</artifactId>
  <version>1.7.7</version>
  <executions>
    <execution>
      <phase>generate-sources</phase>
      <goals>
        <goal>schema</goal>
      </goals>
      <configuration>
        <sourceDirectory>${project.basedir}/src/main/avro/</sourceDirectory>
        <outputDirectory>${project.basedir}/src/main/java/</outputDirectory>
      </configuration>
    </execution>
  </executions>
</plugin>
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <configuration>
    <source>1.6</source>
    <target>1.6</target>
  </configuration>
</plugin>
      

使用Avro步骤
1.  定义schema

Avro schemas 是使用JSON来定义的. Schemas是有原子类型(null, boolean, int, long, float, double, bytes, and string) 和复杂类型(record, enum, array, map, union, and fixed)组成的.下面是一个简单的例子user.avsc:
{"namespace": "example.avro",
 "type": "record",
 "name": "User",
 "fields": [
     {"name": "name", "type": "string"},
     {"name": "favorite_number",  "type": ["int", "null"]},
     {"name": "favorite_color", "type": ["string", "null"]}
 ]
}
一个schema文件只能定义单个模式。一个定义最少必须包括类型("type": "record")名称("name": "User")和字段(favorite_number, and favorite_color)。上面的例子包括了namespace ("namespace": "example.avro"),它的全名类似example.avro.User

生成代码(如果使用Avro Maven plugin会自动产生代码,无需手动执行):
java -jar /path/to/avro-tools-1.7.7.jar compile schema <schema file> <destination>

下面试试怎么使用吧:
User user1 = new User();
user1.setName("Alyssa");
user1.setFavoriteNumber(256);
// Leave favorite color null

// Alternate constructor
User user2 = new User("Ben", 7, "red");

// Construct via builder
User user3 = User.newBuilder()
             .setName("Charlie")
             .setFavoriteColor("blue")
             .setFavoriteNumber(null)
             .build();
        

// Serialize user1, user2 and user3 to disk 序列化到磁盘
DatumWriter<User> userDatumWriter = new SpecificDatumWriter<User>(User.class);
DataFileWriter<User> dataFileWriter = new DataFileWriter<User>(userDatumWriter);
dataFileWriter.create(user1.getSchema(), new File("users.avro"));
dataFileWriter.append(user1);
dataFileWriter.append(user2);
dataFileWriter.append(user3);
dataFileWriter.close();
      



// Deserialize Users from disk  从磁盘读出数据
DatumReader<User> userDatumReader = new SpecificDatumReader<User>(User.class);
DataFileReader<User> dataFileReader = new DataFileReader<User>(file, userDatumReader);
User user = null;
while (dataFileReader.hasNext()) {
// Reuse user object by passing it to next(). This saves us from
// allocating and garbage collecting many objects for files with
// many items.
user = dataFileReader.next(user);
System.out.println(user);
}

输出类似:
{"name": "Alyssa", "favorite_number": 256, "favorite_color": null}
{"name": "Ben", "favorite_number": 7, "favorite_color": "red"}
{"name": "Charlie", "favorite_number": null, "favorite_color": "blue"}
 

    其他的序列化方式还有:Thrift (facebook贡献到apache)  , Hadoop自己实现的Writable

    说一千道一万,序列化有很多实现方式,都各有特色,但是很多思想或者说思路是相同的。

    今天,你序列化了没有?大笑









     




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值