swift下类和结构体的比较(相同点与不同点,什么情况下使用)

 swift  swift下类和结构体的比较(相同点与不同点,什么情况下使用)已关闭评论
3月 122020
 

网上看到的一篇好文章,清楚明了的解释了swift下类与结构体的相同点和不同点,并说明了该在什么情况下使用,推荐! 来自: https://juejin.im/post/5b57e75ef265da0f87593513

结构体是构建代码所用的一种通用且灵活的构造体。我们可以使用完全相同的语法规则来为类和结构体定义属性(变量,常量)和添加方法。从而扩展类和结构体的功能。

与其他编程语言所不同的是,Swift并不要求你为自定义类和结构去创建独立的接口和实现文件。你所要做的是在一个单一文件中定义一个类或者结构体,系统将会自动生成面向其他代码的外部接口。

注: 通常一个的势力被称为对象。在Swift中,类和结构体的关系要比在其他语言中更加密切,本章中所讨论的部分功能都可以在类和结构体上。因此主要使用实例。内容包含类和结构体对比结构体和枚举是值类型类是引用类型类和结构体的选择字符串、数组和字典类型的赋值和复制行为


类和结构体对比

Swift中的类和结构体有很多共同点:

  • 定义属性用于储存值
  • 定义方法用于提供功能
  • 定义下标操作通过下标语法可以访问它们的值
  • 定义构造器用于生成初始化值
  • 通过扩展以增加默认实现的功能
  • 遵循协议以提供某种标准功能

与结构体相比,类还有如下的附加功能:

  • 继承允许一个类继承另一个类的特征
  • 类型转换允许在运行时检查和解释一个类实例的类型
  • 析构器允许一个类实例释放任何其所被分配的资源
  • 引用计数允许对一个类的多次引用

注: 结构体总是通过被复制的方式在代码中传递,不使用引用计数。

定义语法

类和结构体有着类似的定义方式。通过关键字classstruct来分别表示类和结构体,并在一对大括号中定义它们的具体内容:

class SomeClass {
	// 在这里定义类
}
struct SomeStructure {
	// 在这里定义结构体
} 

注: 在每次定义一个新类或者结构体的时候,实际上是定义了一个新的Swift类型。因此请使用UpperCamelCase这种命名方式命名(如SomeClassSomeStructure等),已便符合标准Swift类型的大写命名风格(如StringIntBool)。相反的,请使用lowerCamelCase这种方式为属性和方法命名(如:framerateincrementCount),以便和类型名区分。

以下是定义结构体和定义类的示例:

struct Resolution {
	var width = 0
	var height = 0
}
class VideoMode {
	var resolution = Resolution()
	var interlaced = false
	var frameRate = 0.0
	var name: String?
} 

在上面的示例中我们定义了一个名为Resolution的结构体,用来描述一个显示器的像素分辨率。这个结构体包含了两个名为widthheight的存储属性。存储属性是被捆绑和存储在类或结构体中的常量或变量。这两个属性被初始化为整数0的时候,它们会被推断为Int类型。

在上面的示例中我们还定义了一个名为VideoMode的类,用来描述一个视频显示器的特定模式。这个类包含了四个变量存储属性。第一个是分辨率,它被初始化为一个新的Resolution结构体的实例,属性类型被推断为Resolution。新的VideoMode实例同时还会初始化其他三个属性,它们分别是,初始化为falseinterlaced,初始值为0.0frameRate,以及可选值为Stringnamename属性会被自定赋值nil,为可选类型。

类和结构体实例

Resolution结构体和VideoMode类的定义仅描述了什么是ResolutionVideoMode。它们并没有描述一个特定的分辨率(resolution)和视频模式(video mode)。为了描述一个特定的分辨率或者视频模式,我们需要生成一个它们的实例。

let someResolution = Resolution()
let someVideoMode = VideoMode() 

结构体和类都使用构造器语法来生成新的实例。构造器语法的最简单形式是在结构体或者类的类型名称后跟随一对空括号,如Resolution()VideoMode()。通过这种方式所创建的类或者结构体实例,其属性均会被初始化为默认值。

属性访问

通过使用点语法,你可以访问实例的属性。其语法规则则是,实例名后面紧跟属性名:

print("The width of someResolution is \(someResolution.width)")
// 打印 "The width of someResolution is 0"

print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// 打印 "The width of someVideoMode is 0"

someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// 打印 "The width of someVideoMode is now 1280" 

注: 与OC语言不通的是,Swift允许直接设置结构体属性的子属性。上面的最后一个例子,就是直接设置了someVideoModeresolution属性的width这个子属性,以上操作并不需要重新为整个resolution属性设置新值。

结构体类型的成员逐一构造器

所有结构体都有一个自动生成的成员逐一构造器,用于初始化新结构体实例中成员的属性。新实例中各个属性的初始值可以通过属性的名称传递到成员逐一构造器之中,如下:

let vga = Resolution(width: 640, height: 480) 

与结构体不同,类实例没有默认的成员逐一构造器

结构体和枚举是值类型

值类型被赋予给一个变量、常量或者传递给一个函数的时候,其值会被拷贝

在Swift中,所有的基本类型:整数,浮点数,布尔值,字符串,数组,字典都是值类型,并且在底层都是以结构体的形式所实现。所有的结构体和枚举类型都是值类型。这意味着他们的实例,以及实例中所包含的任何值类型属性,在代码中传递都会被复制。

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd 

在以上示例中,声明了一个名为hd的常量,其值为一个初始化为全高清视频分辨率(1920 像素宽,1080 像素高)的Resolution的实例。

下面,为了符合数码影院的放映需求(2048 像素宽,1080 像素高),cinemawidth 属性需要作如下修改:

cinema.width = 2048 

这里,将会显示 cinemawidth 属性确已改为了 2048

print("cinema is now  \(cinema.width) pixels wide")
// 打印 "cinema is now 2048 pixels wide" 

然而,初始的 hd 实例中 width 属性还是 1920

print("hd is still \(hd.width) pixels wide")
// 打印 "hd is still 1920 pixels wide" 

证明将hd赋值给cinema的时候,实际是将hd中所有储存的值进行拷贝,然后将拷贝的数据存储到新的cinema实例中。由于两者相互独立,因此将 cinemawidth 修改为 2048 并不会影响 hd 中的 width 的值。

枚举也遵循相同的行为准则:

enum CompassPoint {
	case North, South, East, West
}
var currentDirection = CompassPoint.West
let rememberedDirection = currentDirection
currentDirection = .East
if rememberedDirection == .West {
	print("The remembered direction is still .West")
}
// 打印 "The remembered direction is still .West" 

类是引用类型

与值类型不同,引用类型在被赋予到一个变量、常量挥着被传递到一个函数时,其值不会被拷贝。因此,引用的是已存在的实例本身而不是其拷贝。使用VideoMode举例如下:

let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// 打印 "The frameRate property of theEighty is now 30.0" 

以上示例中,声明了一个名为tenEighty的常量,其引用了一个VideoMode了的新实例。在之前的示例中,这个视频模式被赋予了HD分辨率(1920*1080)的一个拷贝(即 hd 实例)。同时设置为interlaced,命名为“1080i”。最为帧率为25.0帧每秒。然后,tenEighty 被赋予名为 alsoTenEighty 的新常量,同时对 alsoTenEighty 的帧率进行修改。

因为类是引用类型,所以 tenEightalsoTenEight 实际上引用的是相同的 VideoMode 实例。换句话说,它们是同一个实例的两种叫法。

通过查看 tenEightyframeRate 属性,我们会发现它正确的显示了所引用的 VideoMode 实例的新帧率,其值为 30.0

注: tenEightyalsoTenEighty 被声明为常量而不是变量。然而你依然可以改变 tenEighty.frameRatealsoTenEighty.frameRate,因为 tenEightyalsoTenEighty 这两个常量的值并未改变。它们并不“存储”这个 VideoMode 实例,而仅仅是对 VideoMode 实例的引用。所以,改变的是被引用的 VideoModeframeRate 属性,而不是引用 VideoMode 的常量的值。

恒等于运算符

因为类是引用类型,有可能有多个常量和变量在幕后同时引用同一个类实例。(对于结构体和枚举来说,这并不成立。因为它们作为值类型,在二笔赋予到常量、变量或者传递到函数时,其值总是被拷贝。)

Swift里有两个恒等于符号:等价于(===不等价于!== 来判定两个常量是否引用同一个类实例。

if tenEighty === alsoTenEighty {
	print("tenEighty and alsoTenEighty refer to the same Resolution instance.")
}
//打印 "tenEighty and alsoTenEighty refer to the same Resolution instance." 

注: 等于(==等价于(===)意义不同:

等于表示两个实例的值相等相同 等价于表示两个类类型的常量或者变量引用同一个类实例。

指针

在OC中,指针是来引用内存中的地址。一个引用某个引用类型实例的Swift常量或者变量,与OC的指针类似,但并不直接指向某个内存地址,也不要求使用(*)来表明你在创建一个引用。Swift中的这些引用于其他的常量或者变量的定义相同。

类和结构体的选择

在我们的代码中,我们可以使用类和结构体来定义我们的自定义数据类型。

然而,结构体实例总是通过值传递,类实例总是通过引用传递。这意味两者使用不同的任务。当你在考虑一个工程项目的数据和功能的时候,你需要决定每个数据结构是定义成类还是结构体。

按照通用准则,当符合一条或多条一下条件时,可以考虑结构体:

  • 该数据结构的主要目的是用来封装少量相关简单数据值
  • 有理由预计该数据结构的实例在被赋值或传递时,封装的数据将会被拷贝而不是被引用。
  • 该数据结构中存储的值类型属性,也应该被拷贝,而不是被引用。
  • 该数据结构不需要去继承另一个既有类型的属性或者行为。

字符串、数组、字典类型的赋值与复制行为

Swift中,许多基本类型如StringArrayDictionary类型均以结构体的形式实现。这意味着被赋值给新的常量,或者被传入函数或方法中时,它们的值会被拷贝。

OC中NSStringNSArrayNSDictionary类型均以类的形式实现,而并非结构体。它们在被赋值或者被传入函数或者方法时,不会发生值拷贝,而是传递现有实例的引用。

 

hadoop、storm和spark的区别、比较

 开发  hadoop、storm和spark的区别、比较已关闭评论
9月 212016
 

整理的一些网上关于hadoop、storm和spark的资料。

一、hadoop、Storm该选哪一个?

为了区别hadoop和Storm,该部分将回答如下问题
1.hadoop、Storm各是什么运算
2.Storm为什么被称之为流式计算系统
3.hadoop适合什么场景,什么情况下使用hadoop
4.什么是吞吐量

首先整体认识:Hadoop是磁盘级计算,进行计算时,数据在磁盘上,需要读写磁盘;Storm是内存级计算,数据直接通过网络导入内存。读写内存比读写磁盘速度快n个数量级。根据Harvard CS61课件,磁盘访问延迟约为内存访问延迟的75000倍。所以Storm更快。

注释:
1. 延时 , 指数据从产生到运算产生结果的时间,“快”应该主要指这个。
2. 吞吐, 指系统单位时间处理的数据量。

storm的网络直传、内存计算,其时延必然比hadoop的通过hdfs传输低得多;当计算模型比较适合流式时,storm的流式处理,省去了批处理的收集数据的时间;因为storm是服务型的作业,也省去了作业调度的时延。所以从时延上来看,storm要快于hadoop。

从原理角度来讲:

  • Hadoop M/R基于HDFS,需要切分输入数据、产生中间数据文件、排序、数据压缩、多份复制等,效率较低。

  • Storm 基于ZeroMQ这个高性能的消息通讯库,不持久化数据。

为什么storm比hadoop快,下面举一个应用场景
说一个典型的场景,几千个日志生产方产生日志文件,需要进行一些ETL操作存入一个数据库。

假设利用hadoop,则需要先存入hdfs,按每一分钟切一个文件的粒度来算(这个粒度已经极端的细了,再小的话hdfs上会一堆小文件),hadoop开始计算时,1分钟已经过去了,然后再开始调度任务又花了一分钟,然后作业运行起来,假设机器特别多,几钞钟就算完了,然后写数据库假设也花了很少的时间,这样,从数据产生到最后可以使用已经过去了至少两分多钟。
而流式计算则是数据产生时,则有一个程序去一直监控日志的产生,产生一行就通过一个传输系统发给流式计算系统,然后流式计算系统直接处理,处理完之后直接写入数据库,每条数据从产生到写入数据库,在资源充足时可以在毫秒级别完成。

同时说一下另外一个场景:
如果一个大文件的wordcount,把它放到storm上进行流式的处理,等所有已有数据处理完才让storm输出结果,这时候,你再把它和hadoop比较快慢,这时,其实比较的不是时延,而是比较的吞吐了。

——————————————————————————————————————————–
最主要的方面:Hadoop使用磁盘作为中间交换的介质,而storm的数据是一直在内存中流转的。
两者面向的领域也不完全相同,一个是批量处理,基于任务调度的;另外一个是实时处理,基于流。
以水为例,Hadoop可以看作是纯净水,一桶桶地搬;而Storm是用水管,预先接好(Topology),然后打开水龙头,水就源源不断地流出来了。

——————————————————————————————————————————–
Storm的主工程师Nathan Marz表示: Storm可以方便地在一个计算机集群中编写与扩展复杂的实时计算,Storm之于实时处理,就好比Hadoop之于批处理。Storm保证每个消息都会得到处理,而且它很快——在一个小集群中,每秒可以处理数以百万计的消息。更棒的是你可以使用任意编程语言来做开发。
Storm的主要特点如下:
1.简单的编程模型。类似于MapReduce降低了并行批处理复杂性,Storm降低了进行实时处理的复杂性。
2.可以使用各种编程语言。你可以在Storm之上使用各种编程语言。默认支持Clojure、Java、Ruby和Python。要增加对其他语言的支持,只需实现一个简单的Storm通信协议即可。
3.容错性。Storm会管理工作进程和节点的故障。
4.水平扩展。计算是在多个线程、进程和服务器之间并行进行的。
5.可靠的消息处理。Storm保证每个消息至少能得到一次完整处理。任务失败时,它会负责从消息源重试消息。
6.快速。系统的设计保证了消息能得到快速的处理,使用MQ作为其底层消息队列。
7.本地模式。Storm有一个“本地模式”,可以在处理过程中完全模拟Storm集群。这让你可以快速进行开发和单元测试。

——————————————————————————————————————————–
在消耗资源相同的情况下,一般来说storm的延时低于mapreduce。但是吞吐也低于mapreduce。storm是典型的流计算系统,mapreduce是典型的批处理系统。下面对流计算和批处理系统流程

这个个数据处理流程来说大致可以分三个阶段:
1. 数据采集与准备
2. 数据计算(涉及计算中的中间存储), 题主中的“那些方面决定”应该主要是指这个阶段处理方式。
3. 数据结果展现(反馈)

1)数据采集阶段,目前典型的处理处理策略:数据的产生系统一般出自页面打点和解析DB的log,流计算将数据采集中消息队列(比如kafaka,metaQ,timetunle)等。批处理系统一般将数据采集进分布式文件系统(比如HDFS),当然也有使用消息队列的。我们暂且把消息队列和文件系统称为预处理存储。二者在延时和吞吐上没太大区别,接下来从这个预处理存储进入到数据计算阶段有很大的区别,流计算一般在实时的读取消息队列进入流计算系统(storm)的数据进行运算,批处理一系统一般会攒一大批后批量导入到计算系统(hadoop),这里就有了延时的区别。
2)数据计算阶段,流计算系统(storm)的延时低主要有一下几个方面(针对题主的问题)
A: storm 进程是常驻的,有数据就可以进行实时的处理
mapreduce 数据攒一批后由作业管理系统启动任务,Jobtracker计算任务分配,tasktacker启动相关的运算进程
B: stom每个计算单元之间数据之间通过网络(zeromq)直接传输。
mapreduce map任务运算的结果要写入到HDFS,在于reduce任务通过网络拖过去运算。相对来说多了磁盘读写,比较慢
C: 对于复杂运算
storm的运算模型直接支持DAG(有向无环图)
mapreduce 需要肯多个MR过程组成,有些map操作没有意义的

3)数据结果展现
流计算一般运算结果直接反馈到最终结果集中(展示页面,数据库,搜索引擎的索引)。而mapreduce一般需要整个运算结束后将结果批量导入到结果集中。

实际流计算和批处理系统没有本质的区别,像storm的trident也有批概念,而mapreduce可以将每次运算的数据集缩小(比如几分钟启动一次),facebook的puma就是基于hadoop做的流计算系统。

 

二、高性能并行计算引擎Storm和Spark比较

Spark基于这样的理念,当数据庞大时,把计算过程传递给数据要比把数据传递给计算过程要更富效率。每个节点存储(或缓存)它的数据集,然后任务被提交给节点。

所以这是把过程传递给数据。这和Hadoop map/reduce非常相似,除了积极使用内存来避免I/O操作,以使得迭代算法(前一步计算输出是下一步计算的输入)性能更高。

Shark只是一个基于Spark的查询引擎(支持ad-hoc临时性的分析查询)

而Storm的架构和Spark截然相反。Storm是一个分布式流计算引擎。每个节点实现一个基本的计算过程,而数据项在互相连接的网络节点中流进流出。和Spark相反,这个是把数据传递给过程。

两个框架都用于处理大量数据的并行计算。

Storm在动态处理大量生成的“小数据块”上要更好(比如在Twitter数据流上实时计算一些汇聚功能或分析)。

Spark工作于现有的数据全集(如Hadoop数据)已经被导入Spark集群,Spark基于in-memory管理可以进行快讯扫描,并最小化迭代算法的全局I/O操作。

不过Spark流模块(Streaming Module)倒是和Storm相类似(都是流计算引擎),尽管并非完全一样。

Spark流模块先汇聚批量数据然后进行数据块分发(视作不可变数据进行处理),而Storm是只要接收到数据就实时处理并分发。

不确定哪种方式在数据吞吐量上要具优势,不过Storm计算时间延迟要小。

总结下,SparkStorm设计相反,而Spark Steaming才和Storm类似,前者有数据平滑窗口(sliding window),而后者需要自己去维护这个窗口。

转自:http://flyingsnail.blog.51cto.com/5341669/1571281

**************************************************************************************************

其它文章说明:




分布式计算(Distributed Computing)

对于如何处理大数据,计算机科学界有两大方向:第一个方向是集中式计算,就是通过不断增加处理器的数量来增强单个计算机的计算能力,从而提高处理数据的速度。第二个方向是分布式计算,就是把一组计算机通过网络相互连接组成分散系统,然后将需要处理的大量数据分散成多个部分,交由分散系统内的计算机组同时计算,最后将这些计算结果合并得到最终的结果。尽管分散系统内的单个计算机的计算能力不强,但是由于每个计算机只计算一部分数据,而且是多台计算机同时计算,所以就分散系统而言,处理数据的速度会远高于单个计算机。
过去,分布式计算理论比较复杂,技术实现比较困难,因此在处理大数据方面,集中式计算一直是主流解决方案。IBM的大型机就是集中式计算的典型硬件,很多银行和政府机构都用它处理大数据。不过,对于当时的互联网公司来说,IBM的大型机的价格过于昂贵。因此,互联网公司的把研究方向放在了可以使用在廉价计算机上的分布式计算上。

服务器集群(Server Cluster)

服务器集群是一种提升服务器整体计算能力的解决方案。它是由互相连接在一起的服务器群所组成的一个并行式或分布式系统。服务器集群中的服务器运行同一个计算任务。因此,从外部看,这群服务器表现为一台虚拟的服务器,对外提供统一的服务。
尽管单台服务器的运算能力有限,但是将成百上千的服务器组成服务器集群后,整个系统就具备了强大的运算能力,可以支持大数据分析的运算负荷。Google,Amazon,阿里巴巴的计算中心里的服务器集群都达到了5000台服务器的规模。

大数据的技术基础:MapReduce、Google File System和BigTable
2003年到2004年间,Google发表了MapReduce、GFS(Google File System)和BigTable三篇技术论文,提出了一套全新的分布式计算理论。
MapReduce是分布式计算框架,GFS(Google File System)是分布式文件系统,BigTable是基于Google File System的数据存储系统,这三大组件组成了Google的分布式计算模型。
Google的分布式计算模型相比于传统的分布式计算模型有三大优势:首先,它简化了传统的分布式计算理论,降低了技术实现的难度,可以进行实际的应用。其次,它可以应用在廉价的计算设备上,只需增加计算设备的数量就可以提升整体的计算能力,应用成本十分低廉。最后,它被Google应用在Google的计算中心,取得了很好的效果,有了实际应用的证明。

后来,各家互联网公司开始利用Google的分布式计算模型搭建自己的分布式计算系统,Google的这三篇论文也就成为了大数据时代的技术核心

主流的三大分布式计算系统:Hadoop,Spark和Storm
由于Google没有开源Google分布式计算模型的技术实现,所以其他互联网公司只能根据Google三篇技术论文中的相关原理,搭建自己的分布式计算系统。
Yahoo的工程师Doug Cutting和Mike Cafarella在2005年合作开发了分布式计算系统Hadoop。后来,Hadoop被贡献给了Apache基金会,成为了Apache基金会的开源项目。Doug Cutting也成为Apache基金会的主席,主持Hadoop的开发工作。
Hadoop采用MapReduce分布式计算框架,并根据GFS开发了HDFS分布式文件系统,根据BigTable开发了HBase数据存储系统。尽管和Google内部使用的分布式计算系统原理相同,但是Hadoop在运算速度上依然达不到Google论文中的标准。

不过,Hadoop的开源特性使其成为分布式计算系统的事实上的国际标准。Yahoo,Facebook,Amazon以及国内的百度,阿里巴巴等众多互联网公司都以Hadoop为基础搭建自己的分布式计算系统。
Spark也是Apache基金会的开源项目,它由加州大学伯克利分校的实验室开发,是另外一种重要的分布式计算系统。它在Hadoop的基础上进行了一些架构上的改良。Spark与Hadoop最大的不同点在于,Hadoop使用硬盘来存储数据,而Spark使用内存来存储数据,因此Spark可以提供超过Hadoop100倍的运算速度。但是,由于内存断电后会丢失数据,Spark不能用于处理需要长期保存的数据。
Storm是Twitter主推的分布式计算系统,它由BackType团队开发,是Apache基金会的孵化项目。它在Hadoop的基础上提供了实时运算的特性,可以实时的处理大数据流。不同于Hadoop和Spark,Storm不进行数据的收集和存储工作,它直接通过网络实时的接受数据并且实时的处理数据,然后直接通过网络实时的传回结果。
Hadoop,Spark和Storm是目前最重要的三大分布式计算系统,Hadoop常用于离线的复杂的大数据处理,Spark常用于离线的快速的大数据处理,而Storm常用于在线的实时的大数据处理。

转自:http://blog.sina.com.cn/s/blog_631d3a630101nb77.html

Java中序列化的方式比较(json,serialize,protobuf)

 java, 开发  Java中序列化的方式比较(json,serialize,protobuf)已关闭评论
9月 162015
 

网上看到的一篇比较文章,分享下

 在java中socket传输数据时,数据类型往往比较难选择。可能要考虑带宽、跨语言、版本的兼容等问题。比较常见的做法有两种:一是把对象包装成JSON字符串传输,二是采用java对象的序列化和反序列化。随着Google工具protoBuf的开源,protobuf也是个不错的选择。对JSON,Object Serialize,ProtoBuf 做个对比。

定义一个待传输的对象UserVo:

Java代码  收藏代码

  1. public class UserVo{  
  2.     private String name;  
  3.     private int age;  
  4.     private long phone;  
  5.       
  6.     private List<UserVo> friends;  
  7. ……  
  8. }  

 初始化UserVo的实例src:

Java代码  收藏代码

  1. UserVo src = new UserVo();  
  2. src.setName(“Yaoming”);  
  3. src.setAge(30);  
  4. src.setPhone(13789878978L);  
  5.       
  6. UserVo f1 = new UserVo();  
  7. f1.setName(“tmac”);  
  8. f1.setAge(32);  
  9. f1.setPhone(138999898989L);  
  10. UserVo f2 = new UserVo();  
  11. f2.setName(“liuwei”);  
  12. f2.setAge(29);  
  13. f2.setPhone(138999899989L);  
  14.           
  15. List<UserVo> friends = new ArrayList<UserVo>();  
  16. friends.add(f1);  
  17. friends.add(f2);  
  18. src.setFriends(friends);  

JSON格式

采用Google的gson-2.2.2.jar 进行转义

Java代码  收藏代码

  1. Gson gson = new Gson();  
  2. String json = gson.toJson(src);  

 得到的字符串:

Js代码  收藏代码

  1. {“name”:“Yaoming”,“age”:30,“phone”:13789878978,“friends”:[{“name”:“tmac”,“age”:32,“phone”:138999898989},{“name”:“liuwei”,“age”:29,“phone”:138999899989}]}  

 字节数为153

Json的优点:明文结构一目了然,可以跨语言,属性的增加减少对解析端影响较小。缺点:字节数过多,依赖于不同的第三方类库。

 

Object Serialize

UserVo实现Serializalbe接口,提供唯一的版本号:

Java代码  收藏代码

  1. public class UserVo implements Serializable{  
  2.   
  3.     private static final long serialVersionUID = -5726374138698742258L;  
  4.     private String name;  
  5.     private int age;  
  6.     private long phone;  
  7.       
  8.     private List<UserVo> friends;  

 

序列化方法:

Java代码  收藏代码

  1. ByteArrayOutputStream bos = new ByteArrayOutputStream();  
  2. ObjectOutputStream os = new ObjectOutputStream(bos);  
  3. os.writeObject(src);  
  4. os.flush();  
  5. os.close();  
  6. byte[] b = bos.toByteArray();  
  7. bos.close();  

 字节数是238

 

反序列化:

Java代码  收藏代码

  1. ObjectInputStream ois = new ObjectInputStream(fis);  
  2. vo = (UserVo) ois.readObject();  
  3. ois.close();  
  4. fis.close();  

Object Serializalbe 优点:java原生支持,不需要提供第三方的类库,使用比较简单。缺点:无法跨语言,字节数占用比较大,某些情况下对于对象属性的变化比较敏感。 

对象在进行序列化和反序列化的时候,必须实现Serializable接口,但并不强制声明唯一的serialVersionUID

是否声明serialVersionUID对于对象序列化的向上向下的兼容性有很大的影响。我们来做个测试:

 
思路一

把UserVo中的serialVersionUID去掉,序列化保存。反序列化的时候,增加或减少个字段,看是否成功。

Java代码  收藏代码

  1. public class UserVo implements Serializable{  
  2.     private String name;  
  3.     private int age;  
  4.     private long phone;  
  5.       
  6.     private List<UserVo> friends;  

 

保存到文件中:

Java代码  收藏代码

  1. ByteArrayOutputStream bos = new ByteArrayOutputStream();  
  2. ObjectOutputStream os = new ObjectOutputStream(bos);  
  3. os.writeObject(src);  
  4. os.flush();  
  5. os.close();  
  6. byte[] b = bos.toByteArray();  
  7. bos.close();  
  8.   
  9. FileOutputStream fos = new FileOutputStream(dataFile);  
  10. fos.write(b);  
  11. fos.close();  

 

增加或者减少字段后,从文件中读出来,反序列化:

Java代码  收藏代码

  1. FileInputStream fis = new FileInputStream(dataFile);  
  2. ObjectInputStream ois = new ObjectInputStream(fis);  
  3. vo = (UserVo) ois.readObject();  
  4. ois.close();  
  5. fis.close();  

 

结果:抛出异常信息

Java代码  收藏代码

  1. Exception in thread “main” java.io.InvalidClassException: serialize.obj.UserVo; local class incompatible: stream classdesc serialVersionUID = 3305402508581390189, local class serialVersionUID = 7174371419787432394  
  2.     at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:560)  
  3.     at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1582)  
  4.     at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1495)  
  5.     at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1731)  
  6.     at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1328)  
  7.     at java.io.ObjectInputStream.readObject(ObjectInputStream.java:350)  
  8.     at serialize.obj.ObjectSerialize.read(ObjectSerialize.java:74)  
  9.     at serialize.obj.ObjectSerialize.main(ObjectSerialize.java:27)  
 
思路二

eclipse指定生成一个serialVersionUID,序列化保存,修改字段后反序列化

略去代码

结果:反序列化成功

结论

如果没有明确指定serialVersionUID,序列化的时候会根据字段和特定的算法生成一个serialVersionUID,当属性有变化时这个id发生了变化,所以反序列化的时候就会失败。抛出“本地classd的唯一id和流中class的唯一id不匹配”。

 

jdk文档关于serialVersionUID的描述:

写道
如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范”中所述。不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修饰符显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于直接声明类 — serialVersionUID 字段作为继承成员没有用处。数组类不能声明一个明确的 serialVersionUID,因此它们总是具有默认的计算值,但是数组类没有匹配 serialVersionUID 值的要求。

 

Google ProtoBuf

protocol buffers 是google内部得一种传输协议,目前项目已经开源(http://code.google.com/p/protobuf/)。它定义了一种紧凑得可扩展得二进制协议格式,适合网络传输,并且针对多个语言有不同得版本可供选择。

以protobuf-2.5.0rc1为例,准备工作:

下载源码,解压,编译,安装

Shell代码  收藏代码

  1. tar zxvf protobuf-2.5.0rc1.tar.gz  
  2. ./configure  
  3. ./make  
  4. ./make install  

 测试:

Shell代码  收藏代码

  1. MacBook-Air:~ ming$ protoc –version  
  2. libprotoc 2.5.0  

 安装成功!进入源码得java目录,用mvn工具编译生成所需得jar包,protobuf-java-2.5.0rc1.jar

 

1、编写.proto文件,命名UserVo.proto 

Text代码  收藏代码

  1. package serialize;  
  2.   
  3. option java_package = “serialize”;  
  4. option java_outer_classname=“UserVoProtos”;  
  5.   
  6. message UserVo{  
  7.     optional string name = 1;  
  8.     optional int32 age = 2;  
  9.     optional int64 phone = 3;  
  10.     repeated serialize.UserVo friends = 4;  
  11. }  

 

2、在命令行利用protoc 工具生成builder类

Shell代码  收藏代码

  1. protoc -IPATH=.proto文件所在得目录 –java_out=java文件的输出路径  .proto的名称   

 得到UserVoProtos类

 

3、编写序列化代码

Java代码  收藏代码

  1. UserVoProtos.UserVo.Builder builder = UserVoProtos.UserVo.newBuilder();  
  2. builder.setName(“Yaoming”);  
  3. builder.setAge(30);  
  4. builder.setPhone(13789878978L);  
  5.           
  6. UserVoProtos.UserVo.Builder builder1 = UserVoProtos.UserVo.newBuilder();  
  7. builder1.setName(“tmac”);  
  8. builder1.setAge(32);  
  9. builder1.setPhone(138999898989L);  
  10.           
  11. UserVoProtos.UserVo.Builder builder2 = UserVoProtos.UserVo.newBuilder();  
  12. builder2.setName(“liuwei”);  
  13. builder2.setAge(29);  
  14. builder2.setPhone(138999899989L);  
  15.           
  16. builder.addFriends(builder1);  
  17. builder.addFriends(builder2);  
  18.           
  19. UserVoProtos.UserVo vo = builder.build();  
  20.           
  21. byte[] v = vo.toByteArray();  

 字节数53

 

4、反序列化

Java代码  收藏代码

  1. UserVoProtos.UserVo uvo = UserVoProtos.UserVo.parseFrom(dstb);  
  2. System.out.println(uvo.getFriends(0).getName());  

 结果:tmac,反序列化成功

google protobuf 优点:字节数很小,适合网络传输节省io,跨语言 。缺点:需要依赖于工具生成代码。

 

工作机制

proto文件是对数据的一个描述,包括字段名称,类型,字节中的位置。protoc工具读取proto文件生成对应builder代码的类库。protoc xxxxx  –java_out=xxxxxx 生成java类库。builder类根据自己的算法把数据序列化成字节流,或者把字节流根据反射的原理反序列化成对象。官方的示例:https://developers.google.com/protocol-buffers/docs/javatutorial。

proto文件中的字段类型和java中的对应关系:

详见:https://developers.google.com/protocol-buffers/docs/proto

 .proto Type  java Type  c++ Type
double  double  double
float  float  float
int32  int  int32
int64  long   int64
uint32  int  uint32
unint64  long  uint64
sint32  int  int32
sint64  long  int64
fixed32  int  uint32
fixed64  long  uint64
sfixed32  int  int32
sfixed64  long  int64
bool  boolean  bool
string  String  string
bytes  byte  string
字段属性的描述:
写道
required: a well-formed message must have exactly one of this field.
optional: a well-formed message can have zero or one of this field (but not more than one).
repeated: this field can be repeated any number of times (including zero) in a well-formed message. The order of the repeated values will be preserved.

 

protobuf 在序列化和反序列化的时候,是依赖于.proto文件生成的builder类完成,字段的变化如果不表现在.proto文件中就不会影响反序列化,比较适合字段变化的情况。做个测试:
把UserVo序列化到文件中:
Java代码  收藏代码

  1. UserVoProtos.UserVo vo = builder.build();  
  2. byte[] v = vo.toByteArray();  
  3. FileOutputStream fos = new FileOutputStream(dataFile);  
  4. fos.write(vo.toByteArray());  
  5. fos.close();  

 

为UserVo增加字段,对应的.proto文件:
Text代码  收藏代码

  1. package serialize;  
  2.   
  3. option java_package = “serialize”;  
  4. option java_outer_classname=“UserVoProtos”;  
  5.   
  6. message UserVo{  
  7.     optional string name = 1;  
  8.     optional int32 age = 2;  
  9.     optional int64 phone = 3;  
  10.     repeated serialize.UserVo friends = 4;  
  11.     optional string address = 5;  
  12. }  

 

从文件中反序列化回来:
Java代码  收藏代码

  1. FileInputStream fis = new FileInputStream(dataFile);  
  2. byte[] dstb = new byte[fis.available()];  
  3. for(int i=0;i<dstb.length;i++){  
  4.     dstb[i] = (byte)fis.read();  
  5. }  
  6. fis.close();  
  7. UserVoProtos.UserVo uvo = UserVoProtos.UserVo.parseFrom(dstb);  
  8. System.out.println(uvo.getFriends(0).getName());  

 成功得到结果。

三种方式对比传输同样的数据,google protobuf只有53个字节是最少的。结论:
方式 优点 缺点
JSON

跨语言、格式清晰一目了然

字节数比较大,需要第三方类库
Object Serialize java原生方法不依赖外部类库 字节数比较大,不能跨语言
Google protobuf

跨语言、字节数比较少

编写.proto配置用protoc工具生成对应的代码

 

以上测试用例覆盖面比较窄,可能无法正确反应真实情况仅代表个人观点,欢迎随时指正和讨论。

转自:http://www.iteye.com/topic/1128881