[libxml2]_[中级]_[使用xmlReaderForIO读取大文件和出现崩溃的原因]

场景

  1. Windows , LinuxmacOS 上开发 C/C++ 版本 XML 处理程序时, 我们通常会使用 libxml2 库, 这个库可以跨平台, 而且在 *ux 下都会自带这个库. 但是使用这个库时为什么可以不调用 xmlInitParser() 初始化,是不是不需要调用?

  2. 我们应该如何使用这个库进行读取大的 XML 文件?

  3. 在使用某个 *Reader 的函数时怎么第二次调用就会崩溃?

图1:
在这里插入图片描述

说明

当前使用 libxml2 版本是 2.7.1.

  1. libxml2 支持以 DOM 方式和 SAX 方式读取 XML 文件. 当我们以 SAX 模式读取文件时,在 Windows 上由于不支持 UNICODE 路径,所以我们需要使用 xmlReaderForIO 函数,因为这个函数可以传递 FILE* 类型的指针,这样我们就可以通过 Windows 提供的 _wfopen 版本打开 UNICODE 路径的文件. 这个函数需要我们自定义读取文件的回调函数 xmlInputReadCallbackxmlInputCloseCallback,也就是代理 fread 读取文件函数和 fclose 关闭文件函数.
xmlTextReaderPtr XMLCALL
		xmlReaderForIO (xmlInputReadCallback ioread,
					 xmlInputCloseCallback ioclose,
					 void *ioctx,
					 const char *URL,
					 const char *encoding,
					 int options);
  1. 这个函数 xmlReaderForIO 在头文件 xmlreader.h 里. 它的读取性能非常高效,页不需要通过 fseek 进行文件指针移动, 一直是顺序读取文件,唯一的麻烦就是要自己处理 XML 的元素进入和离开的分析. 如果是复杂的树形结构,那么需要自己维护一个进入退出的标记。 我这里的项目例子只有两层。

  2. 通常情况下,我们使用 XML 文件保存文本类型的数据。

例子

  1. 如果我们调用 TestLibxml2(2); 开启两个线程时,没有调用 xmlInitParser() 或者 LIBXML_TEST_VERSION 初始化,就会崩溃.

图2:
在这里插入图片描述

info_reader.h

#pragma once

#include <string>
#include <vector>
#include <memory>

using namespace std;

class Info
{
public:
	Info() :_id(0), type(0) {}
	int64_t _id;
	string name;
	string location;
	string date;
	string duration;
	string number;
	int type;
	string typeStr;
};

class InfoReader
{
public:
	int ReadXml(vector<shared_ptr<Info>>* infos, const char* path);
};

info_reader.cpp

#include "info_reader.h"

#include "libxml/parser.h"
#include "libxml/tree.h"
#include "libxml/xmlreader.h"

static const char* kArrayElement = "ARRAY";
static const char* kInfoElement = "info";
static const char* kIdElement = "id";
static const char* kNameElement = "name";
static const char* kDurationElement = "duration";
static const char* kNumberElement = "number";
static const char* kLocationElement = "location";
static const char* kDateElement = "date";
static const char* kTypeElement = "type";
static const char* kTypeStrElement = "typeStr";

static const int kMaxSizeBuf = 256;

static int pXmlInputReadCallback(void * context, char * buffer, int len)
{
	FILE* file = (FILE*)context;
	int readed = fread(buffer, 1, len, file);
	return readed;
}

static int pXmlInputCloseCallback(void * context)
{
	FILE* file = (FILE*)context;
	return fclose(file);
}

static int ProcessNode(xmlTextReaderPtr reader, Info* last,char last_element[]) {
	const xmlChar *name, *value;

	name = xmlTextReaderConstName(reader);
	if (name == NULL)
		name = BAD_CAST "--";

	value = xmlTextReaderConstValue(reader);
	int type = xmlTextReaderNodeType(reader);
	int result = 0;
	switch (type)
	{
	case XML_READER_TYPE_TEXT:
	{
		if (!last || !value)
			break;

		if (!strcmp((const char*)last_element, kIdElement)) {
			last->_id = _atoi64((const char*)value);
		}else if (!strcmp((const char*)last_element, kNameElement)) {
			last->name = (const char*)value;
		}else if (!strcmp((const char*)last_element, kDurationElement)) {
			last->duration= (const char*)value;
		}else if (!strcmp((const char*)last_element, kDateElement)) {
			last->date = (const char*)value;
		}else if (!strcmp((const char*)last_element, kNumberElement)) {
			last->number = (const char*)value;
		}else if (!strcmp((const char*)last_element, kLocationElement)) {
			last->location = (const char*)value;
		}else if (!strcmp((const char*)last_element, kTypeElement)) {
			last->type = atoi((const char*)value);
		}else if (!strcmp((const char*)last_element, kTypeStrElement)) {
			last->typeStr = (const char*)value;
		}
		break;
	}
	case XML_READER_TYPE_ELEMENT:
	{
		strncpy(last_element, (const char*)name, kMaxSizeBuf);
		if (!strcmp((const char*)name, kInfoElement)) {
			result = 1;
		}
		break;
	}

	case XML_READER_TYPE_END_ELEMENT:
	{
		break;
	}
	}

	return result;
}

int InfoReader::ReadXml(vector<shared_ptr<Info>>* infos, const char* path)
{
	// 如果是 Windows 平台,需要unicode版本 _wfopen.这样才可以识别其他语言的路径.
	FILE* file = fopen(path, "rb");
	if (!file)
		return 0;

	xmlTextReaderPtr reader = xmlReaderForIO(pXmlInputReadCallback, pXmlInputCloseCallback, file,
		NULL, "UTF-8", XML_PARSE_RECOVER);
	if (!reader) {
		fclose(file);
		return 0;
	}

	Info* last = NULL;
	char last_element[kMaxSizeBuf] = { 0 };
	int index = 0;
	while (xmlTextReaderRead(reader) == 1) {
		int ret = ProcessNode(reader, last, last_element);
		if (ret) {
			last = new Info();
			infos->push_back(shared_ptr<Info>(last));
			++index;
		}
	}

	xmlTextReaderClose(reader);
	return index;
}

test-libxml2.cpp

// test-libxml2.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <vector>
#include <thread>
#include "info_reader.h"
#include "libxml/parser.h"

using namespace std;

template<typename T>
static void PRINT(const char* key, T value) 
{
	std::cout << key << " -> " << value << std::endl;
}

static void ReadXmlFunc() 
{
	vector<shared_ptr<Info>> infos;
	InfoReader ir;
	int size = ir.ReadXml(&infos, "..\\..\\test.xml");
	PRINT("Read Size", size);

	for (auto& info : infos) {
		PRINT("=================================", "");
		PRINT("name", info->name);
		PRINT("location", info->location);
		PRINT("number", info->number);
		PRINT("date", info->date);
		PRINT("duration", info->duration);
		PRINT("id", info->_id);
		PRINT("type", info->typeStr);
	}
}

static void TestLibxml2(int count)
{
	PRINT("=================== TestLibxml2 =================", "");
	vector<thread> vts;
	vts.reserve(count);

	for (size_t i = 0; i < count; i++){
		thread t1;
		vts.push_back(thread(ReadXmlFunc));
	}
	
	for (auto& one : vts) 
		one.join();
	
}

int main()
{
	// 应该在主线程初始化.否则在工作线程 xmlCleanupParser 应该配套使用.
	xmlInitParser();
	PRINT("Hello World: libxml2 version", LIBXML_DOTTED_VERSION);
	TestLibxml2(1);
	xmlCleanupParser();

	// 以下会崩溃.
	// 如果没有调用 xmlInitParser初始化,开启多线程使用 xmlReaderForIO 会崩溃.
	//TestLibxml2(2);
}


test.xml

<?xml version="1.0" encoding="UTF-8"?>
<ARRAY><info>
    <id>465</id>
    <type>2</type>
    <typeStr>Outgoing</typeStr>
    <date>2018-04-13 14:21:12</date>
    <duration>00:00</duration>
    <name>first mid last</name>
    <location>shenzhen</location>
    <number>1234234</number>
  </info><info>
    <id>466</id>
    <type>2</type>
    <typeStr>Outgoing</typeStr>
    <date>2018-04-13 14:21:07</date>
    <duration>00:00</duration>
    <name>first mid last</name>
    <location></location>
    <number>1234234</number>
  </info><info>
    <id>467</id>
    <type>2</type>
    <typeStr>Outgoing</typeStr>
    <date>2018-04-13 14:21:03</date>
    <duration>00:00</duration>
    <name>Directory Assistance</name>
    <location></location>
    <number>411</number>
  </info>
</ARRAY>

输出

Hello World: libxml2 version -> 2.7.1
=================== TestLibxml2 ================= ->
Read Size -> 3
================================= ->
name -> first mid last
location -> shenzhen
number -> 1234234
date -> 2018-04-13 14:21:12
duration -> 00:00
id -> 465
type -> Outgoing
================================= ->
name -> first mid last
location ->
number -> 1234234
date -> 2018-04-13 14:21:07
duration -> 00:00
id -> 466
type -> Outgoing
================================= ->
name -> Directory Assistance
location ->
number -> 411
date -> 2018-04-13 14:21:03
duration -> 00:00
id -> 467
type -> Outgoing

下载

https://download.csdn.net/download/infoworld/12326207

参考

reader1.c

libxml2

libxml2 sample

使用WTL进行Windows桌面应用开发

LibXML can’t be initialized twice

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Peter(阿斯拉达)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值