C++ 判断txt文件编码格式以及读取txt文件

文章介绍了如何判断和处理TXT文件的编码问题,包括ANSI、UTF-8、Unicode和Unicodebigendian四种常见编码的识别规则。通过文件头和编码规则可以判断文件编码,然后使用适当的方法进行转码,以避免乱码。提供的代码示例展示了如何在C++中实现这一过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  读取txt文件时经常遇到乱码问题,因为txt的解码方式不正确,这就需要判断txt文件的编码方式。常见的txt文本编码格式有ANSI、UTF-8、Unicode、Unicode big endian,这四种编码就不作详细介绍了。判断编码格式可以以二进制方式读取txt文件的数据,通过文件头或者根据各编码格式的规则判断编码格式。相关知识可以参考下下面几篇文章:
1、ASCII码和ANSI码的区别
2、 unicode,ansi,utf-8,unicode big endian编码区别
3、详解“大端”模式和“小端”模式
4、 晰解读C语言中的比特序、字节序、位域、大小端

一、判断编码格式
  txt文件一般都有文件头(除ANSI),可以通过文件头判断编码格式。对于没有文件头的txt文件,可以通过编码规则来进行判断。
1、文件头
  UTF-8文件头是EF BB BF,Unicode文件头是FF FE,Unicode big endian文件头是FE FF。
2、编码规则
(1)ANSI(GBK):在简体中文Windows操作系统中,ANSI 编码代表 GBK 编码;在英文Windows操作系统中,ANSI 编码代表 ASCII编码;在繁体中文Windows操作系统中,ANSI编码代表Big5。ANSI用0-127范围的一个字节表示英文字符,值为ASCII码;用二个字节表示一个中文字符,第一个字节首位为1(即1xxxxxxx xxxxxxxx)。
(2)UTF-8:是一种变长的编码方式,1~4个字节表示一个字符。单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码,亦即是ASCII码。对于n字节的符号(1<n<5),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10,剩下的没有提及的二进制位,全部为这个符号的unicode码。
1个字节: 0xxxxxxx
2个字节: 110xxxxx 10xxxxxx
3个字节: 1110xxxx 10xxxxxx 10xxxxxx
4个字节: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
(3)Unicode:2个字节表示一个字符,数值为Unicode码的反序值(例:”严”的Unicode码为4E25,Unicode编码则为25 4E)
(4)Unicode big endian:2个字节表示一个字符,数值为Unicode码(例:”严”的Unicode码为4E25,Unicode big endian编码则为4E 25)
3、注意:
(1)通过位操作判断位值时,一定要先判断位序,再根据位序读取位置,否则读取所得位值数据不符合编码规则
(2)由于有些字符均满足UTF-8和GBK编码规则,即使限定4个连续字符满足UTF-8编码规则判为UTF-8格式,仍有可能有GBK格式被误判为UTF-8格式导致乱码。如果追求精准,可以去掉4个连续字符的限制,遍历所有数据,均满足UTF-8编码规则判为UTF-8格式。但即便如此还是有缺陷无法解决,当字符串过短时,仍有误判导致乱码的问题,如“力挺联通”就会误判编码格式导致乱码。但该方法已能满足日常txt的读取需求。

#include <bitset>
#define GetBit(v, n) ((v) & ((UINT32)1 << (n))) //获取v 的第 n 位
//判断是否是大端位序,例:1的二进制为00000001,大端位序显示为10000000,小端位序显示为00000001
boolean getIsbigendian_bit()
{
	char a = 1;
	if (GetBit(a, 0) >> 0)
		return true;
	return false;
}
//判断字符串编码格式
Encode getEncode(uint8_t* data, size_t size, int ChineseNumber=4)
{
	//根据文件头判断编码格式
	//在简体中文Windows操作系统中,ANSI 编码代表 GBK 编码;在英文Windows操作系统中,ANSI 编码代表 ASCII编码;在繁体中文Windows操作系统中,ANSI编码代表Big5。
	if (size > 2 && data[0] == 0xFF && data[1] == 0xFE)
		return Encode::UTF16_LE;
	else if (size > 2 && data[0] == 0xFE && data[1] == 0xFF)
		return Encode::UTF16_BE;
	else if (size > 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF)		
		return Encode::UTF8_BOM;
	else//根据编码规则判断编码格式
	{
		//判断位序大小端
		boolean isbigendian = getIsbigendian_bit();
		//无文件头根据编码规律来判断编码格式
		//UTF-8的编码规则很简单,只有二条:
		//1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF - 8编码和ASCII码是相同的。
		//2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
		//取第一个字节判断第一位是否为1,以及获取第一位为1时后面位连续为1的数量
		int byte = 0; 
		int utf8number = 0;
		//如果是大端位序,从右边开始读取位;如果是小端位序,从左边开始读位
		if (isbigendian)
		{
			for (int i = 0; i < size; i++)
			{
				if (GetBit(data[i], 7) >> 7 == 1)
				{
					//取一个字节判断第一位是否为1,以及获取第一位为1时后面位连续为1的数量					
					int byte = 0;
					for (int j = 7; j >= 0; j--)
					{
						if ((GetBit(data[i], j) >> j) == 1)
							byte++;
						else
							break;
					}
					//若byte等于0,则非中文,中文数量清零
					if (byte ==0)
						utf8number = 0;
					//如果该字节开头几位连续为1,且数量byte超过1,则判断d该自己后面byte-1个字节是否符合UTF-8编码规则;
					//但符合UTF-8编码规则的,也可能与GBK重合,需继续遍历后面作判断;
					//UTF-8编码规则来判断后面字节,若是字节数据是UTF-8格式,则都符合UTF-8编码规则;若字节数据是GBK格式,则一定会有不符合UTF-8编码规则的字节段
					//此方法下字节数据如果是GBK格式,则遍历到中文字符就会很快出现不符合UTF-8格式的现象;如果是UTF-8格式,则需遍历所有数据
					//可以限定四个连续字符满足UTF-8编码规则时,可判为UTF-8,这样就无需遍历所有数据,四个连续字符满足UTF-8编码规则和GBK编码规则的概率较少
					//若UTF-8与GBK重合的字符以UTF-8规则编译为中文,且连续为中文的数量为4(ChineseNumber)时,判为UTF-8,否则判为GBK
					//如果该字节开头连续为1的数量等于1,则是GBK编码方式
					if (byte > 1)
					{
						bitset <8> head_bit_match[] = { data[i], data[i + 1], data[i + 2], data[i + 3] };
						boolean isutf8 = true;
						for (int j = 1; j < byte; j++)
						{
							if (!(head_bit_match[j][7] == 1 && head_bit_match[j][6] == 0))
								isutf8 &= false;
						}
						if (isutf8)
						{
							utf8number++;
							i += (byte - 1);
							if (utf8number >= ChineseNumber)
								return Encode::UTF8;
						}
						else
							return Encode::ANSI;
					}
					else if (byte == 1)
						return Encode::ANSI;
				}
			}
		}
		else
		{
			for (int i = 0; i < size; i++)
			{
				if (GetBit(data[i], 0) >> 0 == 1)
				{
					//取一个字节判断第一位是否为1,以及获取第一位为1时后面位连续为1的数量	
					int byte = 0;
					for (int j = 0; j < 8; j++)
					{
						if ((GetBit(data[i], j) >> j) == 1)
							byte++;
						else						
							break;
					}
					//若byte等于0,则非中文,中文数量清零
					if (byte == 0)
						utf8number = 0;
					//如果该字节开头几位连续为1,且数量byte超过1,则判断d该自己后面byte-1个字节是否符合UTF-8编码规则;
					//但符合UTF-8编码规则的,也可能与GBK重合,需继续遍历后面作判断;
					//UTF-8编码规则来判断后面字节,若是字节数据是UTF-8格式,则都符合UTF-8编码规则;若字节数据是GBK格式,则一定会有不符合UTF-8编码规则的字节段
					//此方法下字节数据如果是GBK格式,则遍历到中文字符就会很快出现不符合UTF-8格式的现象;如果是UTF-8格式,则需遍历所有数据
					//可以限定四个连续字符满足UTF-8编码规则时,可判为UTF-8,这样就无需遍历所有数据,四个连续字符满足UTF-8编码规则和GBK编码规则的概率较少
					//若UTF-8与GBK重合的字符以UTF-8规则编译为中文,且连续为中文的数量为4(ChineseNumber)时,判为UTF-8,否则判为GBK
					//如果该字节开头连续为1的数量等于1,则是GBK编码方式
					if (byte > 1)
					{
						bitset <8> head_bit_match[] = { data[i], data[i + 1], data[i + 2], data[i + 3] };
						boolean isutf8 = true;
						for (int j = 1; j < byte; j++)
						{
							if (!(head_bit_match[j][0] == 1 && head_bit_match[j][1] == 0))
								isutf8 &= false;
						}
						if (isutf8)
						{
							utf8number++;
							i += (byte - 1);
							if (utf8number >= ChineseNumber)
								return Encode::UTF8;
						}
						else
							return Encode::ANSI;
					}
					else if (byte == 1)
						return Encode::ANSI;
				}
			}
		}
		return Encode::ANSI;
	}		
}

二、读取txt文件
1、txt数据容器ArrayList
  由于UTF-8数据和ANSI数据字符的字节数不定,以二进制读取时分段解码需要判断字符完整以及缓存字符字节片段,比较麻烦,且txt文件不会很大,可以用一个容器将所有数据保存下来,可自定义一个简单的可变长数组的类来作容器。
ArrayList.h

#pragma once
#include "stdafx.h"
//可变长数组
template <class T>
class ArrayList
{
public:
	T* buf = NULL;
	int size = 0;
	int datasize = 0;
	ArrayList()
	{

	}
	ArrayList(int size)
	{
		buf =(T*) malloc(size);
		this->size = size;
	}
	~ArrayList()
	{
		if (buf != NULL)
			free(buf);
		size = 0;
		datasize = 0;
		buf == NULL;
			
	}	
	void add(T*buf,int size)
	{
		//数组没有分配内存时,分配内存并复制buf数据,否则直接将buf数据复制到数组中
		if (this->buf == NULL)
		{
			this->buf = (T*)malloc(size);
			this->size = size;
			this->datasize = size;
			memcpy(this->buf, buf, size);
		}
		else
		{
			//数组内存充足时,直接将buf数据复制到数组中,否则先重新分配内存,再将数据迁移过去并将buf数据复制到数组中
			if (this->datasize + size <= this->size)
			{
				memcpy(this->buf + this->datasize, buf, size);
				this->datasize += size;
			}
			else
			{
				int addsize = this->datasize + size - this->size;
				T* temp = (T*)malloc(this->size + addsize);
				memcpy(temp, this->buf, this->datasize);
				memcpy(temp + this->datasize, buf, size);
				free(this->buf);
				this->buf = temp;
				this->size += addsize;
				this->datasize += size;
			}						
		}
		
	}
};

2、读取txt文件以及转码
  判断Unicode、Unicode big endian时,需要先判断字节序,再转码,否则会出现乱码。TypeFormat.h是一个封装好的数据类型转换类,代码中使用到的函数网上搜下类型转换方法替换掉就行。

#include "ArrayList.h"
#include "TypeFormat.h"
enum Encode { ANSI = 1, UTF16_LE, UTF16_BE, UTF8_BOM, UTF8 };
//判断是否是大端字节序,例:大端位序时,wchar_t数据类型1的二进制为00000000 10000000,大端字节序显示为00000000 10000000,小端字节序显示为10000000 00000000
boolean getIsbigendian_byte()
{
	wchar_t a = 1;
	char* s = (char*)&a;
	_cprintf("%x %x\n", s[0], s[1]);
	if (s[0] == 1)
		return false;
	return true;
}
//Unicode big endian 和Unicode little endian互转
wchar_t * Unicode_BL_ByteReverse(char* str, int size, boolean isBOM = true)
{
	if (size % 2 == 0)
	{
		char temp;
		int start = 0;
		if (isBOM)
			start = 2;
		for (int i = start; i < size; i += 2)
		{
			temp = str[i+1];
			str[i + 1] = str[i];
			str[i] = temp;
		}
		return (wchar_t *)str;
	}
	else
		return NULL;
}
//对字符串进行转码
wchar_t* Convert(char* str, int targetCodePage)
{	
	int iunicodeLen = MultiByteToWideChar(targetCodePage, 0, str, -1, NULL, 0);
	wchar_t *pUnicode = NULL;
	pUnicode = new wchar_t[iunicodeLen + 1];
	memset(pUnicode, 0, (iunicodeLen + 1)*sizeof(wchar_t));
	MultiByteToWideChar(targetCodePage, 0, str, -1, (LPWSTR)pUnicode, iunicodeLen);//映射一个字符串到一个款字节中
	return pUnicode;
}
//MFC窗口按钮单击事件响应函数
void CtestDlg::OnBnClickedButton1()
{
	CString file("test.txt");
	showdlg->novaltxt = NULL;
	char str[1024 * 10];
	char end_a = '\0';
	wchar_t end_u = '\0';
	memset(str, 0, 1024 * 10);
	FILE* f = fopen(typeformat.tochar(file), "rb");
	int size = filelength(fileno(f));
	ArrayList<char> arraylist(size);
	int red = 0;
	int end_red = 0;
	while (red = fread(str, 1, 1024 * 10, f))
	{
		end_red += red;
		arraylist.add(str, red);
		memset(str, 0, 1024 * 10);
	}
	fclose(f);

	//转码
	int encodelabel[] = { CP_ACP, CP_UTF8, CP_UTF7 };
	//判断字节序大小端
	boolean isBigendian_byte = getIsbigendian_byte();
	wchar_t* buf = NULL;
	switch (getEncode((uint8_t*)arraylist.buf, arraylist.size))
	{
	case ANSI:
		arraylist.add(&end_a, 1);//toCString时需要通过str里的'\0'判断字符串结束,否则转换出来的CString末端有野数据
		showdlg->novaltxt = typeformat.toCString(arraylist.buf);
		break;
	case UTF16_LE:
		arraylist.add((char*)&end_u, 2);//toCString时需要通过str里的'\0'判断字符串结束,否则转换出来的CString末端有野数据
		if (isBigendian_byte)
			buf = Unicode_BL_ByteReverse(arraylist.buf, arraylist.size);
		else
			buf = (wchar_t *)arraylist.buf;
		showdlg->novaltxt = typeformat.toCString(buf);
		break;
	case UTF16_BE:
		arraylist.add((char*)&end_u, 2);//toCString时需要通过str里的'\0'判断字符串结束,否则转换出来的CString末端有野数据
		if (isBigendian_byte)
			buf = (wchar_t *)arraylist.buf;
		else
			buf = Unicode_BL_ByteReverse(arraylist.buf, arraylist.size);
		showdlg->novaltxt = typeformat.toCString(buf);
		break;
	case UTF8_BOM://有文件头utf-8
		arraylist.add(&end_a, 1);//toCString时需要通过str里的'\0'判断字符串结束,否则转换出来的CString末端有野数据
		buf = Convert(arraylist.buf, encodelabel[1]);
		showdlg->novaltxt = typeformat.toCString(buf);
		break;
	case UTF8://无文件头utf-8
		arraylist.add(&end_a, 1);//toCString时需要通过str里的'\0'判断字符串结束,否则转换出来的CString末端有野数据
		buf = Convert(arraylist.buf, encodelabel[1]);
		showdlg->novaltxt = typeformat.toCString(buf);
		break;
	default:
		break;
	}
}
<think>嗯,用户想用C++修改txt文件编码格式。这个问题需要分步骤解决。首先,我需要理解如何检测文件的当前编码,然后进行转换。根据用户提供的引用,特别是引用3,Qt的方法是通过检查头部字节和处理转换状态来判断编码。例如,UTF-8 BOM有固定的EF BB BF,而ANSI可能需要用QTextCodec来检测。 接下来,用户可能需要将文件从一种编码转换为另一种,比如从ANSI转UTF-8,或者反过来。C++标准库本身可能不直接支持编码转换,所以可能需要使用第三方库,比如iconv或者ICU。引用2提到了一个第三方库uchardet来检测编码,这可能有帮助。 用户的问题是如何修改编码,所以步骤应该是:读取文件,检测编码,然后以目标编码写入新文件。需要注意的是,转换过程中可能会遇到字符无法表示的问题,比如ANSI到UTF-8时某些字符丢失。 另外,用户提到的DevC++的问题,引用1中用户通过记事本转换编码,这可能是一个手动方法,但用户希望用C++程序自动化这个过程。所以需要代码示例,比如使用Qt库或者iconv库来实现。 还需要考虑不同平台的处理方式,比如Windows和Linux。引用2和3分别提到了Linux和Qt的方法,可能需要跨平台的解决方案。 总结一下,步骤是:检测原文件编码,读取内容,转换编码,写入新文件。需要选择合适的库来处理编码检测和转换,比如uchardet检测,iconv进行转换,或者使用Qt的QTextCodec类。 最后,代码示例部分需要展示如何用C++实现这些步骤,可能需要分平台或者依赖特定库。同时,提醒用户注意转换后的文件覆盖问题和编码兼容性。</think>### 分步解决方案:C++修改TXT文件编码格式 #### 一、编码转换核心步骤 1. **检测原文件编码** 使用第三方库(如`uchardet`或`Qt`内置方法判断文件编码格式 示例方法: ```cpp // 使用uchardet库检测编码 uchardet_t handle = uchardet_new(); FILE* fp = fopen("input.txt", "rb"); char buffer[4096]; size_t len; while ((len = fread(buffer, 1, sizeof(buffer), fp)) > 0) { uchardet_handle_data(handle, buffer, len); } fclose(fp); const char* charset = uchardet_get_charset(handle); ``` 2. **读取文件内容** 根据检测到的编码格式使用对应方式读取 ```cpp // Qt示例:按编码读取文件 QFile file("input.txt"); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return; QTextStream in(&file); in.setCodec(charset); // 如"UTF-8"、"GBK" QString content = in.readAll(); file.close(); ``` 3. **转换编码格式** 使用编码转换库实现核心转码 ```cpp // 使用iconv库转换编码 iconv_t cd = iconv_open("UTF-8//IGNORE", "GBK"); char* inbuf = (char*)content.data(); size_t inbytesleft = content.size(); char outbuf[4096]; size_t outbytesleft = sizeof(outbuf); char* outptr = outbuf; iconv(cd, &inbuf, &inbytesleft, &outptr, &outbytesleft); ``` 4. **写入新编码文件** 指定目标编码格式写入文件 ```cpp // Qt示例:以UTF-8带BOM格式保存 QFile output("output.txt"); if (output.open(QIODevice::WriteOnly)) { QTextStream out(&output); out.setCodec("UTF-8"); out.setGenerateByteOrderMark(true); out << convertedContent; } ``` #### 二、完整代码示例(基于Qt框架) ```cpp #include <QFile> #include <QTextStream> #include <uchardet.h> void convertEncoding(QString srcPath, QString destPath, const char* destEncoding) { // 检测源文件编码 uchardet_t detector = uchardet_new(); QFile srcFile(srcPath); if (srcFile.open(QIODevice::ReadOnly)) { QByteArray data = srcFile.readAll(); uchardet_handle_data(detector, data.constData(), data.size()); uchardet_data_end(detector); const char* srcEncoding = uchardet_get_charset(detector); // 读取并转换 QTextStream in(&data); in.setCodec(srcEncoding); QString content = in.readAll(); // 写入目标编码 QFile destFile(destPath); if (destFile.open(QIODevice::WriteOnly)) { QTextStream out(&destFile); out.setCodec(destEncoding); if (qstrcmp(destEncoding, "UTF-8") == 0) { out.setGenerateByteOrderMark(true); } out << content; } uchardet_delete(detector); } } ``` #### 三、注意事项 1. **编码兼容性** - UTF-8可包含所有Unicode字符 - GBK/ANSI可能丢失特殊符号 - 转换前建议备份原文件 2. **错误处理** - 添加`iconv_open()`返回值检查 - 处理非法字符(如使用`//IGNORE`参数) - 检测文件是否存在和读写权限 3. **性能优化** - 大文件建议分块处理 - 使用内存映射文件加速IO
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

hzl2323406

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

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

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

打赏作者

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

抵扣说明:

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

余额充值