1、场景
在使用read函数从一个非阻塞文件中读取数据,并显示在QTextEdit上,文件中存在中文字符,当文件数据较多时(场景中达到2kb左右),个别中文字符显示为乱码。
2、问题代码
QString read_data()
{
QString str;
int read_fd = ::open("test.txt", O_RDONLY| O_NONBLOCK);
if(read_fd == -1){
perror("open");
return str;
}
ssize_t byte = 0;
char buf[128] = {0};
while((byte = read(read_fd, buf, sizeof(buf) - 1)) > 0){
buf[byte] = '\0'; // 确保字符串正确终止
//使用fromUtf8处理buf中的数据
str.append(QString::fromUtf8(buf, byte));
}
close(read_fd);
return str;
}
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
QString text = read_data();
if(!text.isEmpty())
ui->textEdit->append(text);
}
运行结果:个别字符乱码
3、问题分析
上述代码通过在 buf[byte] 的位置设置 \0 来确保字符串的正确终止,并且使用 QString::fromUtf8(buf, byte) 来将读取的字节转换为 QString。但是,如果在读取的数据中包含多字节字符(如 UTF-8 编码的中文),数据可能不是按完整的 UTF-8 字符边界被读取,这些字符被分割在两个或更多的 read() 调用之间,那么可能会遇到乱码问题。UTF-8 编码的字符可能占用 1 到 4 个字节。如果 read() 在一个 UTF-8 字符的中间停止读取,那么下次 read() 调用将不会包含前一个字符的剩余部分,这会导致 QString::fromUtf8() 无法正确解码该字符。
4、解决方案
1)增大buf缓冲区大小
UTF-8 编码的一个特点是,ASCII 字符(即英文、数字、标点符号等)只占用一个字节,而多字节字符(如中文字符)可能占用 2 到 4 个字节。因此,如果一个中文字符被分割在两个 read() 调用之间,而你的缓冲区太小无法容纳这个字符的完整表示,那么在下一个 read() 调用时,之前的字符的剩余部分可能已经被新的数据覆盖,导致乱码。通过增加缓冲区的大小,减少了这种分割的可能性,尤其是在处理包含多字节字符的数据流时。但是,即使增加了缓冲区的大小,也不能保证每次 read() 调用都会返回完整的 UTF-8 字符序列,因为 read() 是基于字节的,而不是基于字符的。
char buf[512] = {0};
while((byte = read(read_fd, buf, sizeof(buf) - 1)) > 0){
buf[byte] = '\0'; // 确保字符串正确终止
//使用fromUtf8处理buf中的数据
str.append(QString::fromUtf8(buf, byte));
}
2)使用QByteArray累积数据
QString read_data()
{
QByteArray str;
int read_fd = ::open("test.txt", O_RDONLY| O_NONBLOCK);
if(read_fd == -1){
perror("open");
return str;
}
ssize_t byte = 0;
char buf[128] = {0};
while((byte = read(read_fd, buf, sizeof(buf) - 1)) > 0){
buf[byte] = '\0'; // 确保字符串正确终止
str.append(buf);
}
close(read_fd);
return QString::fromUtf8(str);
}
3)使用std::string累积数据
QString read_data()
{
std::string str;
int read_fd = ::open("test.txt", O_RDONLY| O_NONBLOCK);
if(read_fd == -1){
perror("open");
return "";
}
ssize_t byte = 0;
char buf[128] = {0};
while((byte = read(read_fd, buf, sizeof(buf) - 1)) > 0){
buf[byte] = '\0'; // 确保字符串正确终止
str.append(buf);
}
close(read_fd);
return QString::fromUtf8(str.c_str());
}
4)QStringj进行toAscii转换
QString read_data()
{
QString str;
int read_fd = ::open("test.txt", O_RDONLY| O_NONBLOCK);
if(read_fd == -1){
perror("open");
return "";
}
ssize_t byte = 0;
char buf[128] = {0};
while((byte = read(read_fd, buf, sizeof(buf) - 1)) > 0){
buf[byte] = '\0'; // 确保字符串正确终止
str.append(buf);
}
close(read_fd);
return QString::fromUtf8(str.toAscii());
}
因本例实际场景为管道通信部分摘录,故未使用QTextStream 。