导航:首页 > 数据处理 > 图片数据库包括哪些

图片数据库包括哪些

发布时间:2023-04-25 20:27:36

Ⅰ 开源图数据库有哪些

Neo4j、JanusGraph、TigerGraph、Dgraph这些都是,其实大多数的图数据库都是开源的,图数据库、图计算都算比较新的东西,还需要开源后大家共同去改进。这些都是国外的,其实国内大厂也开始做图数据库、图计算相关的软件了,比如阿里的GraphScope、字节的ByteGraph。

Ⅱ 什麽是Graph database

图形数据库(graphic database)是利用计算机将点、线、画霹图形基本元素按一定数据结同灶行存储的数据集合,将地图与其它类型的平面图中的图形描述为点、线、面等基本元素,并将这些图形元素按一定数据结构(通常为拓扑数据结构)建立起来的数据集合。包括两个层次:第一层次为拓扑编码的数据集合,由描述点、线大模、面等图形元素间关系的数据文件组成,包括多边形文件、线段文件、结点文辩袜件等。文件间通过关联数据项相互联系;第二层次为坐标编码数据集合,由描述各图形元素空间位置的坐标文件组成。图形数据携仿激库仍是目前地理信息系统中对矢量结构地图数字化数据进行组织的主要形式。

Ⅲ 哪类数据库处理图像类数据

image类型的数据库、图像数据库处理图像类数据。
图像数据库系统由数据输入系统、数据表示与管理系统、数据检索与操作系统和应用系早塌统组成。图像数据库系统处理的对象包括图像数据、地图、图形数据、一般文本、图形等,统称为模式数据。数据输入系统从模式数据中提取计算机可以使用的信息。一个典型的好粗例子是自动地将图输入系统。典型的应用系统包括:利用地图数据的地理信息系统、利用遥感图像的土地信息系统、利用各种绘图数据的计算机辅助设计系统、陆袜圆计算机动画设计系统以及其他图像处理和图案信息处理系统。

Ⅳ 国内图数据库排名前三的有哪些

DB-Engines 数据库流行度排行榜 6 月更新已发布,排名前二十如下:总体排名和上个月相比基本一致,其中排名前三的 Oracle、MySQL 和 Microsoft SQL Server 也是分数增加最多的三个数据库,增加的分数分别为 13.67、4.67 和 15.57,三者的总分也均已超过一千。

Ⅳ 用什么数据库来存储图片

一、图片一般是存储为路径的,这样存取速度快,也方便
二、如果非要存在数据库中的话,可以存为二进制数据,只是存取比较麻烦,效率和性能也不高

Ⅵ 图数据库的应用有哪些优点

图形数据库每个对象是一个节点,之间的关系是一条边。相对于关系数据库来说,图形数据库善于处理大量复杂、互连接、低结构化的数据,这些数据变化迅速,需要频繁的查询——在关系数据库中,由于这些查询会导致大量的表连接,从而导致性能问题,而且在设计使用上也不方便。
图形数据库适合用于社交网络,推荐系统等专注于构建关系图谱的系统。
图数据库的代表有Neo4J、FlockDB、InfoGrid、AllegroGraph、GraphDB等。

Ⅶ 基础数据库

(一)数据内容

基础数据库包括系统运行前所采集到的所有支撑数据,数据的具体内容在数据分类与数据源章节中已描述,概括可分为以下几类。

(1)遥感影像数据:包括历史图像数据,以及按照一定监测周期更新的遥感图像数据。

(2)数字线划图数据:矢量数据(现状专题图和历史专题图数据)、栅格数据、元数据等。入库前数据以ArcInfoCoverage格式分幅或整体存储,采用地理坐标系统。

(3)数字栅格图数据:包括1∶5万和1∶10万基础地理图形数据的扫描栅格数据。

(4)数字高程模型数据:塔里木河干流河道1∶1万和“四源一干”区域1∶10万数字高程模型。

(5)多媒体数据:考察照片、录像、录音和虚拟演示成果等多媒体资料。

(6)属性数据:社会经济与水资源数据、水利工程数据、生态环境数据等。

(二)数据存储结构

1.栅格数据

栅格数据包括遥感影像、数字栅格图、数字正射影像图、数字高程模型等,这些数据的存储结构基本类似,因此可进行统一设计。遥感图像数据库与普通的图像数据库在存储上有些差别,遥感图像作为传感器对地理、空间环境在不同条件下的测量结果(如光谱辐射特性、微波辐射特性),必须结合同时得到的几个图像才可以认为是对环境在一定的时间条件下的完整的描述,也即是说,可能需要一个图像集合才能构成一个图像的完整的概念,并使之与语义信息产生联系(罗睿等,2000)。因此,遥感图像数据存储结构模型必须能够描述几个图像(波段)之间的逻辑关系。利用ArcSDE进行数据入库时,系统可自动建立各图像(波段)之间的关系,并按一定规则存储在数据库系统中。

对栅格数据在后台将采用Oracle数据库管理系统进行存储。Oracle系统可直接存储影像信息,并具有较强的数据管理能力,可以实现栅格数据信息的快速检索和提取。数据引擎采用ArcSDE,实现各类影像数据的入库。数据存储的关键是建立图幅索引,本系统数据的存储按图幅号、图名、采集时间等内容建立索引。

栅格数据依据图形属性一体化的存储思想,采用大二进制格式直接存储数据,这种方式的存储可实现内容的快速检索查询,按索引表检索出相关项后可直接打开栅格数据,提高栅格数据的管理效率。

2.矢量数据

本系统采用图属一体化思想即将空间数据和属性数据合二为一,全部存在一个记录集中的思想存储空间数据,是目前GIS数据非常流行的存储方法。考虑到数据的具体情况,决定采用数据库存储空间数据和属性数据,部分具有少量、定型几何信息的地理要素如水文测站、河流、湖泊等,采用图属一体化思想存储其信息,而与其有关联关系的大量、多边化的属性信息如水文信息,则存储在属性数据表中,利用唯一标识符信息建立两表的关联。

针对本系统空间数据的特点,系统按照“数据库—子库—专题(基础数据)—层—要素—属性”的层次框架来构筑空间数据库,按照统一的地理坐标系统来存储空间数据,以实现对地理实体/专题要素进行分层叠加显示。

3.多媒体数据

Oracle系统可直接存储图片和视频信息,并具有较强的数据管理能力,可以实现多媒体信息的快速检索和提取。多媒体数据存储的关键是建立索引表,本系统多媒体数据的存储按类型、时间、内容等项目建立索引,直接存储于Oracle数据库中。

多媒体数据存储时,可以将多媒体内容与索引表结构合为一体,采用大二进制格式直接存储,这种存储方式可实现内容的快速检索和查询,按索引表检索出相关项后可直接打开多媒体内容,而且多媒体数据库也便于维护管理。

(三)空间索引设计

1.矢量空间索引

确定合适的格网级数、单元大小是建立空间格网索引的关键。格网太大,在一个格网内有多个空间实体,查询检索的准确度就低。格网太小,则索引数据量成倍增长和冗余,检索的速度和效率低。每一个数据层可采用不同大小、不同级别的空间索引格网单元,但每层级数最多不能超过三级。索引方式设置遵循以下基本原则:

(1)对于简单要素的数据层,尽可能选择单级索引格网,减少RDBMS搜索格网单元索引的级数,缩短空间索引搜索的过程;

(2)如果数据层中的要素封装边界大小变化比较大,应选择2或3级索引格网;

(3)如果用户经常对图层执行相同的查询,最佳格网的大小应是平均查询范围的1.5倍;

(4)格网的大小不能小于要素封装边界的平均大小。为了减少每个格网单元有多个要素封装边界的可能性,格网单元的大小应取要素封装边界平均大小的3倍;

(5)格网单元的大小不是一个确定性的问题,需要多次尝试和努力才会得到好的结果。有一些确定格网初始值的原则,用它们可以进一步确定最佳的格网大小。

SDE(Spatial Data Engine,即空间数据引擎),从空间管理的角度看,是一个连续的空间数据模型,可将地理特征的空间数据和属性数据统一集成在关系型数据库管理系统中。关系型数据库系统支持对海量数据的存储,从而也可实现对空间数据的海量存储。空间数据可通过层来进行数据的划分,将具有共同属性的一类要素放到一层中,每个数据库记录对应一层中一个实际要素,这样避免了检索整个数据表,减少了检索的数据记录数量,从而减少磁盘输入/输出的操作,加快了对空间数据查询的速度。

ArcSDE采用格网索引方式,将空间区域划分成合适大小的正方形格网,记录每一个格网内所包含的空间实体(对象),以及每一个实体的封装边界范围,即包围空间实体的左下角和右上角坐标。当用户进行空间查询时,首先计算出用户查询对象所在格网,然后通过格网号,就可以快速检索到所需的空间实体。因此确定合适的格网级数、单元大小是建立空间格网索引的关键,太大或太小均不合适,这就需要进行多次尝试,确定合适的网格大小,以保证各单元能均匀落在网格内。利用ArcSDE的索引表创建功能,记录每一网格单元的实体分布情况,形成图层空间索引表。根据空间索引表,ArcSDE实现了对空间数据的快速查询。

2.栅格数据空间索引

栅格数据的空间索引通过建立多级金字塔结构来实现。以高分辨率栅格数据为底层,逐级抽取数据,建立不同分辨率的数据金字塔结构,逐级形成较低分辨率的栅格数据。该方法通常会增加20%左右的存储空间,但却可以提高栅格数据的显示速度。在数据库查询检索时,调用合适级别的栅格数据,可提高浏览和显示速度。

(四)入库数据校验

入库数据的质量关系到系统评价分析结果的准确性。数据在生产中就需要严格进行质量控制。依据数据生产流程,将数据质量控制分成生产过程控制和结果控制。生产过程控制包括数据生产前期的质量控制、数据生产过程中的实时质量控制,结果质量控制为数据生产完成后的质量控制(裴亚波等,2003)。对入库数据的校验主要是进行数据生产完成后的质量控制和检查。

1.规范化检查

(1)代码规范化:所有地理代码尽量采用国家标准和行业标准,例如,行政代码采用中华人民共和国行政区划代码国标。

(2)数据格式规范化:所有数据采用标准交换数据格式,例如,矢量数据采用标准输出Coverage格式和E00格式。

(3)属性数据和关系数据字段规范化:所有属性数据和关系数据提前分门别类地设计字段的内容、长短和格式,操作过程中严格执行。

(4)坐标系统规范化:本系统所有与空间有关的数据采用统一的空间坐标系统,即地理坐标系统。

(5)精度规范化:所有数据按照数据精度与质量控制中所要求的精度进行采集和处理。

(6)命名规范化:所有数据按照命名要求统一命名,便于系统的查询。

(7)元数据规范化:依照元数据标准要求,进行元数据检查。

2.质量控制

数据质量是GIS成败的关键。对于关系型数据库设计,只要能保证表的实体完整性和参照完整性,并使之符合关系数据库的三个范式即可。对于空间数据库设计,则不仅要考虑数据采样、数据处理流程、空间配准、投影变换等问题,还应对数据质量做出定量分析。

数据质量一般可以通过以下几个方面来描述(吴芳华等,2001):

(1)准确度(Accuracy):即测量值与真值之间的接近程度,可用误差来衡量;

(2)精度(Precision):即对现象描述得详细程度;

(3)不确定性(Uncertainty):指某现象不能精确测得,当真值不可测或无法知道时,就无法确定误差,因而用不确定性取代误差;

(4)相容性(Compatibility):指两个来源不同的数据在同一个应用中使用的难易程度;

(5)一致性(Consistency):指对同一现象或同类现象表达的一致程度;

(6)完整性(Completeness):指具有同一准确度和精度的数据在类型上和特定空间范围内完整的程度;

(7)可得性(Accessibility):指获取或使用数据的容易程度;

(8)现势性(Timeliness):指数据反映客观现象目前状况的程度。

塔里木河流域生态环境动态监测系统的所有数据在数据质量评价后,还需要从数据格式、坐标一致性等方面进行入库质量检验,只有通过质量检验的数据才可以入库。

3.数据检验

空间数据质量检验包括以下步骤:

(1)数据命名是否规范,是否按设计要求命名;

(2)数据是否能够正常打开;

(3)投影方式是否正确;

(4)坐标系统是否正确;

(5)改错是否完成,拓扑关系是否建立;

(6)属性数据是否正确,包括字段设置是否依据设计进行、是否有空属性记录、是否有属性错误记录等。

关系数据质量检验包括以下步骤:

(1)数据命名是否规范,是否按设计要求命名;

(2)数据是否能够正常打开;

(3)数据字段是否按设计要求设置;

(4)是否有空属性记录;

(5)是否有属性错误记录。

属性数据的校验,主要采用以下三种方式:

(1)两次录入校验:对一些相互之间毫无关联的数据,进行两次的录入,编写程序对两次录入的结果进行比较,找出两次录入结果不一样的数据,查看正确值,进行改正。

(2)折线图检验:对一些相互之间有关联的序列数据,如人口统计数据,对这一类数据,编写程序把数据以折线图的形式显示在显示器上,数据的序列一般都有一定规律,如果出现较大的波动,则需对此点的数据进行检查修改。

(3)计算校验:对一些按一定公式计算后所得结果与其他数据有关联的数据,如某些数据的合计等于另一数据,编写程序对这类数据进行计算,计算结果与有关联的数据进行比较,找出结果不一样的数据,查看正确值,进行改正。

图形数据的校验,主要包括以下步骤(陈俊杰等,2005):

(1)图层校验:图形要素的放置图层是唯一的。对于入库的Coverage数据,系统将根据图层代码进行检查,确保图形要素对层入座。

(2)代码检查:图形要素的代码是唯一的。对于入库的Coverage数据,系统将根据入库要素代码与特征表中的代码进行比较,确保入库数据代码存在,杜绝非法代码入库。

(3)类型检查:对入库的数据,检查该要素的类型与特征表中的类型是否一致,确保图形要素对表入座。如点要素、线要素、面要素仅能赋相应的点、线、面代码,且该代码必须与特征表中的数据类型代码相同。

(4)范围检查:根据入库的数据,确定该类要素的大体范围(如X、Y坐标等),在数据入库前,比较入库数据与范围数据的大小,若入库数据在该范围内,则入库,否则给出提示检查信息。

(五)数据入库

1.遥感影像数据

利用空间数据引擎———ArcSDE可实现遥感影像数据在Oracle数据库中的存储和管理,在影像数据进行入库时,应加入相应的索引和影像描述字段。

遥感影像入库步骤:

(1)影像数据预处理:要将塔里木河遥感影像数据库建成一个多分辨率无缝影像数据库系统,客观上要求数据库中的影像数据在几何空间、灰度空间连续一致。因此,在数据采集阶段就需要对影像数据进行预处理,包括图像几何校正、灰度拼接(无缝镶嵌)、正射处理、投影变换等。

几何校正的目的是使校正后的图像重新定位到某种地图投影方式,以适用于各种定位、量测、多源影像的复合及与矢量地图、DTM等的套合显示与处理。几何校正多采用二次多项式算法和图像双线性内插重采样法进行图像校正。将纠正后具有规定地理编码的图像按多边形圈定需要拼接的子区,逐一镶嵌到指定模版,同时进行必要的色彩匹配,使整体图像色调一致,完成图像的几何拼接,再采用金字塔影像数据结构和“从粗到精”的分层控制策略实现逐级拼接。

数字正射影像具有统一的大地坐标系、丰富的信息量和真实的景观表达,易于制作具有“独立于比例尺”的多级金字塔结构影像。可以采用DTM和外方位元素经过数字微分纠正方法,获得数字正射影像,它的基本参数包括原始影像与正射影像的比例尺、采样分辨率等(方涛等,1997)。

投影变换需根据数据库系统定义的标准转换到统一的投影体系下。

(2)影像数据压缩:随着传感器空间分辨率的提高和对遥感信息需求的日益增长,获取的影像数据量成几何级数增大,如此庞大的数据将占用较大的存储空间,给影像的存储和传输带来不便(葛咏等,2000)。目前,系统处理的遥感影像数据已达数百千兆,单个文件的影像数据最大达到了2G,这样的数据量在调用显示时速度很慢,对影像数据进行压缩存储,将大大提高影像访问效率。本系统采用ArcSDE软件提供的无损压缩模式对入库影像进行压缩。

(3)影像导入:遥感影像的入库可通过ArcSDE或入库程序进行导入,并填写相关的索引信息,在入库时对大型的遥感影像数据进行自动分割,分为若干的块(tiles)进行存储。

(4)图像金字塔构建:采用ArcSDE提供的金字塔构建工具在入库时自动生成图像金字塔,用户只需要选择相应的参数设置即可。图像金字塔及其层级图像按分辨率分级存储与管理。最底层的分辨率最高,并且数据量最大,分辨率越低,其数据量越小,这样,不同的分辨率遥感图像形成了塔式结构。采用这种图像金字塔结构建立的遥感影像数据库,便于组织、存储与管理多尺度、多数据源遥感影像数据,实现了跨分辨率的索引与浏览,极大地提高了影像数据的浏览显示速度。

2.数字线划图

对纸图数字化、配准、校正、分层及拼接等处理后,生成标准分幅和拼接存储的数字矢量图,就可以进行图形数据入库。

(1)分幅矢量图形数据、图幅接合表:按图形比例尺、图幅号、制作时间、图层等方式,通过入库程序导入到数据库中,同时导入与该地理信息相对应的属性信息,建立空间信息与属性信息的关联。

(2)拼接矢量图形数据:按图形比例尺、制作时间、图层等方式,通过入库程序导入到数据库中,同时导入与该地理信息相对应的属性信息,建立空间信息与属性信息的关联。

3.栅格数据

对纸图数字化、配准、校正、分层及拼接等处理后,生成标准分幅和整体存储的数字栅格图,然后进行图形数据入库。

(1)分幅栅格图形数据、图幅接合表:按图形比例尺、图幅号、制作时间等方式,通过入库程序导入到数据库中。

(2)整幅栅格图形数据:按比例尺、制作时间等方式,通过入库程序导入到数据库中。

4.数字高程模型

(1)分幅数字高程模型数据、图幅接合表:按图形比例尺、图幅号、制作时间等方式,通过入库程序导入到数据库中。

(2)拼接数字高程模型数据:按比例尺、制作时间等方式通过入库程序导入到数据库中。

5.多媒体数据

多媒体数据入库可根据多媒体数据库内容的需要对入库数据进行预处理,包括音频、视频信息录制剪接、文字编辑、色彩选配等。对多媒体信息的加工处理需要使用特定的工具软件进行编辑。由于音频信息和视频信息数据量巨大,因此,对多媒体数据存储时需采用数据压缩技术,现在的许多商用软件已能够直接存储或播放压缩后的多媒体数据文件,这里主要考虑根据数据显示质量要求选择采用不同的存储格式。图4-2为各类多媒体数据的加工处理流程。

图4-2 多媒体数据加工处理流程图

6.属性数据

将收集的社会经济、水利工程、生态环境等属性资料,进行分析整理,输入计算机,最后经过程序的计算处理,存储到数据库中,具体流程如图4-3所示。

图4-3 属性数据入库流程图

Ⅷ 数据库 能不能 存储 图片有哪些数据库可以sql server,my sql 可以吗

关系数据库都可以存储图片,不绝启过是以困宏烂二进制数所的形式存储的。
故:oracle、汪漏db2、sqlserver、mysql等都可以。

有问题欢迎提问,满意请采纳!

Ⅸ 什么是图数据库

图数据库(Graph database) 并非指存储图片的数据库,而是以“图”这种数据结构存储和查询数据。目前比较典型的代表产品是Neo4j。

Ⅹ 如何用 Python 实现一个图数据库(Graph Database)

本文章是 重写 500 Lines or Less 系列的其中一篇,目标是重写 500 Lines or Less 系列的原有项目:Dagoba: an in-memory graph database。

Dagoba 是作者设计用来展示如何从零开始自己实现一个图数据库( Graph Database )。该名字似乎来源于作者喜欢的一个乐队,另一个原因是它的前缀 DAG 也正好是有向无环图 ( Directed Acyclic Graph ) 的缩写。本文也沿用了该名称。

图是一种常见的数据结构,它将信息描述为若干独立的节点( vertex ,为了和下文的边更加对称,本文中称为 node ),以及把节点关联起来的边( edge )。我们熟悉的链表以及多种树结构可以看作是符合特定规则的图。图在路径选择、推荐算法以及神经网络等方面都是重要的核心数据结构。

既然图的用途如此广泛,一个重要的问题就是如何存储它。如果在传统的关系数据库中存储图,很自然的做法就是为节点和边各自创建一张表,并用外键把它们关联起来。这样的话,要查找某人所有的子女,就可以写下类似下面的查询:

还好,不算太复杂。但是如果要查找孙辈呢?那恐怕就要使用子查询或者 CTE(Common Table Expression) 等特殊构造了。再往下想,曾孙辈又该怎么查询?孙媳妇呢?

这样我们会意识到,SQL 作为查询语言,它只是对二维数据表这种结构而设计的,用它去查询图的话非常笨拙,很快会变得极其复杂,也难以扩展。针对图而言,我们希望有一种更为自然和直观的查询语法,类似这样:

为了高效地存储和查询图这种数据结构,图数据库( Graph Database )应运而生。因为和传统的关系型数据库存在极大的差异,所以它属于新型数据库也就是 NoSql 的一个分支(其他分支包括文档数据库、列数据库等)。图数据库的主要代表包括 Neo4J 等。本文介绍的 Dagoba 则是具备图数据库核心功能、主要用于教学和演示的一个简单的图数据库。

原文代码是使用 JavaScript 编写的,在定义调用接口时大量使用了原型( prototype )这种特有的语言构造。对于其他主流语言的用户来说,原型的用法多少显得有些别扭和不自然。

考虑到本系列其他数据库示例大多是用 Python 实现的,本文也按照传统,用 Python 重写了原文的代码。同样延续之前的惯例,为了让读者更好地理解程序是如何逐步完善的,我们用迭代式的方法完成程序的各个组成部分。

原文在 500lines 系列的 Github 仓库中只包含了实现代码,并未包含测试。按照代码注释说明,测试程序位于作者的另一个代码库中,不过和 500lines 版本的实现似乎略有不同。

本文实现的代码参考了原作者的测试内容,但跳过了北欧神话这个例子——我承认确实不熟悉这些神祇之间的亲缘关系,相信中文背景的读者们多数也未必了解,虽然作者很喜欢这个例子,想了想还是不要徒增困惑吧。因此本文在编写测试用例时只参考了原文关于家族亲属的例子,放弃了神话相关的部分,尽管会减少一些趣味性,相信对于入门级的代码来说这样也够用了。

本文实现程序位于代码库的 dagoba 目录下。按照本系列程序的同意规则,要想直接执行各个已完成的步骤,读者可以在根目录下的 main.py 找到相应的代码位置,取消注释并运行即可。

本程序的所有步骤只需要 Python3 ,测试则使用内置的 unittest , 不需要额外的第三方库。原则上 Python3.6 以上版本应该都可运行,但我只在 Python3.8.3 环境下完整测试过。

本文实现的程序从最简单的案例开始,通过每个步骤逐步扩展,最终形成一个完整的程序。这些步骤包括:

接下来依次介绍各个步骤。

回想一下,图数据库就是一些点( node )和边( edge )的集合。现在我们要做出的一个重大决策是如何对节点/边进行建模。对于边来说,必须指定它的关联关系,也就是从哪个节点指向哪个节点。大多数情况下边是有方向的——父子关系不指明方向可是要乱套的!

考虑到扩展性及通用性问题,我们可以把数据保存为字典( dict ),这样可以方便地添加用户需要的任何数据。某些数据是为数据库内部管理而保留的,为了明确区分,可以这样约定:以下划线开头的特殊字段由数据库内部维护,类似于私有成员,用户不应该自己去修改它们。这也是 Python 社区普遍遵循的约定。

此外,节点和边存在互相引用的关系。目前我们知道边会引用到两端的节点,后面还会看到,为了提高效率,节点也会引用到边。如果仅仅在内存中维护它们的关系,那么使用指针访问是很直观的,但数据库必须考虑到序列化到磁盘的问题,这时指针就不再好用了。

为此,最好按照数据库的一般要求,为每个节点维护一个主键( _id ),用主键来描述它们之间的关联关系。

我们第一步要把数据库的模型建立起来。为了测试目的,我们使用一个最简单的数据库模型,它只包含两个节点和一条边,如下所示:

按照 TDD 的原则,首先编写测试:

与原文一样,我们把数据库管理接口命名为 Dagoba 。目前,能够想到的最简单的测试是确认节点和边是否已经添加到数据库中:

assert_item 是一个辅助方法,用于检查字典是否包含预期的字段。相信大家都能想到该如何实现,这里就不再列出了,读者可参考 Github 上的完整源码。

现在,测试是失败的。用最简单的办法实现数据库:

需要注意的是,不管添加节点还是查询,程序都使用了拷贝后的数据副本,而不是直接使用原始数据。为什么要这样做?因为字典是可变的,用户可以在任何时候修改其中的内容,如果数据库不知道数据已经变化,就很容易发生难以追踪的一致性问题,最糟糕的情况下会使得数据内容彻底混乱。

拷贝数据可以避免上述问题,代价则是需要占用更多内存和处理时间。对于数据库来说,通常查询次数要远远多于修改,所以这个代价是可以接受的。

现在测试应该正常通过了。为了让它更加完善,我们可以再测试一些边缘情况,看看数据库能否正确处理异常数据,比如:

例如,如果用户尝试添加重复主键,我们预期应抛出 ValueError 异常。因此编写测试如下:

为了满足以上测试,代码需要稍作修改。特别是按照 id 查找主键是个常用操作,通过遍历的方法效率太低了,最好是能够通过主键直接访问。因此在数据库中再增加一个字典:

完整代码请参考 Github 仓库。

在上个步骤,我们在初始化数据库时为节点明确指定了主键。按照数据库设计的一般原则,主键最好是不具有业务含义的代理主键( Surrogate key ),用户不应该关心它具体的值是什么,因此让数据库去管理主键通常是更为合理的。当然,在部分场景下——比如导入外部数据——明确指定主键仍然是有用的。

为了同时支持这些要求,我们这样约定:字段 _id 表示节点的主键,如果用户指定了该字段,则使用用户设置的值(当然,用户有责任保证它们不会重复);否则,由数据库自动为它分配一个主键。

如果主键是数据库生成的,事先无法预知它的值是什么,而边( edge )必须指定它所指向的节点,因此必须在主键生成后才能添加。由于这个原因,在动态生成主键的情况下,数据库的初始化会略微复杂一些。还是先写一个测试:

为支持此功能,我们在数据库中添加一个内部字段 _next_id 用于生成主键,并让 add_node 方法返回新生成的主键:

接下来,再确认一下边是否可以正常访问:

运行测试,一切正常。这个步骤很轻松地完成了,不过两个测试( DbModelTest 和 PrimaryKeyTest )出现了一些重复代码,比如 get_item 。我们可以把这些公用代码提取出来。由于 get_item 内部调用了 TestCase.assertXXX 等方法,看起来应该使用继承,但从 TestCase 派生基类容易引起一些潜在的问题,所以我转而使用另一个技巧 Mixin :

实现数据库模型之后,接下来就要考虑如何查询它了。

在设计查询时要考虑几个问题。对于图的访问来说,几乎总是由某个节点(或符合条件的某一类节点)开始,从与它相邻的边跳转到其他节点,依次类推。所以链式调用对查询来说是一种很自然的风格。举例来说,要知道 Tom 的孙子养了几只猫,可以使用类似这样的查询:

可以想象,以上每个方法都应该返回符合条件的节点集合。这种实现是很直观的,不过存在一个潜在的问题:很多时候用户只需要一小部分结果,如果它总是不计代价地给我们一个巨大的集合,会造成极大的浪费。比如以下查询:

为了避免不必要的浪费,我们需要另外一种机制,也就是通常所称的“懒式查询”或“延迟查询”。它的基本思想是,当我们调用查询方法时,它只是把查询条件记录下来,而并不立即返回结果,直到明确调用某些方法时才真正去查询数据库。

如果读者比较熟悉流行的 Python ORM,比如 SqlAlchemy 或者 Django ORM 的话,会知道它们几乎都是懒式查询的,要调用 list(result) 或者 result[0:10] 这样的方法才能得到具体的查询结果。

在 Dagoba 中把触发查询的方法定义为 run 。也就是说,以下查询执行到 run 时才真正去查找数据:

和懒式查询( Lazy Query )相对应的,直接返回结果的方法一般称作主动查询( Eager Query )。主动查询和懒式查询的内在查找逻辑基本上是相同的,区别只在于触发机制不同。由于主动查询实现起来更加简单,出错也更容易排查,因此我们先从主动查询开始实现。

还是从测试开始。前面测试所用的简单数据库数据太少,难以满足查询要求,所以这一步先来创建一个更复杂的数据模型:

此关系的复杂之处之一在于反向关联:如果 A 是 B 的哥哥,那么 B 就是 A 的弟弟/妹妹,为了查询到他们彼此之间的关系,正向关联和反向关联都需要存在,因此在初始化数据库时需要定义的边数量会很多。

当然,父子之间也存在反向关联的问题,为了让问题稍微简化一些,我们目前只需要向下(子孙辈)查找,可以稍微减少一些关联数量。

因此,我们定义数据模型如下。为了减少重复工作,我们通过 _backward 字段定义反向关联,而数据库内部为了查询方便,需要把它维护成两条边:

然后,测试一个最简单的查询,比如查找某人的所有孙辈:

这里 outcome/income 分别表示从某个节点出发、或到达它的节点集合。在原作者的代码中把上述方法称为 out/in 。当然这样看起来更加简洁,可惜的是 in 在 Python 中是个关键字,无法作为函数名。我也考虑过加个下划线比如 out_.in_ 这种形式,但看起来也有点怪异,权衡之后还是使用了稍微啰嗦一点的名称。

现在我们可以开始定义查询接口了。在前面已经说过,我们计划分别实现两种查询,包括主动查询( Eager Query )以及延迟查询( Lazy Query )。

它们的内在查询逻辑是相通的,看起来似乎可以使用继承。不过遵循 YAGNI 原则,目前先不这样做,而是只定义两个新类,在满足测试的基础上不断扩展。以后我们会看到,与继承相比,把共同的逻辑放到数据库本身其实是更为合理的。

接下来实现访问节点的方法。由于 EagerQuery 调用查询方法会立即返回结果,我们把结果记录在 _result 内部字段中。虽然 node 方法只返回单个结果,但考虑到其他查询方法几乎都是返回集合,为统一起见,让它也返回集合,这样可以避免同时支持集合与单结果的分支处理,让代码更加简洁、不容易出错。此外,如果查询对象不存在的话,我们只返回空集合,并不视为一个错误。

查询输入/输出节点的方法实现类似这样:

查找节点的核心逻辑在数据库本身定义:

以上使用了内部定义的一些辅助查询方法。用类似的逻辑再定义 income ,它们的实现都很简单,读者可以直接参考源码,此处不再赘述。

在此步骤的最后,我们再实现一个优化。当多次调用查询方法后,结果可能会返回重复的数据,很多时候这是不必要的。就像关系数据库通常支持 unique/distinct 一样,我们也希望 Dagoba 能够过滤重复的数据。

假设我们要查询某人所有孩子的祖父,显然不管有多少孩子,他们的祖父应该是同一个人。因此编写测试如下:

现在来实现 unique 。我们只要按照主键把重复数据去掉即可:

在上个步骤,初始化数据库指定了双向关联,但并未测试它们。因为我们还没有编写代码去支持它们,现在增加一个测试,它应该是失败的:

运行测试,的确失败了。我们看看要如何支持它。回想一下,当从边查找节点时,使用的是以下方法:

这里也有一个潜在的问题:调用 self.edges 意味着遍历所有边,当数据库内容较多时,这是巨大的浪费。为了提高性能,我们可以把与节点相关的边记录在节点本身,这样要查找边只要看节点本身即可。在初始化时定义出入边的集合:

在添加边时,我们要同时把它们对应的关系同时更新到节点,此外还要维护反向关联。这涉及对字典内容的部分复制,先编写一个辅助方法:

然后,将添加边的实现修改如下:

这里的代码同时添加正向关联和反向关联。有的朋友可能会注意到代码略有重复,是的,但是重复仅出现在该函数内部,本着“三则重构”的原则,暂时不去提取代码。

实现之后,前面的测试就可以正常通过了。

在这个步骤中,我们来实现延迟查询( Lazy Query )。

延迟查询的要求是,当调用查询方法时并不立即执行,而是推迟到调用特定方法,比如 run 时才执行整个查询,返回结果。

延迟查询的实现要比主动查询复杂一些。为了实现延迟查询,查询方法的实现不能直接返回结果,而是记录要执行的动作以及传入的参数,到调用 run 时再依次执行前面记录下来的内容。

如果你去看作者的实现,会发现他是用一个数据结构记录执行操作和参数,此外还有一部分逻辑用来分派对每种结构要执行的动作。这样当然是可行的,但数据处理和分派部分的实现会比较复杂,也容易出错。

本文的实现则选择了另外一种不同的方法:使用 Python 的内部函数机制,把一连串查询变换成一组函数,每个函数取上个函数的执行结果作为输入,最后一个函数的输出就是整个查询的结果。由于内部函数同时也是闭包,尽管每个查询的参数形式各不相同,但是它们都可以被闭包“捕获”而成为内部变量,所以这些内部函数可以采用统一的形式,无需再针对每种查询设计额外的数据结构,因而执行过程得到了很大程度的简化。

首先还是来编写测试。 LazyQueryTest 和 EagerQueryTest 测试用例几乎是完全相同的(是的,两种查询只在于内部实现机制不同,它们的调用接口几乎是完全一致的)。

因此我们可以把 EagerQueryTest 的测试原样不变拷贝到 LazyQueryTest 中。当然拷贝粘贴不是个好注意,对于比较冗长而固定的初始化部分,我们可以把它提取出来作为两个测试共享的公共函数。读者可参考代码中的 step04_lazy_query/tests/test_lazy_query.py 部分。

程序把查询函数的串行执行称为管道( pipeline ),用一个变量来记录它:

然后依次实现各个调用接口。每种接口的实现都是类似的:用内部函数执行真正的查询逻辑,再把这个函数添加到 pipeline 调用链中。比如 node 的实现类似下面:

其他接口的实现也与此类似。最后, run 函数负责执行所有查询,返回最终结果;

完成上述实现后执行测试,确保我们的实现是正确的。

在前面我们说过,延迟查询与主动查询相比,最大的优势是对于许多查询可以按需要访问,不需要每个步骤都返回完整结果,从而提高性能,节约查询时间。比如说,对于下面的查询:

以上查询的意思是从孙辈中找到一个符合条件的节点即可。对该查询而言,主动查询会在调用 outcome('son') 时就遍历所有节点,哪怕最后一步只需要第一个结果。而延迟查询为了提高效率,应在找到符合条件的结果后立即停止。

目前我们尚未实现 take 方法。老规矩,先添加测试:

主动查询的 take 实现比较简单,我们只要从结果中返回前 n 条记录:

延迟查询的实现要复杂一些。为了避免不必要的查找,返回结果不应该是完整的列表( list ),而应该是个按需返回的可迭代对象,我们用内置函数 next 来依次返回前 n 个结果:

写完后运行测试,确保它们是正确的。

从外部接口看,主动查询和延迟查询几乎是完全相同的,所以用单纯的数据测试很难确认后者的效率一定比前者高,用访问时间来测试也并不可靠。为了测试效率,我们引入一个节点访问次数的概念,如果延迟查询效率更高的话,那么它应该比主动查询访问节点的次数更少。

为此,编写如下测试:

我们为 Dagoba 类添加一个成员来记录总的节点访问次数,以及两个辅助方法,分别用于获取和重置访问次数:

然后浏览代码,查找修改点。增加计数主要在从边查找节点的时候,因此修改部分如下:

此外还有 income/outcome 方法,修改都很简单,这里就不再列出。

实现后再次运行测试。测试通过,表明延迟查询确实在效率上优于主动查询。

不像关系数据库的结构那样固定,图的形式可以千变万化,查询机制也必须足够灵活。从原理上讲,所有查询无非是从某个节点出发按照特定方向搜索,因此用 node/income/outcome 这三个方法几乎可以组合出任意所需的查询。

但对于复杂查询,写出的代码有时会显得较为琐碎和冗长,对于特定领域来说,往往存在更为简洁的名称,例如:母亲的兄弟可简称为舅舅。对于这些场景,如果能够类似 DSL (领域特定语言)那样允许用户根据专业要求自行扩展,从而简化查询,方便阅读,无疑会更为友好。

如果读者去看原作者的实现,会发现他是用一种特殊语法 addAlias 来定义自己想要的查询,调用方法时再进行查询以确定要执行的内容,其接口和内部实现都是相当复杂的。

而我希望有更简单的方法来实现这一点。所幸 Python 是一种高度动态的语言,允许在运行时向类中增加新的成员,因此做到这一点可能比预想的还要简单。

为了验证这一点,编写测试如下:

无需 Dagoba 的实现做任何改动,测试就可以通过了!其实我们要做的就是动态添加一个自定义的成员函数,按照 Python 对象机制的要求,成员函数的第一个成员应该是名为 self 的参数,但这里已经是在 UnitTest 的内部,为了和测试类本身的 self 相区分,新函数的参数增加了一个下划线。

此外,函数应返回其所属的对象,这是为了链式调用所要求的。我们看到,动态语言的灵活性使得添加新语法变得非常简单。

到此,一个初具规模的图数据库就形成了。

和原文相比,本文还缺少一些内容,比如如何将数据库序列化到磁盘。不过相信读者都看到了,我们的数据库内部结构基本上是简单的原生数据结构(列表+字典),因此序列化无论用 pickle 或是 JSON 之类方法都应该是相当简单的。有兴趣的读者可以自行完成它们。

我们的图数据库实现为了提高查询性能,在节点内部存储了边的指针(或者说引用)。这样做的好处是,无论数据库有多大,从一个节点到相邻节点的访问是常数时间,因此数据访问的效率非常高。

但一个潜在的问题是,如果数据库规模非常大,已经无法整个放在内存中,或者出于安全性等原因要实现分布式访问的话,那么指针就无法使用了,必须要考虑其他机制来解决这个问题。分布式数据库无论采用何种数据模型都是一个棘手的问题,在本文中我们没有涉及。有兴趣的读者也可以考虑 500lines 系列中关于分布式和集群算法的其他一些文章。

本文的实现和系列中其他数据库类似,采用 Python 作为实现语言,而原作者使用的是 JavaScript ,这应该和作者的背景有关。我相信对于大多数开发者来说, Python 的对象机制比 JavaScript 基于原型的语法应该是更容易阅读和理解的。

当然,原作者的版本比本文版本在实现上其实是更为完善的,灵活性也更好。如果想要更为优雅的实现,我们可以考虑使用 Python 元编程,那样会更接近于作者的实现,但也会让程序的复杂性大为增加。如果读者有兴趣,不妨对照着去读读原作者的版本。

阅读全文

与图片数据库包括哪些相关的资料

热点内容
手机微信博云学小程序怎么登录 浏览:792
口罩出口信息怎么看 浏览:860
产品防伪数码是什么意思啊 浏览:161
市场营销有哪些应用 浏览:317
花喜代理怎么加盟 浏览:40
信息管理人员经历了哪些阶段 浏览:969
仁化汽车配件代理加盟如何 浏览:1000
之江生物产品销量怎么样 浏览:670
宇花灵技术怎么用 浏览:602
想去泉州卖菜哪个菜市场人流大 浏览:411
沈阳雪花酒水怎么代理 浏览:125
rng秘密交易是什么意思 浏览:732
重庆红糖锅盔怎么代理赚钱吗 浏览:383
考察投资项目关注哪些数据 浏览:592
家纺家具都有什么产品 浏览:37
丘氏冰棒产品有哪些 浏览:414
程序员如何拉到业务 浏览:177
揭阳火车站到炮台市场怎么走 浏览:843
二线国企程序员怎么提升技能 浏览:154
蓝翔技术学院西点多少钱 浏览:789