数据流 QDataStream
数据流 QDataStream 用于直接读写二进制的数据和网络通信数据,二进制数据具体表的物理意义由读写方法以及后续的解码决定,数据流的读写与具体的操作系统无关,
用DataStream类创建数据流对象的方法如下所示它可以连接到继承自 QIODevice 的设各QByteArray 上。
from PySide6.QtCore import QDataStream
QDataStream(self)-> None
QDataStream(arg__1: PySide6.QtCore.QIODevice)-> None
QDataStream(arg__1: Union[PySide6.QtCore.QByteArray,bytes])-> None
QDataStream(arg__1: Union[PySide6.QtCore.QByteArray,bytes],flags: PySide6.QtCore.QIODeviceBase.OpenModeFlag)-> None
QDataStream说明
QDataStream类向QIODevice提供二进制数据的序列化。
数据流是编码信息的二进制流,它100%独立于主机的操作系统、CPU或字节顺序。例如,由PC在Windows下编写的数据流可以由运行Solaris的Sun SPARC读取。
您还可以使用数据流来读取/写入未编码的原始二进制数据。如果您想要一个"解析"输入流,请参阅QTextStream。
QDataStream类实现了C++基本数据类型的序列化,如char、short、int、char*等。更复杂数据的序列化是通过将数据分解为基元单元来实现的。
数据流与QIODevice密切协作。QIODevice代表一种可以从中读取数据和向中写入数据的输入/输出介质。QFile类是I/O设备的一个例子。
示例(将二进制数据写入流):
file = QFile("file.dat")
file.open(QIODevice.WriteOnly)
out = QDataStream(file)
out << QString("the answer is")# serialize a string
out <<(qint32)42 # serialize an integer
示例(从流中读取二进制数据):
file = QFile("file.dat")
file.open(QIODevice.ReadOnly)
in = QDataStream(file)
str = QString()
a = qint32()
in >> str >> a # extract "the answer is" and 42
写入流的每个项目都以预定义的二进制格式写入,该格式根据项目的类型而变化。支持的Qt类型包括QBrush、QColor、QDateTime、QFont、QPixmap、QString、QVariant和许多其他类型。有关所有支持数据流的Qt类型的完整列表,请参阅序列化Qt数据类型。
对于整数,最好始终强制转换为Qt整数类型进行写入,并读取回相同的Qt整数。这可以确保您获得所需大小的整数,并使您免受编译器和平台差异的影响。
枚举可以通过QDataStream进行序列化,而无需手动定义流运算符。枚举类使用声明的大小进行序列化。
举个例子,char字符串被写成一个32位整数,该整数等于包含"\0"字节的字符串的长度,后跟包含"\0"字节的所有字符串字符。读取char字符串时,读取4个字节以创建32位长度值,然后读取char*串的许多字符,包括"\0"终止符。
初始I/O设备通常在构造函数中设置,但可以使用setDevice()进行更改。如果您已经到达数据的末尾(或者如果没有I/O设备集),atEnd()将返回true。
版本控制
QDataStream的二进制格式自Qt1.0以来一直在发展,并且可能会继续发展以反映Qt中所做的更改。当输入或输出复杂类型时,确保读写使用相同版本的流(version())是非常重要的。如果您需要前向和后向兼容性,您可以在应用程序中对版本号进行硬编码:
stream.setVersion(QDataStream.Qt_4_0)
如果您正在生成一种新的二进制数据格式,例如应用程序创建的文档的文件格式,则可以使用QDataStream以可移植的格式写入数据。通常,你会写一个简短的标题,其中包含一个魔术字符串和一个版本号,为自己的未来扩展提供空间。例如:
file = QFile("file.xxx")
file.open(QIODevice.WriteOnly)
out = QDataStream(file)
# Write a header with a "magic number" and a version
out <<(quint32)0xA0B0C0D0
out <<(qint32)123
out.setVersion(QDataStream.Qt_4_0)
# Write the data
out << lots_of_interesting_data
然后阅读:
file = QFile("file.xxx")
file.open(QIODevice.ReadOnly)
in = QDataStream(file)
# Read and check the header
magic = quint32()
in >> magic
if magic != 0xA0B0C0D0:
return XXX_BAD_FILE_FORMAT
# Read the version
version = qint32()
in >> version
if version < 100:
return XXX_BAD_FILE_TOO_OLD
if version > 123:
return XXX_BAD_FILE_TOO_NEW
if version <= 110:
in.setVersion(QDataStream.Qt_3_2)
else:
in.setVersion(QDataStream.Qt_4_0)
# Read the data
in >> lots_of_interesting_data
if version >= 120:
in >> data_new_in_XXX_version_1_2
in >> other_interesting_data
您可以选择在序列化数据时使用的字节顺序。默认设置为big endian(MSB优先)。将其更改为little endian会破坏可移植性(除非读取器也更改为little-endian)。除非您有特殊要求,否则我们建议您保留此设置。
读取和写入原始二进制数据
您可能希望直接在数据流中读取/写入自己的原始二进制数据。可以使用readRawData()将数据从流中读取到预先分配的char中。类似地,可以使用writeRawData()将数据写入流。请注意,数据的任何编码/解码都必须由您完成。
readBytes()和writeBytes(()是一对类似的函数。这些与原始对应项的区别如下:readBytes()读取一个quint32,该quint32被视为要读取的数据的长度,然后该字节数被读取到预分配的char中;writeBytes()写入一个包含数据长度的quint32,后跟数据。请注意,数据的任何编码/解码(除了长度为quint32之外)都必须由您完成。
阅读和写作Qt集合课程
Qt容器类也可以序列化为QDataStream。其中包括QList、QSet、QHash和QMap。流运算符被声明为类的非成员。
阅读和写作其他Qt课程
除了这里记录的重载流运算符之外,您可能希望序列化为QDataStream的任何Qt类都将有适当的流运算符声明为该类的非成员:
operator<< = QDataStream(QDataStream , QXxx)
operator>> = QDataStream(QDataStream , QXxx)
例如,以下是被声明为QImage类非成员的流运算符:
= QDataStream(QDataStream stream, QImage image)
= QDataStream(QDataStream stream, QImage image)
要查看您最喜欢的Qt类是否定义了类似的流运算符,请查看该类文档页面的Related Non-Members部分。
使用读取事务
当数据流在异步设备上运行时,数据块可以在任意时间点到达。QDataStream类实现了一种事务机制,该机制提供了使用一系列流运算符原子读取数据的能力。例如,您可以通过在连接到readyRead()信号的插槽中使用事务来处理套接字中的不完整读取:
in.startTransaction()
str = QString()
a = qint32()
in >> str >> a # try to read packet atomically
if not in.commitTransaction():
return # wait for more data
如果没有收到完整的数据包,此代码会将流恢复到初始位置,之后您需要等待更多数据到达。
数据流 QDataStream 的方法
数据流的一些常用方法如表所示,主要方法介绍如下
-
创建数据流对象时,可以设置数据流关联的设备
- 也可用setDevice(QIODevice)方法重新设置关联的设备
- 用device()方法获取关联的设备。
-
用setVersion(int)方法设置版本号。不同版本号的数据的存储格式有所不同,因此建议设暨版本号。到目前为止,版本号可取 :
- QDataStream.Qt_1_0
- QDataStream.Qt_2_0
- QDataStream.Qt_3_0
- QDataStream.Qt_3_1
- QDataStream.Qt_3_3
- QDataStream.Qt_4_0
- QDataStream.Qt_4_9
- QDataStream.Qt_5_0 ~ QDataStream.Qt_5_15
- QDataStream.Qt_6_0 ~ QDataStream.Qt_6_2
-
用setFloatingPointPrecision(QDataStream,FloatingPointPrecision)方法设置读写0浮点数的精度,其中参数QDataStream.FloatingPointPrecision 可以取:
- QDataStream,SinglePrecision
- QDataStream,DoublePrecision。
对于版本高于Qt_4_6且精度设置为 DoublePrecision 的点数是 64 位精度
对于版本高于Qt_4_6且,精度设置为 SinglePrecision 的浮点数是 32 位精度。
-
用setByteOrder(QDataStream,ByteC)rder)方法设置字节序,参数 QDataStream.ByteOrder 可以取 :
- QDataStream,BigEndian(大端字节序,默认值)
- QDataStream.IittleEndian(小端字节序),
大端字节序的高位字节在前,低位字节在后,小端字节序与此相反。对于十进制数 123,如果用"123"顺序存储是大端字节序,而用"321"顺序存储是小端字节序,二进制与此类似。
-
用setStatus(QDataStream,Status)方法设置状态,状态的取值与 QTextStream的取值相同。
-
用skipRawData(len:int)方法可以跳过指定长度的原生字节,返回真实跳过的字节数。
- 原生数据是机器上存储的二进制数据,需要用户自己解码。
-
用startTransaction()方法可以记录一个读数据的点
- 对于顺序设备会在内部复制读取的数据,对于随机设备会保存当前数据流的位置
- 用commitTransaction()方法确认完成记录一个数据块,当数据流的状态是已经超过末尾时,用该方法会回到数据块的记录点,如果状态是数据有误,则会放弃记录的数据块,
- 用rollbackTransaction()方法在确认完成记录数据块之前返回到记录点;
- 用abortTransaction()方法放弃对数据块的记录,并不影响当前读数据的位置
QDataStream的方法及参数类型 | 说己明 |
---|---|
setDevice(QIODevice) | 设置设备 |
setByteOrder(QDataStream.ByteOrder) | 设置字节序 |
byteOrder() | 获取字节序 QDataStream.ByteOrder |
setFloatingPointPrecision(QDataStream. FloatingPointPrecision) | 设置读写浮点数的精度 |
setStatus(QDataStream. Status) | 设置状态 |
resetStatus()、status() | 重置状态、获取状态 |
setVersion(int) | 设置版本号 |
version() | 获取版本号 |
skipRawData(len:int) | 跳过原生数据,返回跳过的字节数量 |
startTransaction() | 开启记录一个数据块起始点 |
commitTransaction() | 完成数据块,成功则返回True |
rollbackTransaction() | 回到数据块的记录点 |
abortTransaction() | 放弃对数据块的记录 |
atEnd() | 获取是否还有数据可读 |
整数、浮点数和逻辑值的读写方法
计算机中存储的数据用二进制表示,每个位有0和1两种状态通常用8位作为1个字节,如果这8位全部用来记录数据,则这8位数据的最大值是0b11111111=2-1=255
-
如要记录正负号,可以用第1位记录,这时用7位记录的最大值是 0b1111111-2-1127。
-
如果要记录更大的值,用1个字节显然是不够的,这时可以用更多个字节来记录
- 例如用2个字节(16 位)来记录一个数,如果全部用于记录数据,可以记录的最大值为 216 -1;
- 如果用1位记录正负号,可以记录的最大值为 215 -1。
-
因此在读写不同大小的数值时,
- 要根据数值的大小选择合适的字节数来保存数值,可以分别用1个字节、2个字节4 个字节和8个字节来存储数值,
- 在读取数值时,要根据写入时指定的字节数来读取,
-
数据流用于读/写整数、浮点数和逻辑值的方法和数值的范围如表所示。需要特别注意的是,在读数值时,必须按照写人数值时所使用的字节数来读,否则读取的数值不是写人时的数值。
读/写方法(->表示返回值的类型) 读/写方法说明 读/写取值范围 readInt8()->int writeInt8(int) 在1个字节上读/写带正负号整数 -2^7 ~ 2^7 -1 readInt16()-> int writeInt16(int) 在2个字节上读/写带正负号整数 -2^15 ~ 2^15-1 readInt32()-> int writeInt32(int) 在4个字节上读/写带正负号整数 -2^31 ~ 2^31-1 readInt64()-> int writeInt64(int) 在8个字节上读/写带正负号整数 -2^63 ~ 2^63-1 readUInt8()->int writeUInt8(int) 在1个字节上读/写不带正负号整数 0 ~ 2^8 -1 readUInt16()->int writeUInt16(int) 在2个字节上读/写不带正负号整数 0 ~ 2^16 -1 readUInt32()->int writeUInt32(int) 在4个字节上读/写不带正负号整数 0 ~ 2^32 -1 readUInt64()->int writeUInt64(int) 在8个字节上读/写不带正负号整数 0 ~ 2^64 -1 readFloat()-> float writeFloat(float) 在4个字节上读/写带正负号浮点数 ±3.40282E38(精 确到6位小数) readDouble()-> float writeDouble(float) 在8个字节上读/写带正负号浮点数 ±1.79769E308(精 确到15位小数) readBo01()->bool writeBo01(bool) 在1个字节上读/写逻辑值
对字符串的读/写方法
数据流用于读/写字符串的方法如表所示。读/写字符串时不需要指定字节数量系统会根据字符串的大小来决定所使用的字节数。
读/写方法(>表示返回值的类型) | 读/写方法说明 | |
---|---|---|
readQString()-> str | writeQString(str) | 读/写文本 |
readQStringList()-> List[str] | writeQStringList(Sequence[str]) | 读/写文本列表 |
readString()-> str | writeString(str) | 读/写文 |
用QDataStream读写字符串和数值的应用实例
下面的程序是将上一个用QTextStream读写文本数据的程序改用QDataStream来完成读写二进制数据,将数据保存到二进制文件中。程序中用到读写字符串、整数和浮点数的方法。
# -*- coding: UTF-8 -*-
# File date: Hi_2023/3/11 15:00
# File_name: 03-用QDataStream读写字符串和数值的应用实例.py
from PySide6.QtWidgets import QApplication,QMainWindow,QPlainTextEdit,QFileDialog
from PySide6.QtCore import QFile,QDataStream
import sys,math
class MyWindow(QMainWindow):
def __init__(self,parent=None):
super().__init__(parent)
self.setupUI()# 界面
self.fileName ="./sin_cos.bin"# 写入的文件
def setupUI(self):
self.plainText = QPlainTextEdit()
self.resize(800,600)
self.setCentralWidget(self.plainText)
self.status = self.statusBar()
self.menubar = self.menuBar()# 菜单栏
self.file = self.menubar.addMenu("文件")# 文件菜单
action_textCreate = self.file.addAction("生成文件")# 动作
action_textCreate.triggered.connect(self.binCreate_triggered)# 动作与槽的连接
action_textOpen = self.file.addAction("打开文件")
action_textOpen.triggered.connect(self.binOpen_triggered)
self.file.addSeparator()
action_close = self.file.addAction("关闭")
action_close.triggered.connect(self.close)
def binCreate_triggered(self):
file = QFile(self.fileName)
try:
if file.open(QFile.WriteOnly | QFile.Truncate): # 打开文件
writer = QDataStream(file)
writer.setVersion(QDataStream.Qt_6_2)
writer.setByteOrder(QDataStream.ByteOrder.BigEndian)
writer.writeQString("version:Qt_6_2")
writer.writeQString("x(度)")
writer.writeQString("sin(x)")
writer.writeQString("cos(x)")
writer.writeQString("sin(x)+cos(x)")
for i in range(360):
r = i / 180 * math.pi
writer.writeInt16(i)
writer.writeDouble(math.sin(r))# sin
writer.writeDouble(math.cos(r))# cos
writer.writeDouble(math.sin(r)+ math.cos(r))# sin + cos
except:
self.status.showMessage("写入文件失败!")
else:
self.status.showMessage("写入文件成功!")
file.close()
def binOpen_triggered(self):
fileName,file = QFileDialog.getOpenFileName(self,caption="打开二进制文件",dir=".",filter="bin(*.bin);;所有文件(*.*)")
file = QFile(fileName)
template ="{:^16}{:^16.10}{:^16.10}{:^16.10}"
try:
if file.open(QFile.ReadOnly): # 打开文件
self.plainText.clear()
reader = QDataStream(file)
reader.setVersion(QDataStream.Version.Qt_6_2)
reader.setByteOrder(QDataStream.ByteOrder.BigEndian)
if reader.readQString()=="version:Qt_6_2":
self.plainText.clear()
str1 = reader.readQString()
str2 = reader.readQString()
str3 = reader.readQString()
str4 = reader.readQString()
string = template.format(str1,str2,str3,str4)
self.plainText.appendPlainText(string)
while not reader.atEnd():
deg = reader.readInt16()
sin = reader.readDouble()
cos = reader.readDouble()# 读取浮点数
sin_cos = reader.readDouble()# 读取浮点数
string = template.format(deg,sin,cos,sin_cos)
self.plainText.appendPlainText(string)
except:
self.status.showMessage("打开文件失败!")
else:
self.status.showMessage("打开文件成功!")
file.close()
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MyWindow()
win.show()
sys.exit(app.exec())
类对象的读写方法
用QDataStream可以将一些常用的类实例写人文件中例如字体颜色、调色板列表项、表格项等。
用writeQVariant(Any)方法可以将 QBrush,QColor,QDateTime QFont.QPixmap、QMargin、QPint、QLine QRect、 QSize、 QTime、 QDate、QDateTime、QListWidgetItem、QTableWidgetItem、QTreeWidgetItem 和其他一些实例对象写人文件中,用readQVariant()方法可以读取这些对象
下面的程序是 writeQVariant()和 readQVariant()方法的使用实例。通过"文件"菜单根据文件的扩展名用QDataStream方式打开或保存二进制文件,用QTextStream方式打开或保存文本文件,可以利用"设置"菜单设置颜色和字体。当保存二进制文件时,用writeQVariant()方法写入调色板和字体;当打开二进制文件时用readQVariant()方法读取调色板和字体。
# -*- coding: UTF-8 -*-
# File date: Hi_2023/3/11 15:30
# File_name: 04-类对象的读写方法实例.py
from PySide6.QtWidgets import QApplication,QMainWindow,QPlainTextEdit,QFileDialog,QMessageBox,QFontDialog,QColorDialog
from PySide6.QtCore import QFile,QTextStream,QDataStream,QStringConverter
from PySide6.QtGui import QPalette
import sys,os
class MyWindow(QMainWindow):
def __init__(self,parent=None):
super().__init__(parent)
self.resize(800,600)
self.setupUI()# 界面
def setupUI(self): # 界面建立
self.plainText = QPlainTextEdit()
self.setCentralWidget(self.plainText)
self.status = self.statusBar()
self.menubar = self.menuBar()# 菜单栏
self.file = self.menubar.addMenu('文件')# 文件菜单
action_new = self.file.addAction("新建")
action_new.triggered.connect(self.plainText.clear)
action_open = self.file.addAction("打开文件")# 动作 打开二进制文件或文本文件
action_open.triggered.connect(self.open_triggered)# 动作与槽的连接
self.action_save = self.file.addAction("保存文件")# 动作,保存二进制文件 或文本文件
self.action_save.triggered.connect(self.save_triggered)
self.action_save.setEnabled(False)# 动作与槽的连接
self.file.addSeparator()
action_close = self.file.addAction("关闭")
action_close.triggered.connect(self.close)
self.setting = self.menubar.addMenu("设置")
action_color = self.setting.addAction("设置颜色")
action_color.triggered.connect(self.color_triggered)
action_font = self.setting.addAction("设置字体")
action_font.triggered.connect(self.font_triggered)
self.plainText.textChanged.connect(self.plainText_textChaneged)
def open_triggered(self):
fileName,file = QFileDialog.getOpenFileName(self,caption="打开二进制文件",dir=".",filter="所有文件(*.*);;二进制文件(*.bin);;文本文件(*.txt);;py文件(*.py)")
if not os.path.isfile(fileName):
return
name,extension = os.path.splitext(fileName)# 获取文件名和扩展名
file = QFile(fileName)
try:
if file.open(QFile.ReadOnly): # 打开文件
if extension ==".bin": # 根据扩展名识别二进制文件
reader = QDataStream(file)
reader.setVersion(QDataStream.Version.Qt_6_2)# 设置版本
reader.setByteOrder(QDataStream.BigEndian)
version = reader.readQString()# 读取版本号
if version !="version:Qt_6_2":
QMessageBox.information(self,"错误","版本不匹配")
return
palette = reader.readQVariant()# 读取调色板信息
font = reader.readQVariant()# 读取字体信息
self.plainText.setPalette(palette)# 设置调色板
self.plainText.setFont(font)# 设置字体
if not file.atEnd():
string = reader.readQString()# 读取文本
self.plainText.clear()
self.plainText.appendPlainText(string)
if extension ==".txt"or extension ==".py": # 根据扩展名识别tt或py文件
file.setTextModeEnabled(True)
reader = QTextStream(file)
reader.setEncoding(QStringConverter.Utf8)
reader.setAutoDetectUnicode(True)
string = reader.readAll()
self.plainText.clear()
self.plainText.appendPlainText(string)# 读取所有数据
except:
self.status.showMessage("打开文件失败!")
else:
self.status.showMessage("打开文件成功!")
file.close()
def save_triggered(self):
fileName,fil = QFileDialog.getSaveFileName(self,caption="保存文件",dir=".",filter="二进制文件(*.bin);;text(*.txt);;python(*.py);;所有文件(*.*)")
if fileName =="":
return
name,extension = os.path.splitext(fileName)# 获取文件名和扩展名
file = QFile(fileName)
try:
if file.open(QFile.WriteOnly | QFile.Truncate): # 打开文件
if extension ==".bin": # 根据扩展名识别二进制文件
writer = QDataStream(file)# 创建数据流
writer.setVersion(QDataStream.Qt_6_2)# 设微版本
writer.setByteOrder(QDataStream.BigEndian)
writer.writeQString("version:Qt_6_2")# 写人版本
palette = self.plainText.palette()
font = self.plainText.font()
string = self.plainText.toPlainText()
writer.writeQVariant(palette)# 写人调色板
writer.writeQVariant(font)# 写人字体
writer.writeQString(string)# 写人内容
if extension ==".txt"or extension ==".py": # 根据扩展名识别txt 或 py文件
reader = QTextStream(file)
reader.setEncoding(QStringConverter.Utf8)
string = self.plainText.toPlainText()
reader << string # 写人内容
except:
self.status.showMessage("文件保存失败!")
else:
self.status.showMessage("文件保存成功!")
file.close()
def font_triggered(self): # 槽函数,设置字体
font = self.plainText.font()
ok,font = QFontDialog.getFont(font,parent=self,title="选择字体")
if ok:
self.plainText.setFont(font)
def color_triggered(self): # 槽函数,设置颜色
color = self.plainText.palette().color(QPalette.Text)
colorDialog = QColorDialog(color,parent=self)
if colorDialog.exec():
color = colorDialog.selectedColor()
palette = self.plainText.palette()
palette.setColor(QPalette.Text,color)
self.plainText.setPalette(palette)
def plainText_textChaneged(self): # 槽函数,判断保存动作是否需要激活或失效
if self.plainText.toPlainText()=="":
self.action_save.setEnabled(False)
else:
self.action_save.setEnabled(True)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MyWindow()
win.show()
sys.exit(app.exec())