今天看到一个对象的序列化的知识点。所以整理和收藏了一些东西,说不定以后会用的上。

对象的序列化,好像是一个软件中很专业的问题。不过这个看着好像是一个基础问题。是面向对象的一个基础知识点。

Objective-C序列化和反序列化

​http://www.codeios.com/thread-31683-1-1.html​


Objective-C可以程序用到的各种对象序列话到文件,在任何需要的情况下,从文件中重新读取数据重新构造对象,下面说一下对象的序列化和反序列化。



利用NSKeyedArchiver把对象序列化到文件中:


  1. //=================NSKeyedArchiver========================   
  2. NSString *saveStr1 = @"NSKeyedArchiver1";  
  3. NSString *saveStr2 = @"NSKeyedArchiver2";  
  4. NSArray *array = [NSArray arrayWithObjects:saveStr1, saveStr2, nil];   
  5. //—-Save  
  6. //这一句是将路径和文件名合成文件完整路径  
  7. NSString *Path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];   
  8. NSString *filename = [Path stringByAppendingPathComponent:@"saveDatatest"];  
  9. [NSKeyedArchiver archiveRootObject:array toFile:filename];

复制代码



利用NSKeyedUnarchiver从文件中反序列化成对象:

  1. array = [NSKeyedUnarchiver unarchiveObjectWithFile: filename];   
  2.  NSLog(@">>%@",array);

我在这里说一句,其实不只是NSArray有这个方法,Objective-C中的字典类也有这个功能。他们都可以保存成xml文件的。

下边是另一个有关的东西

对象自动序列化的代码例子:

转自:​​http://www.cocoachina.com/downloads/video/2010/1018/2198.html​

想要对对象进行持久化,就必须实现encode decode方法。如果对象多的话,实在是个体力活。要么用runtime,要么用脚本去生成相应的方法。下面是 CocoaChina 会员 “durian” 分享的对象自动序列化的代码例子,​​ AutoEncodeDecode.zip​​ (20 K)

  代码解析


// 先看两个重要的结构体, class和ivar
struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
 
typedef struct objc_ivar *Ivar;
 
struct objc_class {
    Class isa;
 
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
 
} OBJC2_UNAVAILABLE;
 
typedef struct objc_class *Class;
 
// 一个class所包含的变量信息,存在ivars当中。
// Ivar包含有变量的信息,名字,类型,及距离一个对象实例本身地址的偏移。
// 比如DODog就含有四个变量, name, year, size, point。
// DOServiceDog含有serviceCount一个变量。
// 取得每个ivar所代表的变量的值,然后将其encode,就可以完成序列化。
 
 
- (void)encodeWithCoder:(NSCoder *)encoder {
    Class cls = [self class];
    while (cls != [NSObject class]) {
        unsigned int numberOfIvars = 0;
        // 取得当前class的Ivar数组
        Ivar* ivars = class_copyIvarList(cls, &numberOfIvars);
        for(const Ivar* p = ivars; p < ivars+numberOfIvars; p++)
        {
            Ivar const ivar = *p;
            // 得到ivar的类型
            const char *type = ivar_getTypeEncoding(ivar);
            // 取得它的名字,比如"year", "name".
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            // 取得某个key所对应的值
            id value = [self valueForKey:key];
            if (value) {
                switch (type[0]) {
                    // 如果是结构体的话,将其转化为NSData,然后encode.
                    // 其实cocoa会自动将CGRect等四种结构转化为NSValue,能够直接用value encode.
                    // 但其他结构体就不可以,所以还是需要我们手动转一下.
                    case _C_STRUCT_B: {
                        NSUInteger ivarSize = 0;
                        NSUInteger ivarAlignment = 0;
                        // 取得变量的大小
                        NSGetSizeAndAlignment(type, &ivarSize, &ivarAlignment);               
                        // ((const char *)self + ivar_getOffset(ivar))指向结构体变量
                        NSData *data = [NSData dataWithBytes:(const char *)self + ivar_getOffset(ivar)
                                                      length:ivarSize];
                        [encoder encodeObject:data forKey:key];
                    }
                        break;
                    // 如果是其他数据结构,也与处理结构体类似,未实现。
                    // case _C_CHR:    {
                    //}
                    // break;   
                    default:
                        [encoder encodeObject:value
                                       forKey:key];
                        break;
                }                       
            }
        }       
        cls = class_getSuperclass(cls);
    }
}
 
- (id)initWithCoder:(NSCoder *)decoder {
    // NSObject没有super,所以用了只能用self,应该没什么问题。
    // 因为[self init]内部必然会调用[super init], 如果一个class有super的话。
    // 这是我的理解,不知道对不对。
    self = [self init];
 
    if (self) {
        Class cls = [self class];
        while (cls != [NSObject
                       class]) {
            unsigned int numberOfIvars = 0;
            Ivar* ivars = class_copyIvarList(cls, &numberOfIvars);
 
            for(const Ivar* p = ivars; p < ivars+numberOfIvars; p++)
            {
                Ivar const ivar = *p;
                const char *type = ivar_getTypeEncoding(ivar);
                NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
                id value = [decoder decodeObjectForKey:key];
                if (value) {
                    switch (type[0]) {
                        case _C_STRUCT_B: {
                            NSUInteger ivarSize = 0;
                            NSUInteger ivarAlignment = 0;
                            NSGetSizeAndAlignment(type, &ivarSize, &ivarAlignment);               
                            NSData *data = [decoder decodeObjectForKey:key];
                            char *sourceIvarLocation = (char*)self+ ivar_getOffset(ivar);                                
                            [data getBytes:sourceIvarLocation length:ivarSize];
                            // 在10.1号我碰到一个很奇怪的问题, 在这个方法内部,我能正确的设置结构体的值。
                            // 但是当self被返回后再打印结构体却是(0, 0);
                            // 我也不知道如何解决,就加了个memcpy函数,实际上什么都没干,自己copy自己。
                            // 但是值却被正确的带出了。
                            // 现在我去掉这个函数,也能正常工作。所以很奇怪,大家看看吧,不知道还会不会出现问题。
//                            memcpy((char *)self + ivar_getOffset(ivar), sourceIvarLocation, ivarSize);
                        }
                            break;
                        default:
                            [self setValue:[decoder decodeObjectForKey:key]
                                    forKey:key];
                            break;
                    }
                }   
            }
            cls = class_getSuperclass(cls);
        }
    }
 
    return self;
}




encode和decode



转自:​​http://media.ccidnet.com/media/ciw/681/01370001.htm​​​  电子邮件的普及拉近了我们与世界的距离,但是针对传输电子邮件和发布新闻而设计的协议却仅能接收、发送可显示的ascii字符。我们怎样传输图像、声音、应用软件等大量的二进制文件呢?本文的介绍能给您满意的答案。



  encode和decode即编码和解码。

  encode是一种将二进制文件转换成可显示的ascii码文本文件格式的方法,而decode则是将转换成的ascii文件再还原为原来的二进制文件。



  为什么要encode和decode

  因为用于传送电子邮件和发布新闻的协议被设计成只能传送可显示的ascii字符而不能发送二进制文件。但在现实工作中常常需要在internet上通过internetmail和usenetnewsgroups将二进制文件发送出去。

  二进制数据包括软件、图像、声音、影像等,它们使用了一个字节中所有256个可能的值。这256个值的某些值代表控制字符,它们或者对传输过程产生错误的控制作用,或者使数据内容不能正确的传送出去。

  为了顺利发送二进制数据,发送前必须首先对该数据编码,使其成为一个可安全传输的字符集组成的文件。收件人收到编码文件后,通过解码再还原成原来的二进制文件。



  encode和decode的方法

  若干年来,出现了许多不同的方案,用来编码二进制文件,以便它们能够通过netmail和usenet安全成功的发送出去。这些方案有mime、uu-encoding、xx-encoding和binhexencoding等等。二进制文件经过encoding后,其内容看起来就象一些杂乱无章的字符放在一块,仅在文件的最开始部分有几行可读的句子。

  无论采用那一种encode和decode方法,其基本原理都是一样:发送二进制文件的用户先对该文件进行编码,然后将编码转换后的文本文件传送出去。收件人运行decode程序解码收到的文本文件即可恢复获得原来的二进制文件。

  当要传送的文件很大时还会出现更复杂的情况。某些e-mail和新闻发布系统对其传送的文件大小有限制,即只能传送小于某一长度的文件。为了解决这一问题,先要将文件划分为多个部分,分别进行编码,编码转换后产生多个文本文件。收件人必须收到所有划分的文本文件后,再进行解码、合并,然后才能获得原来的二进制文件。

  编码发送和解码的方法并非只有一种,特别是我们将介绍的这四种方法,它们完全不同、相互不兼容但均可将二进制数据编码成为文本文件,这使问题变得有点复杂了,发送人和收件人都必须事先达成一致,即使同一种方法来进行编码解码工作。否则收件人无法还原得到原来的二进制数据。下面我来介绍这常用的四种方法:

  uu-encoding。这是历史上发明的第一个方法,它的原理很简单,但也产生过一些问题,在最初的文件编码转换中使用了空格字符,而许多邮件网关会将位于行末尾的空格字符剔除,其结果是使收件人获得的文件面目全非。后来引入了新的方法才解决了这一问题。

  xx-encoding。此方法很少使用,它的出现是由于uu-encoding在使用初期时出了问题。但用该方法在使用不同字符集进行encoding时同样要避免使用空格字符。

  base64。该方法是根据mime标准产生的,从而避免了使用上述两种方法会出现的问题。但是,在某些计算机中没有该方法所使用的部分字符。除了具有mime的其它优点外,这种方法还是一种最安全的方法。

  binhex。可在macintosh系统环境下传送文件的方法。在macintosh机器中文件由两个部分组成:“datafork”和“resourcefork”。对文件编码转换时,首先它会再加上一个文件头部分,然后将这三个部分组成单一的一个数据流,再稍加压缩后才进行编码。

  实际上,还有一些其它使用得不太广泛的编码/解码方法,如ship或btoa等,由于很难在实际工作中碰到,在此不作介绍。



  采用何种方法进行encode和decode

  首先是避免使用xx-encoding,它是一种陈旧的方法。而binhex必须使用于macintosh系统,它的解码程序使用并不很广泛,在其它系统中选择binhex通常是不明智的。的确,binhex声称它在编码时对文件进行了压缩,使得传输文件变得更小。但对已经压缩的文件,这就不算什么优点了。

  值得一提的是,对文件进行压缩在文件传输中是很重要的。即不要试图发送一个未经压缩的文件,这会浪费宝贵的带宽。gif和jpeg图像已经是一个自压缩的格式。其它类型的数据在传输前应该总是先压缩成zip文件或其它类似的压缩格式。

  因此只有uu-encoding和base64这两种方法了。其主要考虑点要放在所使用的电子邮件软件上。如果该软件是mime兼容的,提供“attach”按base64方法编码的文件的功能,那么就使用这种方法。对mime信息而言,base64是首选方法。

  此外,可使用uu-encoding方法,它现在仍然是使用得最广泛的编码方法。由于越来越多的软件已成为mime兼容软件,uu-encoding方法将会被base64方法取而代这。但是只要有收件人使用老的软件系统,uu-encoding就有存在的可能性,uu-encoding方法是最安全的。

  请注意,mime兼容仅指电子邮件软件能否处理而言。例如,如果你使用uuenview编码/解码软件将数据编码成base64格式,然后将编码数据“attach”在发送的信息中,则最终发送的信息已经不再是mime兼容的了。

  该事实很重要,必须理解清楚。因此,如果你的电子邮件并不带有“attachment”功能,你又必须使用外部编码器时,最好使用uu-encoding方法。



  文件的划分与否与划分大小标准

  对所有方法来说还有另一个问题,即文件划分问题。对发件人和收件人来说,在一个单一邮件中发送任何东西是最容易的,你不会被如何对文件进行划分及再将它们合并起来所困扰。但是你必须确信发出的邮件在传输途中并未丢失内容。现在,对电子邮件来说,这已经不是什么很大的问题了。通常你能一次发送数兆字节的邮件。因此建议你先尝试送出一个完整的大文件,然后再询问你的收件人是否文件完整。如果未能收到完整文件,只有靠实验来找出可安全送出的最大文件长度。但是对新闻发布则完全不同,有许多网关仅允许发送小于某长度的新闻。通常一次仅能发送文件长度约为一千行。就是说大文件需按每千行一个部分来划分,再编码后才能成功传输出去。



  必要的文件信息

  当你发送文件时,应该让收件人在作解码前就知道他们得到的是什么。同样,大多数人下载编码文件后,在文件解码之前想知道的也是他们获得的究竟是什么。当他们发现他们下载的东西完全是没有用处时,他们会变得非常脑火。为了避免这种事情发生,发件人应先送一个小信息来说明该文件的内容、目的和功能等。不要只是说,你必须拥有这个文件,而要包括一些必须的文件说明信息。

  对小于一千行的编码文件,你可将小信息文件附在编码文件开始。但对于较大的文件,你必须单独将信息文件发送出去。



  主题行的构成

  最后,我们必须面临的问题是如何为邮件建立一个适当主题。主题建立得好,收件人就能很容易地知道收到的是什么文件,对于划整为零的文件,收件人也能从主题得知,这是文件的第几个部分等等。

  下面是主题的一个实例:

  uudeview0.5aforwindows-uudvw05a.zip(001/004)

  首先在主题行的是对文件的一个简短描述,应少于40个字符;然后是一个连线“-”,后跟着编码前的文件名;最后在括号中是文件的第几部分和文件总共有几个部分组成。在这个例子中,收件人知道,他还应该在收到第2部分至第4部分后再对文件进行解码。象上例这个主题行中包括了所有必要的信息。

  如果为四个部分的文件附加一个信息说明文件,通常应作为该文件的第零部分。该部分仅有文字描述,不包含有任何编码数据。对收件人而言,当看到所收到的邮件中有分成几个部分的文件,且有一个邮件的部分代号为“0”,则应先行阅读这第零部分,在这一部分中收件人能知道所收到的文件是什么内容,是否有必要对文件解码等等。

  注意,如果编码文件只有一个部分,也同样应该将部分代号包括在主题上,且应为(001/001)。这样收件人就知道该邮件没有其它部分了。



  小结

  ·如果你的邮件或新闻是mime兼容的,可直接“attach”二进制文件时,使用base64方法。如果是使用外部编码器将编码数据包括在你发送的信息中时,采用uuencoding方法。

  ·通常在邮件发送时并不必要将编码文件分成几个部分,但是如果是通过新闻组来发布新闻时,每个划分的文件部分应不超过1000行。除非文件的确太大,否则发送的部分不应超过100个。

  ·发送文件时先压缩。

  ·建立一个好的主题行,将所有必要的信息包括在内,以方便收件人和解码。某些软件如“uudeview”在合并文件时对主题行有特殊要求。

  ·发送描述文件时先将部分代号设置为零。

 

JAVA中的序列化问题

转自:​​http://www.blogjava.net/amigoxie/archive/2007/09/16/145465.html​

当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

把Java对象转换为字节序列的过程称为对象的序列化。

把字节序列恢复为Java对象的过程称为对象的反序列化。

对象的序列化主要有两种用途:

1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;

2) 在网络上传送对象的字节序列。

一.             JDK类库中的序列化API

java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。

java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。、

只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以采用默认的序列化方式 。

对象序列化包括如下步骤:

1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;

2) 通过对象输出流的writeObject()方法写对象。

对象反序列化的步骤如下:

1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;

2) 通过对象输入流的readObject()方法读取对象。

下面让我们来看一个对应的例子,类的内容如下:


import java.io.*;

import java.util.Date;

/**

 * 对象的序列化和反序列化测试类.    

 * @author <a href="​​mailto:xiexingxing1121@126.com">AmigoXie</a​​>

 * @version 1.0

 * Creation date: 2007-9-15 - 下午21:45:48

 */

public class ObjectSaver {

       /**

        * @param args

        * @author <a href="​​mailto:xiexingxing1121@126.com">AmigoXie</a​​>

        * Creation date: 2007-9-15 - 下午21:45:37

        */

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

              ObjectOutputStream out = new ObjectOutputStream

                     (new FileOutputStream("D:""objectFile.obj"));

 

              //序列化对象

 

              Customer customer = new Customer("阿蜜果", 24);

              out.writeObject("你好!");

              out.writeObject(new Date());

              out.writeObject(customer);

              out.writeInt(123); //写入基本类型数据

 

              out.close();

              //反序列化对象

 

              ObjectInputStream in = new ObjectInputStream

                     (new FileInputStream("D:""objectFile.obj"));

              System.out.println("obj1=" + (String) in.readObject());

              System.out.println("obj2=" + (Date) in.readObject());

              Customer obj3 = (Customer) in.readObject();

              System.out.println("obj3=" + obj3);

              int obj4 = in.readInt();

              System.out.println("obj4=" + obj4);

              in.close();

       }

}

class Customer implements Serializable {

       private String name;

       private int age;

       public Customer(String name, int age) {

              this.name = name;

              this.age = age;

       }

       public String toString() {

              return "name=" + name + ", age=" + age;

       }

}


       输出结果如下:



obj1=你好!

obj2=Sat Sep 15 22:02:21 CST 2007

obj3=name=阿蜜果, age=24

obj4=123

    因此例比较简单,在此不再详述。

二.实现Serializable接口

ObjectOutputStream只能对Serializable接口的类的对象进行序列化。默认情况下,ObjectOutputStream按照默认方式序列化,这种序列化方式仅仅对对象的非transient的实例变量进行序列化,而不会序列化对象的transient的实例变量,也不会序列化静态变量。

当ObjectOutputStream按照默认方式反序列化时,具有如下特点:

1)              如果在内存中对象所属的类还没有被加载,那么会先加载并初始化这个类。如果在classpath中不存在相应的类文件,那么会抛出ClassNotFoundException;

2)              在反序列化时不会调用类的任何构造方法。

如果用户希望控制类的序列化方式,可以在可序列化类中提供以下形式的writeObject()和readObject()方法。

private void writeObject(java.io.ObjectOutputStream out) throws IOException

private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;

当ObjectOutputStream对一个Customer对象进行序列化时,如果该对象具有writeObject()方法,那么就会执行这一方法,否则就按默认方式序列化。在该对象的writeObjectt()方法中,可以先调用ObjectOutputStream的defaultWriteObject()方法,使得对象输出流先执行默认的序列化操作。同理可得出反序列化的情况,不过这次是defaultReadObject()方法。

有些对象中包含一些敏感信息,这些信息不宜对外公开。如果按照默认方式对它们序列化,那么它们的序列化数据在网络上传输时,可能会被不法份子窃取。对于这类信息,可以对它们进行加密后再序列化,在反序列化时则需要解密,再恢复为原来的信息。

默认的序列化方式会序列化整个对象图,这需要递归遍历对象图。如果对象图很复杂,递归遍历操作需要消耗很多的空间和时间,它的内部数据结构为双向列表。

在应用时,如果对某些成员变量都改为transient类型,将节省空间和时间,提高序列化的性能。

三.             实现Externalizable接口

Externalizable接口继承自Serializable接口,如果一个类实现了Externalizable接口,那么将完全由这个类控制自身的序列化行为。Externalizable接口声明了两个方法:

public void writeExternal(ObjectOutput out) throws IOException

public void readExternal(ObjectInput in) throws IOException , ClassNotFoundException

前者负责序列化操作,后者负责反序列化操作。

在对实现了Externalizable接口的类的对象进行反序列化时,会先调用类的不带参数的构造方法,这是有别于默认反序列方式的。如果把类的不带参数的构造方法删除,或者把该构造方法的访问权限设置为private、默认或protected级别,会抛出java.io.InvalidException: no valid constructor异常。

四.             可序列化类的不同版本的序列化兼容性

凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:

private static final long serialVersionUID;

以上serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。

类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的serialVersionUID,也有可能相同。为了提高哦啊serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。显式地定义serialVersionUID有两种用途:

1)              在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;

2)              在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。

 


/*
* @(#)BeanXML.java 1.00 2005-10-7
*
* Copyright 2005 BeanSoft Studio. All rights reserved.
* PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package beansoft.xml;

import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;

/**
* BeanXML
*
* Chinese documents:
*
* @author BeanSoft
* @version 1.00 2005-10-7
*/
public class BeanXML {
/**
* 使用 java.beans.XMLEncoder 将 对象编码为 XML.
* @param bean 对象
* @return String - 编码后的 XML
*/
public static String encodeBean(Object bean) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
XMLEncoder encoder = new XMLEncoder(out);
encoder.writeObject(bean);
encoder.close();

return out.toString();
}

/**
* 使用 java.beans.XMLDecoder 将 XML 解码为 对象.
* @param xml - 编码后的 XML
* @return Object 反编码后的对象
*/
public static Object decodeBean(String xml) {
try {
// FIXME 必须使用 GBK 解码才对, 否则出来的文字是乱码?
XMLDecoder decoder = new XMLDecoder(new ByteArrayInputStream(xml.getBytes("GBK")));
return decoder.readObject();
} catch (UnsupportedEncodingException e) {
}

return null;
}
}