本文乃Siliphen原创,转载请注明出处:http://blog.csdn.net/stevenkylelee/article/details/38309147

  本文分为5小节,基本上就是我刚接触CSV文件到思考、实践做一个CSV解析器的过程的还原。

希望我的思路也能带领你一步步从浅到深认识CSV文件格式。

  1.简单的CSV解析器实现。

  2.简单实现的CSV解析器的问题

  3. CSV格式的定义

  4.用FSM(有限状态机)来做CSV格式解析。

  5.为什么使用CSV格式

  1.简单的CSV解析器实现。

  近期有一个需求,读取CSV格式的配置。

CSV是CommaSeparated Value(逗号分隔值)的缩写,通经常使用文本表示数据。

CSV格式数据的结构类似表格。不同的记录占用一行,一行中的字段用“,”(逗号)分隔,比如:

  名字,职业,工作经验(年),

  Siliphen Lee,软件project师(码畜),5

  Edison Chou,游戏server端主程,1

  Deson,钢琴教师兼游戏策划,1

  … …

  咋一看,CSV格式比較简单。

就是用行来分隔不同的记录。记录中用“,”逗号分隔不同的字段域。不过这样考虑的话,那么编写CSV解析器也非常easy了。

就是字符串的切割而已。

  好。以下来动手实现下这个思路。C#等语言的字符串都有Split函数,C++的标准库却连这个非经常常使用的函数都没,C++的标准库简直是弱爆了。而就算用了boost,由于boost接近std的风格,类似Split功能的函数,使用起来也比較麻烦。

  没办法,仅仅好自己编写Split来实现CSV的解析了。

对于CSV的解析功能。为了实现“组件化”,“复用”的理想,能够单独写一个Csv类,封装一些相关操作。以后在别的project项目中,也能够直接把这个类拿过去用。

  CSV类。三下五除二。就编写好了。代码例如以下:

  头文件

#pragma once

#include <vector>
#include <string>
using namespace std; class Csv
{
public:
Csv();
~Csv(); public: // 加载一个CSV文件
void Load(const string& strFileName); // 从字符串从解析
void Parse(const string& strText); public : /*
切割字符串
str 要分隔的字符串
seperator 分隔符
Ret 切割后的结果
*/
static void Split(const string &str, const string& seperator, vector< string >& Ret); /*
读取整个文件的数据
*/
static void ReadAll(const string& strFileName , string& Data ); public : vector< vector< string > >& GetGridData(){ return m_GridData; } private : // 原始表格数据
vector< vector< string > > m_GridData; };

  实现文件:

#include "Csv.h"
#include <stdio.h> Csv::Csv()
{
} Csv::~Csv()
{
} void Csv::ReadAll(const string& strFileName, string& Data)
{
// 读取文件数据
FILE* pFile = fopen(strFileName.c_str(), "rb");
if (pFile == 0)
{
return;
} fseek(pFile, 0, SEEK_END);
long len = ftell(pFile); char *pBuffer = new char[len + 1]; fseek(pFile, 0, SEEK_SET);
fread(pBuffer, 1, len, pFile);
fclose(pFile); pBuffer[len] = 0;
Data.assign(pBuffer, len ); delete[] pBuffer; } void Csv::Load(const string& strFileName)
{
string Data;
ReadAll(strFileName, Data);
Parse(Data );
} void Csv::Parse(const string& strText)
{
// 清除之前的数据
m_GridData.clear(); // 分出行,分出字段。 vector< string > ret;
Split(strText, "\r\n", ret);
for (size_t i = 0; i < ret.size(); ++i)
{
vector< string > Fields;
Split(ret[i], ",", Fields);
m_GridData.push_back(Fields);
} } void Csv::Split(const string &str, const string& seperator, vector< string >& Ret)
{
Ret.clear(); size_t nStartPosFound = str.find(seperator, 0);
size_t nFieldStart = 0;
for (; nStartPosFound != -1; nStartPosFound = str.find(seperator, nStartPosFound))
{
string strSub = str.substr(nFieldStart, nStartPosFound - nFieldStart);
nStartPosFound = nStartPosFound + seperator.size();
nFieldStart = nStartPosFound; Ret.push_back(strSub);
} // 增加最后一个字段
if (nFieldStart < str.size())
{
string strSub = str.substr(nFieldStart, str.size() - nFieldStart);
Ret.push_back(strSub);
} }

  对代码做一些简要说明。设计Csv类首先考虑的就是“独立性”。

Csv类不应该耦合(依赖)不论什么其它库,比方说:尽量避免使用Cocos2d-x。QT里面的函数。

Cocos2d-x有跨平台的文件读取方法。QT有字符串的split函数。假设用了这些库的现成机制,会导致类的通用性下降。比方,在还有一个非Cocos2d-x,非QT的项目中,就不能直接用了。

  相同地,从可移植性考虑。用VS编写读取文件,也不应该使用CreateFile, ReadFile等Win32 Api。fopen尽管不太好用,但因为其是C语言标准库的,移植性好,故用之。

  这里有一个问题须要注意下,用VS2013编辑和编译的话,可能会对fopen函数提示有错误。

例如以下:

  这句英文大概是说:“fopen函数或者变量可能是不安全的。考虑使用fopen_s替代。要屏蔽这个错误,请使用_CRT_SECURE_NO_WARNINGS。

  _CRT_SECURE_NO_WARNINGS如何用呢?简单,在“项目属性 -> 配置属性 -> C/C++ -> 命令行 -> 其它选择”上,加入“/D _CRT_SECURE_NO_WARNING”。例如以下图:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvU3RldmVuS3lsZUxlZQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

  加入完后,又一次编译。这样。我们的简单的CSV解析器就写好了。执行调试一下,看看是否执行正确

  OK,没问题。

解析的结果,我们放在一个vector< vector< string > > 类型的数据结构中。

这个数据结构比較灵活,能够模拟表示一个表格。

实际上,vector< vector< string >
> 也能够表示可动态调整大小的二维数组。

  2.简单实现的CSV解析器的问题

  之前用字符串处理方法split做的CSV解析器有问题吗?对于简单的数据,没问题。但细致想下就会发现,字段之间用“,”英文逗号分隔。假设字段数据本身包括了逗号,怎么办?我们用Excel做下实验。看看Excel导出的CSV格式表格是如何的。

  实验结果例如以下:

  从以上结果能够看到。对于包括了“,”字段分隔符的字段,是用“”””双引號把这个字段包围起来。

但假设字段数据本身也包括了“””引號,又怎么办呢?继续做实验。例如以下图

  实验发现,假设字段数据本身存在“””引號。那么引號的前面也会放一个引號,前置的引號相当于C语言的字符串的转义字符。

  假设用一个字段表示一篇文章,有分段换行。那又是一个什么情况?做实验看看,实验结果例如以下:

  复制一段文章或者是有段落的一段文本,然后粘贴到Excel的一个字段中,最后Excel另存为CSV格式文件。

用记事本打开那个CSV格式文件。发现用记事本看不到换行,这是什么情况?用UltraEdit查看下二进制数据

  发现,回车换行(/r/n),会被Excel替换成/n换行。

而用记事本程序打开是看不到/n换行效果的。

  假设用记事本程序编辑CSV文件。敲入回车换行(/r/n),然后用Excel打开,会如何?例如以下图:

  能够看到用记事本程序编辑CSV文件,人工按下回车键。会键入回车换行(/r/n)数据。用Excel打开的话,也显示了分行的效果。

  做了一些实验得出的结论是:CSV一个字段的数据是能够表示存在“,”字段分隔符的。

假设有逗号字段分隔符存在,那么整个字段就会用双引號包围起来。假设字段数据本身有引號,那么会在之前放一个前置的引號表示转义,而且该字段也会用双引號包围起来。

用双引號包围起来的字段数据里面。也能够有回车换行数据。

  这样。问题来了。我们用回车换行来切割出行,再用逗号切割出一行中不同的字段。

这样的简单的方法无法区分一个逗号是字段本身的数据,还是用来作为字段分隔符的。也无法区分一个回车换行。是字段里面的数据,还是用来分隔行的。

  3. CSV格式的定义

  上面是用Excel另存为CSV文件格式做实验来摸索CSV格式和一些显示特性。

事实上CSV是有格式规范的。

  关于CSV格式的定义,能够參考这里: 一篇百度文档的CSV格式定义
,IETF上的CSV格式定义的文档

  4.用FSM(有限状态机)来做CSV格式解析。

  了解CSV格式的定义后。我们知道。要写一个完好的CSV解析器,不能简单地用字符串的Split方法了。

  我们的目标是:写一个CSV类,能正确解析Excel导出的不论什么CSV文件!或许有人会觉得。用正則表達式能够搞定。

用正则有几个问题。第一,C++的正則表達式通常都依赖于一些第三方库,QT。Boost等。C++11的正则也不是在全部的编译器上都实现了,这非常大地影响了我们的CSV类通用性。第二,对于语法格式分析,正则不是万能的。至少文章作者本人是非常难写出能解析CSV的正则。

  这里介绍一个非常强大的方法:FSM(Finite State Machine,有限状态机)。我第一次接触FSM是在罗森(KennethH.Rosen)著的《离散数学及其应用》。

关于FSM的概念,能够百度下,看看百度百科的解释。

也能够看看维基百科的解释:http://zh.wikipedia.org/wiki/%E6%9C%89%E9%99%90%E7%8A%B6%E6%80%81%E6%9C%BA

  FSM的应用非常广。电路、游戏开发和编译原理等都会涉及。这里,我们就使用FSM来做CSV的解析。使用FSM通常是先画状态图,然后编码实现,调试,改动,重复这个过程。我画的CSV解析FSM状态图例如以下:

  从上图。能够看到FSM还能够做语法检查。实际上《编译原理》中的词法分析的扫描器。通常就是基于FSM。这个FSM要解析Excel导出的不论什么CSV格式文件应该是足够了。为了简化实现。不考虑一些语法容错。

  因为代码较多。

我就不贴完整实现代码了。

用我们之前实现的简单Csv类来改。就是砍掉Split函数,然后,重写Parse函数。

  大概步骤是:

  先定义状态

	// 定义状态
enum StateType
{
// 新字段開始
NewFieldStart , // 非引號字段
NonQuotesField , // 引號字段
QuotesField , // 字段分隔
FieldSeparator , // 引號字段中的引號
QuoteInQuotesField , // 行分隔符字符1,回车
RowSeparator , // 语法错误
Error ,
};

  然后。写一个for循环,从头到尾遍历CSV文件数据,把遍历到的字符“喂给”FSM。FSM用一个Switch-Case语句实现。遍历结束后。再推断下状态。做一些对应处理。

算法截图例如以下:

  做下測试,读取效果怎样。

先用Excel构造一个复杂的表格,字段里面包括“, 半角逗号”,““ 引號”。“\r\n 回车换行符”。例如以下所看到的:

  导出CSV文件。用Csv类读一下。结果例如以下图:

  OK。

大功告成!一个相对完好的Csv类完毕了!

我们但是用编译原理涉及的技术搞定的哦,赞!

  我的Csv类完整实现(包含整个VSproject和測试CSV文件)能够在这里下载到:http://download.csdn.net/detail/stevenkylelee/7697315

  假设发现我的类读取Excel导出的CSV文件有什么bug。请联系我。

我改!

:)

  5.为什么使用CSV格式



  我思考一段时间,总结的理由有例如以下几点:

  1.CSV文件格式占用空间比較小,是文本文件。

  2.CSV文件能够用记事本打开。编辑改动方便。同一时候也能够用Excel打开。

  3.游戏项目中,策划通常喜欢用Excel做数值和配置。Excel能够直接另存为CSV文件。

  4.配置也能够用xml。Excel相同能够导出xml文件格式。xml不错。但C++的标准库没有xml的读取方法。通常C++项目读xml须要依赖第三方库。比如:TinyXml之类的。

版权声明:本文博客原创文章,博客,未经同意,不得转载。

CSV文件格式分析器执行:从字符串Split至FSM的更多相关文章

  1. SQL 语句在查询分析器执行很快,程序 Dapper 参数化查询就很慢(parameter-sniffing)

    这个问题困扰我好长时间了,使用SQLSERVER 事务探查器找到执行超时的SQL语句,参数查询都是通过执行exe sp_executesql 的存储过程调用,因为它能够分析并缓存查询计划,从而优化查询 ...

  2. divmod(a,b)函数是实现a除以b,然后返回商与余数的元组、eval可以执行一个字符串形式的表达式、exec语句用来执行储存在字符串或文件中的Python语句

    #!/usr/bin/env python a = 10/3 print(a) #divmod计算商与余数 r = divmod(10001,20) print(r) #eval可以执行一个字符串形式 ...

  3. 几种c++字符串split 函数实现的比较

    文中的字符串split函数功能是 从字符串中按照特定的分隔符进行分割,分割的结果保存到std::vector中. 1. strtok实现 std::vector<std::string> ...

  4. Weka中数据挖掘与机器学习系列之数据格式ARFF和CSV文件格式之间的转换(五)

    不多说,直接上干货! Weka介绍: Weka是一个用Java编写的数据挖掘工具,能够运行在各种平台上.它不仅提供了可以直接用于数据挖掘的软件,还提供了src代码,使用者可以修改源代码,进行二次开发. ...

  5. python入门-python处理csv文件格式相关

    python入门-python处理csv文件格式相关 处理 下载的csv格式文件 直接上代码和效果图 import csv from datetime import datetime from mat ...

  6. c++字符串split 函数实现

    - 经常遇到字符串分割问题,但是相对于c++而言实现比较麻烦,直接遍历一遍也很冗余 - 另外也适用于,在字符串中找到某个字符的所有位置 //函数功能:将输入字符串s,以字符串c(;)进行拆分,拆分结果 ...

  7. csv文件格式

    弱渣今天第一次读Kaggle入门文章,知道train data,test data以及提供的result文件大都是以csv文件格式给出的. csv,全称 Comma-Separated Values, ...

  8. java 字符串split有很多坑,使用时请小心!!

    System.out.println(":ab:cd:ef::".split(":").length);//末尾分隔符全部忽略 System.out.print ...

  9. python分割字符串split,filter函数用法

    现有字符串,需要取出用空格分隔的第一段,操作如下 >>> product_model = ‘WS-C2960G-24TC-L – Fixed Module 0′>>> ...

随机推荐

  1. 关于产品的一些思考——腾讯之QQ音乐

    --------------------2014.5.11-------------------- 原来一直使用小米手机自带的音乐播放器,除了搜歌下载不方便,其它的还好(省电是最大的长处),用过Jin ...

  2. 【PHP】PHP5.4.0版本号ChangeLog具体解释(上)

    前言 随着大量的框架使用composer和namespace,渐渐的线上环境也从之前的5.3变成了5.4或者5.5甚至5.6,随着7月份PHP7的公布,会有很多其它的公司採用新版本号. 之前好久就想写 ...

  3. OPENVPN开启用户password认证

    一.服务端配置 1.改动openvpn的主配置文件,加入例如以下内容 [root@ttt openvpn]# cat /etc/openvpn/server.conf |more #########a ...

  4. IOS成长之路-Nsstring搜索方法rangeOfString

    NSString *str1 = @"can you \n speak English"; NSString *str = @"\n"; //在str1该字符串 ...

  5. C# Windows Phone 8 WP8 , 文字超连结到网页 免打程式码,Xaml就搞定 ! !

    原文:C# Windows Phone 8 WP8 , 文字超连结到网页 免打程式码,Xaml就搞定 ! ! 一般我们在开发Windows Phone 8 APP ,有时会需要超连结连到其他的网页,但 ...

  6. ubuntu12.04硬盘安装

    ubuntu12.04发布了 , 安装又是一个话题.安装系统有很多方法,比如livecd,和u盘,但这些都需借用外部设备,所以硬盘安装是最好不过的方法了.u盘,cd安装都非常的简 单,对于那些讨厌用光 ...

  7. 【原创】《算法导论》链表一章带星习题试解——附C语言实现

    原题: 双向链表中,需要三个基本数据,一个携带具体数据,一个携带指向上一环节的prev指针,一个携带指向下一环节的next指针.请改写双向链表,仅用一个指针np实现双向链表的功能.定义np为next ...

  8. AWR报告生成

    ORACLE数据库两个比較重要的问题查看报告:awrrpt.sql,ashrpt.sql 生成报告的脚本一般存放在例如以下路径: /home/TEST/db/tech_st/11.2.0/rdbms/ ...

  9. effective c++ 条款11 Handle assignment to self in operator=

    赋值给自己,听起来有些不可思议,但是却要引起重视,它很容易把自己隐藏起来. 例如 1 a[i]=a[j]; 如果 i, j的值一样? 2 *px=*py; 如果px py指向同一个object 3   ...

  10. 安装gcc 3.4

    安装   gcc 3.4 f**k,不是为了编译0.11内核.我才懒得鸟3.4的版本号 源代码编译被我实践--"不归路",各种报错,我起码不止是了4个版本号的gcc,各种不兼容.各 ...