我们开始需要使用protobuf的原因是,在处理OJ的contest模块的时候,碰到一个问题就是生成contestRank的时候,需要存储很多信息。如果我们采用model存储的话,那么一方面后续如果继续加入其他信息的话修改会灰常麻烦,另一方面就是实现比较复杂,因为对于rank来说,每一条rank的主键首先是UserID,其次存储的基本信息有AC数,题目AC情况,罚时等等,其中题目AC情况又包括以题目ID为主键,属性(是否AC,AC时间,罚时,这道题目的提交统计),而每一道题目的提交统计又是一个submit实体,这样的话,就会有多级嵌套存在。如果后续添加的话会更多。还有一个原因就是存储也不方便,逻辑构思不是很明确,需要注意保存各种信息,实现复杂。
protobuf的主要机理是把存储的信息序列化为一个字符串存储,需要信息的时候又可以逆序列化把原始信息还原出来,而且可以实现多级嵌套的属性。所以就尝试使用这个来解决rank的存储(/ □ \)
1:protobuf是什么?
好吧,这个有百科,see see:protocolbuffer是google 的一种数据交换的格式,它独立于语言,独立于平台。google 提供了三种语言的实现:java、c++ 和 python,每一种实现都包含了相应语言的编译器以及库文件。由于它是一种二进制的格式,比使用 xml 进行数据交换快许多。可以把它用于分布式应用之间的数据通信或者异构环境下的数据交换。作为一种效率和兼容性都很优秀的二进制数据传输格式,可以用于诸如网络传输、配置文件、数据存储等诸多领域。
简单地说,就是把某种数据结构的信息,以某种格式保存起来。主要用于数据存储、传输协议格式等场合。
2:下载安装:
地址:http://code.google.com/p/protobuf/downloads/list
安装:
tar -xzf protobuf-2.1.0.tar.gz
cd protobuf-2.1.0
./configure
make
make check
make install
3:使用建立
网上有各种例子,比如简单的book的书写等等都可以用来练习操作
首先,首先我们需要编写一个 proto 文件,定义我们程序中需要处理的结构化数据,在 protobuf 的术语中,结构
化数据被称为 Message。例如:
package contest_submit;
message ContestSubmit {
required int32 id = 1;
required int64 timestamp = 2;
}
上述例子中id是int32的,timestamp是int64的。required代表这个是必须的,有点像数据库里的not null,还有一
种就是optional,即可选的。
编译.proto文件,命令如下:
protoc -I=./ --python_out=./ ./*.proto
代表的意思是输入的.proto文件在当前目录,输出生成的编译文件到当前目录,源文件是所有以.proto结尾的文件
。网上完整的格式是:
假设您的 proto 文件存放在 $SRC_DIR 下面,您也想把生成的文件放在同一个目录下,则可以使用如下命令:
protoc -I=$SRC_DIR --python_out=$DST_DIR $SRC_DIR/文件明.proto
编译完成之后会生成 contest_submit_pb2.py文件(对于上面的例子来说)
当然,protobuf也是可以嵌套的。即可以在一个类中声明另一个类的对象,例如,rank的实现:
package contest_rank;
message Submit {
optional string status = 1;
optional string date_time = 2;
optional string runID = 3;
}
message Problem {
optional int32 problemID = 1;
repeated Submit submit = 2;
optional int32 acindex = 3;
optional int32 totalindex = 4;
optional int32 time = 5;
optional int32 FB = 6;
}
message Rank {
optional string userID = 1;
optional string contestID = 2;
repeated Problem problem = 3;
optional int32 penalty = 4;
optional int32 ac = 5;
optional int32 total = 6;
}
message ContestRankList {
optional string contestID = 1;
repeated Rank rank = 2;
}
这样的话,就可以实现我们前面的需求即多级信息嵌套了。
使用:
首先,要记得载入头文件from 文件夹.proto import rank_pb2
对于实现rank的存储的时候,首先声明一个rank类,以便后续对数据的处理
class Rank():
class Problem():
class Submit():
def __init__(self, runID = "", status="", date_time=""):
self.status = status
self.date_time = date_time
self.runID = runID
def __init__(self, problemID):
self.problemID = problemID
self.submit_list = []
self.acindex = 0
self.totalindex = 0
self.time = 0
self.FB = 0
def add_submit(self, submit):
self.submit_list.append(submit)
def __init__(self, userID, contestID):
self.userID = userID
self.contestID = contestID
self.problem_list = {}
self.ac = 0
self.penalty = 0
self.total = 0
当我们得到rank_list的时候(rank_list是一个存放rank的字典),首先声明一个proto的载体:
contest_rank_list = rank_pb2.ContestRankList()
然后填充信息,比如先把比赛的ID加入,即contest_rank_list.contestID = contestID。然后按照protobuf的结构,依
次把需要的信息填入。对于嵌套的实体来说,每次声明的时候可以使用add()方法,比如在一个比赛中,会有多个用户
的rank信息存在,所以按照用户ID来添加信息:
rank_proto = contest_rank_list.rank.add()
rank.load_data_to_proto(rank_proto)
其中的load_data_to_proto是rank类的一个方法,内容就是把信息加入到protobuf里面而已:
def load_data_to_proto(self, rank):
rank.userID = self.userID
rank.contestID = self.contestID
rank.ac = self.ac
rank.penalty = self.penalty
rank.total = self.total
for problemID in self.problem_list:
problem = self.problem_list[problemID]
p = rank.problem.add()
p.problemID = problem.problemID
p.acindex = problem.acindex
p.totalindex = problem.totalindex
p.time = problem.time
p.FB = problem.FB
for submit in problem.submit_list:
s = p.submit.add()
s.status = submit.status
s.date_time = submit.date_time
s.runID = submit.runID
return rank
这样我们就把数据存入了我们声明的protobuf里面了,然后就是存储的时候序列化,比如我们存储到
SSDB里面即:
ssdb_api.SetContestRankListProto(contestID, contest_rank_list.SerializeToString())
至此,信息已经存入数据库,那么接下来就是如何读出了:
对于得到我们存储的rank信息来说,即:
contest_rank_list = ssdb_api.GetContestRankListProto(contest_id)
rank_list = rank_pb2.ContestRankList()
rank_list.ParseFromString(contest_rank_list)
即,再次声明一个protobuf的对象,然后逆序列化,就得到了我们需要的信息,简单吧(/ □ \)。
后面需要的数据,就用父名.子名访问即可。