文本文件与二进制文件区别

         最近因为工作需要,处理了大批的文本文件,其中发现了不少关于文本文件的处理问题,最主要的问题是,如何在文本中进行随机跳转读取记录,仔细的研究了几天,发现流文件处理隐藏的东西还真不少,这里就只谈一部分。

       文本文件与二进制文件是各种语言的标准读写模式,各种语言都提供了打开模式,例如C语言fopen就有b模式和t模式,所有的文件在磁盘上的存储都是以二进制的序列存储,为何还要区分去打开,直接用二进制去读取文本文件不可以吗?看一个例子就知道了。

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

//SRC CTest.cpp
const char* chSrcCpp = "CTest.cpp";
const char* test_ch = "#include <iostream>";

int main()
{
	string strline;
	
	cout << strlen(test_ch) << "\t" << (int)test_ch[strlen(test_ch) - 1] << endl;

	//r模式
	ifstream ifs(chSrcCpp);
	if (getline(ifs, strline))
	{
		cout << strline.size() << "\t" << (int)strline[strline.size() - 1]<< endl;
	}

	//b模式
	ifs.close();
	ifs.open(chSrcCpp, ios::binary | ios::in);
	if (getline(ifs, strline))
	{
		cout << strline.size() << "\t" << (int)strline[strline.size() - 1]<< endl;
	}

	return 0;
}

程序的功能会分别以r模式和b模式读取源文件本身,输出读取的数据的字符数并输出读取得最后一个字符。

Windows Server 2003

Red Hat Linux

       看到这个结果是不是在怀疑我的VC出问题了,Linux下的很正常(这个测试其实要分三种环境,由于没有Mac环境,所以没有Mac的测试,但是下面会给出实例)。

       一步一步分析,首先看最后一个字符,想一下,C++ getline函数就是读取一行的数据,遇到换行符就会停止,那最后一个字符就应该’>’,没错ASCII就是62,而在Windows环境下用b模式读取时最后一个字符是回车(ASCII CR=13),那按照getline规则下一个字符才是换行符,再加上读取的是20个字符,则可以得出Windows环境下文本多了一个字符。

       在此给大家推荐一个文本编辑器,可以替代简单的Notepad,Notepad++,有点C++的感觉,用Notepad++可以将Windows,Unix,Mac文本文件互相转换,而且可以显示所有不可见的字符。

Windows文本


Unix文本


Mac文本

各系统的换行处理

系统换行处理
Windows/r/n (回车+换行)
Unix/n (换行)
Mac/r (回车)

       系统这样换行方式,其实是表现在底层的,只有系统的API知道是这样的,看看Windows API 打开文件CreateFile函数根本没有说是以二进制还是文本模式的区分方式,所以例如C语言,它最后要打开文件还是要调用这个函数,但是为了跨平台就把换行操作统一化,你只要写入换行字符,由它来实现底层的转换。所以在Windows环境下,以文本模式打开时,写入字符时如果遇到换行符时,会先进行转换,变成回车加换行然后再写入磁盘,这样可以确保在文件被记事本类型的软件打开时可以显示出正确的换行操作,如果不进行此转换,那么记事本根本不知道在哪里换行,然而在以文本模式读的时候,也会进行相反的转换。

       这种转换操作各种语言标准库都有提供的,VC下中有源码,C语言的fgets,fput函数中可以看到添加,删除回车符的操作,不过前提是fopen选择文本模式打开。

       其实说了这么多二进制模式和文本模式的区别就在于换行符的处理,因为二进制是直接读取磁盘数据,不做任何处理,有什么就读什么,或者写什么。

       那么说了这些区别到底有什么作用呢,既然标准库做了操作,知道了似乎也没有多大作用,来看看实现一个功能,有一个股票记录文本文件,按行存储的记录,每条记录第一个字段是日期,比如我现在做的就是要查找某天的前后几天的记录,但是问题就是这些记录并不是每天都有,会断断续续,你必须先查找到指定的这一天,然后再往回查看前几天的记录,这里就涉及到一个往回读的问题,C语言的fseek函数就要你必须给出读指针移动的字节数,可以提前记录上一个已经读取的字节数,读取的数据加上换行需要的字节数就行了,所以你要清楚系统换行的字节数,那么这些会因为文件的不同换行类型而数据不一样。

       但是这似乎很麻烦,最后查看库函数,发现标准库提供了记录当前位置的操作,C语言的fgetpos,C++ stream的tellg都可以知道当前的位置,都可以用来记录文件位置,这样其实对于写出一个随机在文本中跳转到代码就简单多了,自己写了一个解析文本的类,可以设置各种记录分割标记,提取指定的记录,并且可以保存当前环境,进行其他操作,如果失败了,可以回退到保存的流状,这里就只贴类定义代码,源文件在资源里可以下载到。

#ifndef ___CANALYZ_TEXT_H___ 
#define ___CANALYZ_TEXT_H___

#pragma warning(disable:4786)

#include <fstream>
#include <vector>
#include <string>

class CAnalyzText {

public:
	CAnalyzText();
	~CAnalyzText();
public:
	//opem a new text
	bool OpenText(const char* chBufName, char* chSign = "", int nCnt = 0);
	
	//reopen a new text
	bool ReOpenText(const char* chBufName, char* chSign, int nCnt = 0);
	
	//close text
	void CloseText();

	//skip nCnt line from the current line pos
	bool SkipLine(int nCnt);

	//read cur record 
	bool ReadOneRecord();
	
	//read cur record by strSign
	bool ReadOneRecord(const std::string& strSign);

	//get current record field counts
	size_t GetCurFieldCnt();

	//get current record field vaule
	std::string GetCurSubRec(size_t nPos);
	
	//get current line pos
	size_t GetCurRecLinePos();
	
	//get the record counts of the file 
	size_t GetRecCnt();

	//save the environments
	void SaveEnv();
	
	//restore the environments
	void RestoreEnv();

	bool IsBegin();

	bool IsEnd();

private:
	//init member varibale
	void ReSetVariable();

	//split strLine by strSign and push back to the ve_strRec
	void SplitStrBySign(const std::string& strLine, 
		const std::string& strSign, std::vector<std::string>& ve_strRec);

private:
	//temp value to save environment

	size_t m_nTmpCurLinePos;
	size_t m_nTmpCurReadbyte;
	std::vector<std::string> m_ve_strTmpCurRec;

private:

	size_t m_nCurLinePos;					//当前的读取的数据是第几行的
	size_t m_nCurReadbyte;					//已经读取的字节数
	std::vector<std::string> m_ve_strCurRec;		//当前读取的已分割的记录数组

	std::ifstream m_ifsOut;					//文件流		
	size_t m_nStartLinePos;					//从第几行开始读
	size_t m_nTotalRecs;					//全部的记录数
	std::string m_strSign;					//分隔符
	std::vector<size_t> m_ve_sizeRetLinePos;	       //每一行所在文件流位置数组							
};
#endif

           最后,个人觉得数据还是以二进制读写要好一些,因为谁都不希望最后写的文件竟然被库函数修改过了,但是还有一个问题要注意就是内存对齐,必须确保文件二进制写和读内存对齐的格式是一样的,不然肯定也会出问题的。









  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值