2012-07-14 wcdj
问题描述:
在做字符串处理时,若缓冲区的长度固定,在向缓冲区copy字符串时会出现被截断的情况。此处考虑被copy的字符串使用GBK编码并含有汉字,若出现截断则缓冲区的内容结尾可能会出现乱码。此问题会引发向DB导入数据发生异常等其他一些问题。
解决方法:
首先要了解出现这个问题的原因,即字符集编码的问题。下面是关于GBK和UTF-8的介绍:
GBK http://baike.baidu.com/view/25421.htm
UTF-8 http://baike.baidu.com/view/25412.htm
Unicode http://baike.baidu.com/view/40801.htm
需要知道:
(1) GBK采用双字节表示,总体编码范围为8140-FEFE,首字节在81-FE 之间,尾字节在40-FE 之间。GBK的中文编码是双字节来表示的,英文编码是用ASCII码表示的,既用单字节表示。但GBK编码表中也有英文字符的双字节表示形式,所以英文字母可以有两种GBK表示方式。为区分中文,将其最高位都定成1。英文单字节最高位都为0。当用GBK解码时,若高字节最高位为0,则用ASCII码表解码;若高字节最高位为1,则用GBK编码表解码。
(2) UTF-8是UNICODE的一种变长字符编码又称万国码,它用1到6个字节编码UNICODE字符。
(3) Unicode是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。Unicode用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符,或者说有1114112个码位。码位就是可以分配给字符的数字。UTF-8、UTF-16、UTF-32都是将数字转换到程序数据的编码方案。
在Linux下如何查看文件的编码方式:
方法1:使用 vi 编辑器
我们通常是在Windows下写好代码然后再将文件上传到Linux下,那么可能会遇到文件编码转换的问题。Windows默认的文件格式是GBK(gb2312)编码,而Linux一般都是UTF-8编码。
在Linux下可以通过vi编辑器来查看文件的编码以及对文件进行编码转换:
在vi中可以直接查看文件编码:
:se fileencoding
在vi中可以直接转换文件编码:
:se fileencoding=utf-8
:se fileencoding=gbk
本文使用gbk的编码格式。
PS:
vi中有四个跟字符编码方式有关的选项:
(1) encoding vi内部使用的字符编码方式
(2) fileencoding vi中当前编辑的文件的字符编码方式
(3) fileencodings vi自动探测fileencoding的顺序列表
(4) termencoding vi所工作的终端的字符编码方式
这些选项可能的取值请参考vi在线帮助 :help encoding-names
在vi中可以通过以下两个命令查看字符的十六进制来确认当前的编码:
(1) ga
显示光标下字符的ASCII数值,十六进制,八进制数值。
(2) :%!xxd
用十六进制方式显示和编辑文件。
例如:
:%!xxd 整个文件以十六进制方式显示
:3!xxd 文件中第3行以十六进制方式显示
:%!od 隐藏右侧的文本列内容
注意:编辑完毕后,要使用命令 !xxd -r 将修改后的十六进制内容转换回来,否则修改后的十六进制的内容将被当作普通文本对待。
方法2:使用 od 命令
cat file | od -x
若使用不同的文件编码,可以查看文件实际存储数据是不同的。
方法3:使用 xxd 命令
xxd file | less方法4:使用 hexdump 命令
hexdump -C file | less测试代码
/*
* 防止串尾汉字(GBK)出现乱码
* gerryyang
* 2012-07-14
*/
#include <stdio.h>
#include <string.h>
#include <string>
using std::string;
/*
* 功能:计算GBK中文串合理被截断的长度以防止串尾汉字出现乱码
* @para s: 含有GBK编码字符串的头指针
* @para iLeft: 函数外部能够使用的缓冲区大小
* @para ret: 函数返回字符串s合理被截取的长度
*/
int GbkSubString(const char *s, int iLeft)
{
int len = 0, i = 0;
if( s == NULL || *s == 0 || iLeft <= 0 )
return(0);
while( *s )
{
if( (*s & 0x80) == 0 )
{
i ++;
s ++;
len ++;
}
else
{
if( *(s + 1) == 0 ) break;
i += 2;
s += 2;
len += 2;
}
if( i == iLeft ) break;
else if( i > iLeft )
{
len -= 2;
break;
}
}
return(len);
}
int main(int argc, char **argv)
{
char szBuf[10] = {0};
string str = "123abc你好";
/*
* [1] 未处理中文截断的情况
*/
snprintf(szBuf, sizeof(szBuf), "%s", str.c_str());
printf("szBuf: %s\n", szBuf);
/*
* [2] 处理中文可能被截断的情况
*/
memset(szBuf, 0x0, sizeof(szBuf));
int iBufLeftLen = sizeof(szBuf)-1;
// 计算合法的长度为iGbkValidLen
int iGbkValidLen = GbkSubString(str.c_str(), iBufLeftLen);
puts("");
printf("str被合理截取的长度为: %d\n", iGbkValidLen);
snprintf(szBuf, iGbkValidLen + 1, "%s", str.c_str());
printf("szBuf: %s\n", szBuf);
return 0;
}
/*
g++ -Wall -g -o test code_test.c
output:
szBuf: 123abc你?
str被合理截取的长度为: 8
szBuf: 123abc你
*/
更多参考:
[1] Linux下文件中文乱码的一些情况
[2] vim encoding and font
[3] vim 字符编码设置
[4] 谈谈Unicode编码,简要解释UCS、UTF、BMP、BOM等名词
http://blog.csdn.net/fmddlmyy/article/details/372148
[5] 浅谈文字编码和 Unicode(上)
http://blog.csdn.net/fmddlmyy/article/details/1510189
[6] 浅谈文字编码和 Unicode(下)
http://blog.csdn.net/fmddlmyy/article/details/1510193
[7] 字符集GBK和UTF8的区别说明
http://space.itpub.net/55022/viewspace-713901
[8] 常见字符编码和编码头BOM
http://xouou.iteye.com/blog/1337417