10-标准编码文件的读写
7.2.3 标准编码文件的读写
1.保存为dat文件
前面是采用Qt预定义编码读写stm文件,这种方法使用简单,但是文件的格式不完全透明,不能创建用于交换的通用格式文件。
创建通用格式文件(即文件格式完全透明,每个字节都有具体的定义,如SEG-Y文件)的方法是以标准编码方式创建文件,使文件的每个字节都有具体的定义。用户在读取这种文件时,按照文件格式定义读取出每个字节数据并做解析即可,不管使用什么编程语言都可以编写读写文件的程序。
主窗口工具栏上的“保存dat文件”按钮将表格中的数据保存为标准编码的文件,文件后缀是“.dat”。保存dat文件的代码是:
void MainWindow::on_actSaveBin_triggered()
{//保存dat文件
QString curPath=QDir::currentPath();
QString aFileName=QFileDialog::getSaveFileName(this,"选择保存文件",
curPath, "标准编码数据文件(*.dat)");
if (aFileName.isEmpty())
return;
if (saveBinaryFile(aFileName))
QMessageBox::information(this,"提示消息","文件已经成功保存!");
}
bool MainWindow::saveBinaryFile(QString &aFileName)
{ //保存为dat文件
QFile aFile(aFileName);
if (!(aFile.open(QIODevice::WriteOnly)))
return false;
QDataStream aStream(&aFile);
aStream.setByteOrder(QDataStream::LittleEndian);//小端字节序
qint16 rowCount=theModel->rowCount();
qint16 colCount=theModel->columnCount();
aStream.writeRawData((char *)&rowCount,sizeof(qint16)); //写入文件流
aStream.writeRawData((char *)&colCount,sizeof(qint16));//写入文件流
//获取表头文字
QByteArray btArray;
QStandardItem *aItem;
for (int i=0;i<theModel->columnCount();i++)
{
aItem=theModel->horizontalHeaderItem(i); //获取表头item
QString str=aItem->text(); //获取表头文字
btArray=str.toUtf8(); //转换为字符数组
aStream.writeBytes(btArray,btArray.length());//写入字符串数据
}
//获取表格数据区
qint8 yes=1,no=0; //分别代表逻辑值 true和false
for (int i=0;i<theModel->rowCount();i++)
{
aItem=theModel->item(i,0); //测深
qint16 ceShen=aItem->data(Qt::DisplayRole).toInt();
aStream.writeRawData((char *)&ceShen,sizeof(qint16));
aItem=theModel->item(i,1); //垂深
qreal chuiShen=aItem->data(Qt::DisplayRole).toFloat();
aStream.writeRawData((char *)&chuiShen,sizeof(qreal));
aItem=theModel->item(i,2); //方位
qreal fangWei=aItem->data(Qt::DisplayRole).toFloat();
aStream.writeRawData((char *)&fangWei,sizeof(qreal));
aItem=theModel->item(i,3); //位移
qreal weiYi=aItem->data(Qt::DisplayRole).toFloat();
aStream.writeRawData((char *)&weiYi,sizeof(qreal));
aItem=theModel->item(i,4); //固井质量
QString zhiLiang=aItem->data(Qt::DisplayRole).toString();
btArray=zhiLiang.toUtf8();
aStream.writeBytes(btArray,btArray.length()); //写字符串数据
aItem=theModel->item(i,5); //测井取样
bool quYang=(aItem->checkState()==Qt::Checked);
if (quYang)
aStream.writeRawData((char *)&yes,sizeof(qint8));
else
aStream.writeRawData((char *)&no,sizeof(qint8));
}
aFile.close();
return true;
}
- 字节序
在保存为标准编码的二进制文件时,无须指定QDataStream的版本,因为不会用到Qt的类型预定义编码,文件的每个字节的意义都是用户自己定义的。但是如有必要,需要为文件指定字节顺序,如:
aStream.setByteOrder(QDataStream::LittleEndian);
字节顺序分为大端字节序和小端字节序,小端字节序指低字节数据存放在内存低地址处,高字节数据存放在内存高地址处;大端字节序则相反。
基于X86平台的计算机是小端字节序的,所以Windows系统是小端字节序,而有的嵌入式平台或工作站平台则是大端字节序的。读取一个文件时,首先需要知道它是以什么字节序存储的,这样才可以正确的读出。
setByteOrder()函数的参数是QDataStream::ByteOrder枚举类型常量,QDataStream::BigEndian是大端字节序,QDataStream::LittleEndian是小端字节序。
- writeRawData()函数
QdataStream采用函数writeRawData()将数据写入数据流,在保存qint8、qint16、qreal等类型的数据时都使用这个函数,其函数原型是:
int QDataStream::writeRawData(const char *s, int len)
其中参数s是一个指向字节型数据的指针,len是字节数据的长度。调用writeRawData()函数将会向文件流连续写入len个字节的数据,这些字节数据保存在指针s指向的起始地址里。例如,将qint16类型变量rowCount写入文件的语句是:
qint16 rowCount=theModel->rowCount();
aStream.writeRawData((char *)&rowCount,sizeof(qint16));
- writeBytes()函数
在将字符串数据写入文件时,使用的是writeBytes()函数,而不是writeRawData()。下面是writeBytes()函数的原型定义:
QDataStream &QDataStream::writeBytes(const char *s, uint len)
其中参数s是一个指向字节型数据的指针,len是字节数据的长度。writeBytes()在写入数据时,会先将len作为一个quint32类型写入数据流,然后再写入len个从指针s获取的数据。
writeBytes()适合于写入字符串数据,因为在写入字符串之前要先写入字符串的长度,这样在读取文件时,就能知道字符串的长度,以便正确读出字符串。
例如,下面的代码将字符串“Depth”写入文件流:
QString str=”Depth”;
QByteArray btArray=str.toUtf8();
aStream.writeBytes(btArray,btArray.length());
文件中实际保存的内容见表7-2。前4个字节是quint32类型的整数,表示保存数据的字节个数,这里是5,表示后续有5个字节数据。从第5字节开始,是保存的字符串”Depth”的每个字符的ASCII码。
| 字节序号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | | :----- | :----- | :----- | :----- | :----- | :----- | :----- | :----- | :----- | :----- | :----- | :----- | | 字节数据(Hex) | 05 | 00 | 00 | 00 | 44 | 65 | 70 | 74 | 68 | | 内容 | 后续数据长度,表示有5字节 | | | | D | e | p | t | h |
由于写入文件的字符串的长度一般是不固定的,因此如果以writeRawData()函数写入文件,只会写入字符串的内容,而没有表示字符串的长度。在文件读出时,如果不已知字符串长度,则难以正确读出字符串内容。而writeBytes()函数首先写入了字符串的长度,在读取文件时,先从前四个字节读出字符串长度,知道数据有多少个字节就可以正确读出了。
QDataStream提供了与writeBytes()对应的函数readBytes(),它可以自动读取长度和内容,适用于字符串数据的读取。
2.dat文件格式
用saveBinaryFile()函数保存数据为标准编码二进制文件,文件后缀为“.dat”。根据saveBinaryFile()函数的内容,dat文件的格式见表7-3。
| 顺序号 | 数据 | 类型 | 字节数 | 备注 | | :----- | :----- | :----- | :----- | :----- | :----- | :----- | | 1 | rowCount | qint16 | 2 | 行数 | | 2 | colCount | qint16 | 2 | 列数 | | 3 | “Depth” | QString | 4+5 | 表头标题1 | | 4 | "Measured Depth" | QString | 4+14 | 表头标题2 | | 5 | "Direction" | QString | 4+9 | 表头标题3 | | 6 | "Offset" | QString | 4+6 | 表头标题4 | | 7 | "Quality" | QString | 4+7 | 表头标题5 | | 8 | "Sampled" | QString | 4+7 | 表头标题6 | | 9 | 第1行各列数据 | qint16 | 2 | 测深 | | 10 | | qreal | 8 | 垂深 | | 11 | | qreal | 8 | 方位 | | 12 | | qreal | 8 | 位移 | | 13 | | QString | 4+字符串字节数 | 固井质量字符串 | | 14 | | qint8 | 1 | 是否测井取样 | | 15 | 第2行各列数据 | | ... |
在表7-3中,可以看到文件内的每个字节都是有具体定义的,这样,无论用什么语言编写一个文件读取的程序,只要按照这个格式来读取,都可以正确读出文件内容。
dat文件的数据是否是按照表7-3所示的顺序存储的呢?可以创建一个简单的数据表格,保存为dat后缀的文件,然后用显示文件二进制内容的软件来查看,如UltraEdit或WinHex,这些软件在分析文件格式,编写文件读写程序时特别有用。
3.读取dat文件
对于保存的dat文件,主窗口工具栏上的“打开dat文件”按钮可以打开保存的dat文件,下面是打开dat文件的函数openBinaryFile()的代码。
bool MainWindow::openBinaryFile(QString &aFileName)
{//打开dat文件
QFile aFile(aFileName);
if (!(aFile.open(QIODevice::ReadOnly)))
return false;
QDataStream aStream(&aFile);
aStream.setByteOrder(QDataStream::LittleEndian);
qint16 rowCount,colCount;
aStream.readRawData((char *)&rowCount, sizeof(qint16));
aStream.readRawData((char *)&colCount, sizeof(qint16));
this->resetTable(rowCount);
//读取表头文字, 但是并不利用
char *buf;
uint strLen;
for (int i=0;iindex(i,0);
aItem=theModel->itemFromIndex(index);
aItem->setData(ceShen,Qt::DisplayRole);
aStream.readRawData((char *)&chuiShen, sizeof(qreal)); //垂深
index=theModel->index(i,1);
aItem=theModel->itemFromIndex(index);
aItem->setData(chuiShen,Qt::DisplayRole);
aStream.readRawData((char *)&fangWei, sizeof(qreal)); //方位
index=theModel->index(i,2);
aItem=theModel->itemFromIndex(index);
aItem->setData(fangWei,Qt::DisplayRole);
aStream.readRawData((char *)&weiYi, sizeof(qreal)); //位移
index=theModel->index(i,3);
aItem=theModel->itemFromIndex(index);
aItem->setData(weiYi,Qt::DisplayRole);
aStream.readBytes(buf,strLen);//固井质量
zhiLiang=QString::fromLocal8Bit(buf,strLen);
index=theModel->index(i,4);
aItem=theModel->itemFromIndex(index);
aItem->setData(zhiLiang,Qt::DisplayRole);
aStream.readRawData((char *)&quYang, sizeof(qint8)); //测井取样
index=theModel->index(i,5);
aItem=theModel->itemFromIndex(index);
if (quYang==1)
aItem->setCheckState(Qt::Checked);
else
aItem->setCheckState(Qt::Unchecked);
}
aFile.close();
return true;
}
- 字节序
在流创建后,需要用setByteOrder()函数指定字节序,并且与写入文件时用的字节序一致。
- readRawData()函数
在读取基本类型数据时,使用QDataStream的readRawData()函数,该函数原型为:
int QDataStream::readRawData(char *s, int len)
它会读取len个字节的数据,并且保存到指针s指向的存储区。例如:
qint16 rowCount;
aStream.readRawData((char *)&rowCount, sizeof(qint16));
- readBytes()函数
读取字符串时使用readBytes()函数,它是与writeBytes()功能对应的函数,其函数原型为:
QDataStream &QDataStream::readBytes(char *&s, uint &len)
对应表格7-2,使用readBytes()函数时,会先自动读取前4个字节数据作为quint32的数据,并赋值给len参数,因为len是以引用方式传递的参数,所以,len返回读取的数据的字节数。然后根据len的大小读取相应字节的数据,存储到指针s指向的存储区。