libmxml是一个开源、小巧的C语言xml库。这里简单分析一下它是用什么样的数据结构来保存分析过的xml文档。

  mxml关键的结构体mxml_node_t是这样的实现的:

struct mxml_node_s            /**** An XML node. @private@ ****/
{
mxml_type_t type; /* Node type */
struct mxml_node_s *next; /* Next node under same parent */
struct mxml_node_s *prev; /* Previous node under same parent */
struct mxml_node_s *parent; /* Parent node */
struct mxml_node_s *child; /* First child node */
struct mxml_node_s *last_child; /* Last child node */
mxml_value_t value; /* Node value */
int ref_count; /* Use count */
void *user_data; /* User data */
}; typedef struct mxml_node_s mxml_node_t; /**** An XML node. ****/

  

  它使用左孩子右兄弟的树形结构来描述xml报文:即下层节点登记在child链表,兄弟节点登记在next链表。 如果某个节点下面有N个子节点,则child指向第一个子节点,该子节点的next指向下一个同父节点的子节点。  比较特殊的是,mxml把xml节点值也认为是一个子节点。例如<group>value</group>, 其中value(type是MXML_OPAQUE)是一个独立的子节点,挂载在group节点(type是MXML_ELEMENT)下面。  另外,空白符(空格,回车换行,制表符)和注释,虽然对xml报文无实质意义,但mxml还是把它们做为一个节点存储起来。

  由于mxml只是使用简单的链表存储xml元素,所以元素节点个数比较多时,mxml查找元素效率是比较低的。所以libmxml提供了一个索引查找的函数,它需要先遍历xml元素树,生成一个排序过的数组,加快查找速度。

  为了方便大家理解,我写了一个函数打印xml结构体。

void printNode(mxml_node_t *node, int nNodeSn, int level)
{
static int currNodeSn = ;
if (node == NULL)
{
return;
} ++currNodeSn; //每遇到一个新节点 则将节点序号递增,做为本节点序号
printf("[%- 3d -> %- 3d] ", currNodeSn, nNodeSn); switch (node->type)
{
case MXML_ELEMENT:
{
int i;
printf("level %d MXML_ELEMENT [%s]", level, node->value.element.name);
for (i = ; i < node->value.element.num_attrs; ++i)
{
printf(" %s=%s", node->value.element.attrs[i].name, node->value.element.attrs[i].value);
}
printf("\n");
}
break;
case MXML_INTEGER:
printf("level %d MXML_INTEGER %d\n", level, node->value.integer);
break;
case MXML_OPAQUE:
printf("level %d MXML_OPAQUE [%s]\n", level, node->value.opaque);
break;
case MXML_REAL:
printf("level %d MXML_REAL %lf\n", level, node->value.real);
break;
case MXML_TEXT:
printf("level %d MXML_TEXT [%s]\n", level, node->value.text.string);
break;
case MXML_CUSTOM:
printf("level %d MXML_CUSTOM\n", level);
break;
default:
printf("unknown node type %d\n", node->type);
} //深度优先遍历
if (node->child)
{
//访问子节点时把本节点序号做为父节点序号 层级加1
printNode(node->child, currNodeSn, level + );
} if (node->next)
{
//访问兄弟节点,直接传父节点序号即可 层级也不用加1
printNode(node->next, nNodeSn, level);
}
}

  运行示例如下:

  xml源如下:

<?xml version="1.0" encoding="GBK" ?>
<group>
<option>122334 我们
<string>我们</string>45677
<keyword type="opaque">InputSlot</keyword>
<default type="opaque">Auto</default>
<text>Media Source</text>
<order type="real">10.000000</order>
<choice>
<keyword type="opaque">Auto</keyword>
<text>Auto Tray Selection</text>
<code type="opaque" />
</choice>
<choice>
<keyword type="opaque">Upper</keyword>
<text>Tray 1</text>
<code type="opaque">&lt;&lt;/MediaPosition 0&gt;&gt;setpagedevice</code>
</choice>
<choice>
<keyword type="opaque">Lower</keyword>
<text>Tray 2</text>
<code type="opaque">&lt;&lt;/MediaPosition 1&gt;&gt;setpagedevice</code>
</choice>
</option> 我 12334545 050504550
<integer>123</integer>
<string>Now is the time for all good men to come to the aid of their
country.</string>
<!-- this is a comment -->
<![CDATA[this is CDATA 0123456789ABCDEF]]>
</group>

  用我这个printNode分析结果如下:

说明:[   ->   ],代表本节点序号是1,其父节点序号是0,level 0代表本节点是最顶层节点。

[   ->   ] level  MXML_ELEMENT [?xml version="1.0" encoding="GBK" ?]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_ELEMENT [group]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_ELEMENT [option]
[ -> ] level MXML_OPAQUE [ 我们
]
[ -> ] level MXML_ELEMENT [string]
[ -> ] level MXML_OPAQUE [我们]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_ELEMENT [keyword] type=opaque
[ -> ] level MXML_OPAQUE [InputSlot]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_ELEMENT [default] type=opaque
[ -> ] level MXML_OPAQUE [Auto]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_ELEMENT [text]
[ -> ] level MXML_OPAQUE [Media Source]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_ELEMENT [order] type=real
[ -> ] level MXML_OPAQUE [10.000000]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_ELEMENT [choice]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_ELEMENT [keyword] type=opaque
[ -> ] level MXML_OPAQUE [Auto]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_ELEMENT [text]
[ -> ] level MXML_OPAQUE [Auto Tray Selection]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_ELEMENT [code] type=opaque
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_ELEMENT [choice]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_ELEMENT [keyword] type=opaque
[ -> ] level MXML_OPAQUE [Upper]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_ELEMENT [text]
[ -> ] level MXML_OPAQUE [Tray ]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_ELEMENT [code] type=opaque
[ -> ] level MXML_OPAQUE [<</MediaPosition >>setpagedevice]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_ELEMENT [choice]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_ELEMENT [keyword] type=opaque
[ -> ] level MXML_OPAQUE [Lower]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_ELEMENT [text]
[ -> ] level MXML_OPAQUE [Tray ]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_ELEMENT [code] type=opaque
[ -> ] level MXML_OPAQUE [<</MediaPosition >>setpagedevice]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_OPAQUE [ 我12334545
]
[ -> ] level MXML_ELEMENT [integer]
[ -> ] level MXML_OPAQUE []
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_ELEMENT [string]
[ -> ] level MXML_OPAQUE [Now is the time for all good men to come to the aid of their
country.]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_ELEMENT [!-- this is a comment --]
[ -> ] level MXML_OPAQUE [
]
[ -> ] level MXML_ELEMENT [![CDATA[this is CDATA 0123456789ABCDEF]]]
[ -> ] level MXML_OPAQUE [
]
xml报文与结构体转换优化

    项目中每个交易都有一个必须的步骤:把请求报文的内容转换到流水结构体。目前的做法是对于流水结构里面的字段,逐个到xml报文(调用XmlGetTextByPath),根据路径在xml报文里面找出对应的值。
根据callgrind分析,此类操作在占用了交易的30%以上cpu时间,值得优化。为此我提出另一个做法:
开发新的函数,XmlGetTextByPathMutiple。改变目前每取一个字段就遍历一次xml的操作,一次性将所有需要取出的字段对应的xml路径传给新函数。在遍历过程中检查所需路径是否存在,如果存在则取出。
如果遍历完成后还没有遇到的路径,则视为不存在。
由于目前我们的请求xml报文都相对比较小,遍历xml开销不大。并且现在每个交易需要从请求xml获取的字段至少十几个以上,新函数理论上可以比原函数更节约时间与资源。
为加快遍历过程中检查xml路径是否存在的过程,需要在函数开始前先对所有字段的xml路径做哈希计算。然后把哈希结果放到C99变长数组,再进行排序。
数组元素结构说明如下:
typedef struct tagMemInfo
{
const char *xmlPath; //字段对应的xml路径
void *destBuf; //结果存放区
size_t destBufLen; //存放区长度
int destType; //结果类型,目前只支持char数组和double
int isNullAble; //是否可为空
}MemInfo; typedef struct tagXmlGetInfo
{
int pathHashCode; //xml路径对应的哈希值
const char *xmlPath; //字段对应的xml路径
int isNullFlag; //是否为空,初始化都是1
MemInfo *memInfo;
}XmlGetInfo;
遍历过程中,对于每个xml节点,我们先计算其路径的哈希值。根据哈希值二分查找数组,看是否有与该值相同的目标字段。
如果找到哈希值相同,并且路径也相同的,则把xml节点值取出来放到指定的缓冲区。 计算哈希的函数推荐使用glib的g_str_hash,其使用的是DJB算法。这样我们在遍历子路径可以在父节点的哈希值基础上做增量计算,减少哈希值计算的开销。 //伪代码如下:
//parentHashCode:外部调用统一传5381,递归调用传本节点哈希值
void XmlGetTextByPathMutipleInternal(const XmlGetInfo *xmlGetArr, size_t arrLen,
mxml_t *currNode, int parentHashCode);
{
int nHashCode = parentHashCode;
根据parentHashCode及本节点名称计算nHashCode
使用nHashCode在xmlGetArr里面二分查找
如果找到符合要求的路径,则将本节点值取出来(即第一个孩子节点) for (第一个孩子节点; 兄弟节点 != NULL; 取出兄弟节点)
{
如果发现该节点是xml路径节点 //可能是文本节点
则调用函数XmlGetTextByPathMutipleInternal
}
} int XmlGetTextByPathMutipleRel(const XmlGetInfo *xmlGetArr, size_t arrLen,
mxml_t *rootNode)
{
int nHashCode = ;
for (第一个孩子节点; 兄弟节点 != NULL; 取出兄弟节点)
{
如果发现该节点是xml路径节点 //可能是文本节点
则调用函数XmlGetTextByPathMutipleInternal
} 检查xmlGetArr所有不允许为空的成员是否找到对应的路径
} 对于组装报文,我们也可以做类似优化:先将需要修改的xml报文路径及其对应的值缓存起来,
等到把xml报文值设置好后再统一遍历一次xml报文,根据缓存信息,进行实际的xml报文修改。
修改逻辑如下:对缓存以xml路径进行排序,排序后顺序处理。由于路径相似的xml路径排序后肯定在一起,
可以减少修改过程中对于xml报文的查找。(可以根据待修改值的数量决定是否对当前xml节点构建索引,为保持与之前代码兼容性,建索引时可以考虑使用归并排序,或者使用冒泡法即可) 相关函数设计:
XmlSetTextByPathCopy //xml报文值是存在临时变量,需要把值复制出来,暂存。
XmlSetTextByPathNoCopy //xml报文值是存在非临时变量
XmlSetTextByPathDual //真正对xml报文进行修改

  

libmxml数据结构(源码分析)的更多相关文章

  1. HDFS源码分析心跳汇报之数据结构初始化

    在<HDFS源码分析心跳汇报之整体结构>一文中,我们详细了解了HDFS中关于心跳的整体结构,知道了BlockPoolManager.BPOfferService和BPServiceActo ...

  2. 物联网安全himqtt防火墙数据结构之红黑树源码分析

    物联网安全himqtt防火墙数据结构之红黑树源码分析 随着5G的发展,物联网安全显得特别重要,himqtt是首款完整源码的高性能MQTT物联网防火墙 - MQTT Application FireWa ...

  3. Java ArrayList源码分析(有助于理解数据结构)

    arraylist源码分析 1.数组介绍 数组是数据结构中很基本的结构,很多编程语言都内置数组,类似于数据结构中的线性表 在java中当创建数组时会在内存中划分出一块连续的内存,然后当有数据进入的时候 ...

  4. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  5. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  6. ABP源码分析二十八:ABP.MemoryDB

    这个模块简单,且无实际作用.一般实际项目中都有用数据库做持久化,用了数据库就无法用这个MemoryDB 模块了.原因在于ABP限制了UnitOfWork的类型只能有一个(前文以作介绍),一般用了数据库 ...

  7. JDK1.8 HashMap 源码分析

    一.概述 以键值对的形式存储,是基于Map接口的实现,可以接收null的键值,不保证有序(比如插入顺序),存储着Entry(hash, key, value, next)对象. 二.示例 public ...

  8. Java并发包源码分析

    并发是一种能并行运行多个程序或并行运行一个程序中多个部分的能力.如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互性将大大改善.现代的PC都有多个CPU或一个CPU中有多个 ...

  9. spring源码分析之spring-core总结篇

    1.spring-core概览 spring-core是spring框架的基石,它为spring框架提供了基础的支持. spring-core从源码上看,分为6个package,分别是asm,cgli ...

  10. 【集合框架】JDK1.8源码分析之HashMap(一)

    一.前言 在分析jdk1.8后的HashMap源码时,发现网上好多分析都是基于之前的jdk,而Java8的HashMap对之前做了较大的优化,其中最重要的一个优化就是桶中的元素不再唯一按照链表组合,也 ...

随机推荐

  1. 有用的linux指令(资料转载)

    对 Linux 新手非常有用的20个命令                                                                    你打算从Windows换 ...

  2. 5.FileWriter 和 BufferWriter

    FileWriter 和 BufferWriter的使用场景  IO这块,各种Writer,Reader,让人眼晕 而在网上基本找不到在什么时候用哪个类,并且网上的IO demo 很多用法都是错的 在 ...

  3. selenium&phantomjs实战--漫话爬取

    为什么直接保存当前网页,而不是找到所有漫话链接,再有针对性的保存图片? 因为防盗链的原因,当直接保存漫话链接图片时,只能保存到防盗链的图片. #!/usr/bin/env python # _*_ c ...

  4. [C# | XML] XML 反序列化解析错误:<xml xmlns=''> was not expected. 附通用XML到类解析方法

    使用 XML 反化时出现错误: public static TResult GetObjectFromXml<TResult>(string xmlString) { TResult re ...

  5. 连接AWS Ubuntu服务器

    1.在AWS上创建了Ubuntu实例后,在实例里点连接.点使用PuTTY连接,下载PuTTY软件. 2.在所有程序里找到PuTTYgen并打开,点Load选择创建实例时的pem文件,点save pri ...

  6. Linux man 命令详细介绍

    知道linux帮助文件(man-pages,手册页)一般放在,$MANPATH/man 目录下面,而且按照领域与语言放到不同的目录里面. 看了上一章,要找那个命令使用相关手册,只要我们按照领域区分,到 ...

  7. [UI] 精美UI界面欣赏[11]

    精美UI界面欣赏[11]

  8. django 板块动态切换

    需求:在同一页面的不同板块上可以实现动态切换,使用一个view实现,具体如下图所示,点击phy显示物理机列表,点击vm显示虚机列表,phy.vm对应的url均是动态生成:               ...

  9. Git提交代码自动触发JenKins构建项目

    1.需求场景 用户提交代码后自动触发jenkins构建项目 流程图如下: 2.JenKins安装Gitlab Hook Plugin插件 3.JenKins配置 4.Gitlab Hook Plugi ...

  10. Linux su命令详解

    su switch user,用于切换用户用 su常见命令参数 用法:su [选项]... [-] [用户 [参数]... ] Change the effective user id and gro ...