踩坑记:Poco库,MySql,解析大文本的bug
这两天在调试一个小功能,使用c++,读取MySql。使用的是Poco库。按照官网的写法:
std::cout << "read normal data by poco recordset "<<std::endl;
Poco::Data::MySQL::Connector::registerConnector();
Poco::Data::Session session("MySQL", "host=127.0.0.1;port=3306;user=root;password=Citic@123;db=test");
Poco::Data::Statement select(session);
select << "select id,name from student ",now;//查询
Poco::Data::RecordSet rs(select);//创建结果集
bool more = rs.moveFirst();//第一条
while (more)
{
int id = rs[0].convert<int>();//读取id
std::string name = rs[1].convert<std::string>();//读取name
std::cout << "id=" << id << " name=" << name << std::endl;//输出
more = rs.moveNext();//下一条
}
代码没有问题,可以查询到数据,并输出:
换一张表,其中有一个字段是text,里面存储的数据比较大,4K个字符。结果就报错了。表结构如下:
查询代码:
std::cout << "read normal data by poco recordset " << std::endl;
Poco::Data::MySQL::Connector::registerConnector();
Poco::Data::Session session("MySQL", "host=127.0.0.1;port=3306;user=root;password=Citic@123;db=test");
Poco::Data::Statement select(session);
select << "select uid,data1 from test ", now;
Poco::Data::RecordSet rs(select);
bool more = rs.moveFirst();
while (more)
{
int id = rs[0].convert<int>();//读取id
std::string data = rs[1].convert<std::string>();//读取data
std::cout << "id=" << id << " data=" << data << std::endl;//输出
more = rs.moveNext();//下一条
}
两次代码一模一样,但是执行就报错了:
报错:ucrtbase.dll,处有未经处理的异常: 将一个无效参数传递给了将无效参数视为严重错误的函数。
这个dll属于系统C语言运行时。在网上搜相关的内容,基本上都是流读写或者关闭不正常导致。经在尝试各种方法以后,还是无法解决这个问题。
推测,应该是poco库本身在对mysql的大文本字段进行解析的时候,出bug了。只好另辟蹊径。偶然发现,RecordSet这个类,有一个copy(ostrem),大致意思是直接给recordset流拷贝一下。既然这样,那么就来尝试一下。
std::cout << "read big data by ofstream" << std::endl;
Poco::Data::MySQL::Connector::registerConnector();
Poco::Data::Session session("MySQL", "host=127.0.0.1;port=3306;user=root;password=Citic@123;db=test");
Poco::Data::Statement select(session);
select << "select uid,data1 from test ", now;
Poco::Data::RecordSet rs(select);//创建数据集,select对象给数据全部写入数据集
std::ofstream outFile("data.txt");//创建一个文件流
rs.copy(outFile);//流拷贝
outFile.flush();//强制写入
这个代码的意思,创建一个文件流,输出到test.txt。然后直接从recordset把流拷贝走。如果成功的话,这个文件里面应该是mysql查询到的数据。
执行结果如上图所示。实际上,这就是mysql返回的查询结果,rescordset里面存储的就是这个。recordset再提供一些方法去从这个数据里面解析成一个个值。到此基本上发现了问题,就是Poco自带的解析方法,再解析大量数据的时候,出问题了。
所以:不想重新编译Poco库,那就想办法自己解析。解析办法,就是按字符串,一行一行去解析。代母如下:
std::cout << "read big data by ostream" << std::endl;
Poco::Data::MySQL::Connector::registerConnector();
Poco::Data::Session session("MySQL", "host=127.0.0.1;port=3306;user=root;password=Citic@123;db=test");
Poco::Data::Statement select(session);
select << "select uid,data1 from test ", now;
Poco::Data::RecordSet rs(select);//创建数据集,select对象给数据全部写入数据集
std::stringstream stm;//定义一个用于接收recoredset的字符串流
rs.copy(stm);//结果集复制到字符串流
stm.flush();//强制读取完毕
char line[4500] = { 0 };//读取字符串流读取的缓存,要大于一行总字节长度
stm.getline(line, 4500);//跳过标题 uid , data1
stm.getline(line, 4500);//跳过分割 --------------------
int id = 0;//id变量
std::string data = "";//data变量
while (stm.getline(line, 4500))
{
sscanf(line, "%d ", &id);//解析id
data = std::string(line + 17);//解析长文本
std::cout << id << "-----" << data << std::endl;
}
运行结果,解析成功,并且不报错。
解析原理就是把recordset里面的流先复制到字符串流,然后再一行一行的读取。前两行是标题和分隔符,后面是数据,按照自己定义的数据规则去读取。
line+17:因为uid是一个int,读取出来,占了16个字节,前面留空。uid和data1列之间有一个空格,所以,data1数据开始的下标是17。
到此问题解决。