QT学习:10 IO类

--- title: framework-cpp-qt-10-IO类 EntryName: framework-cpp-qt-10-QIODevice date: 2020-04-17 10:24:06 categories: tags: - qt - c/c++ ---

章节描述:
QIODevice类是所有输入输出IO类的基础类,为IO类提供了统一的调用接口(因此我们称QIODevice类以及其派生类为IO类)。
QIODevice 是一个抽象类,所以不能被实例化,但通常会用到它定义的接口,这些接口提供设备无关的I/O特性。例如Qt的XML类通过操作一个QIODevice 的指针,可以使用各种各样的设备(files,buffers等)。

IO设备类型

IO类支持随机存储,和顺序储存设备。

可以使用 isSequential()判断设备是否是顺序设备。

其中顺序设备不支持pos(),size()方法。

调用size()函数时,如果是随机设备则返回当前设备的大小,如果是顺序设备则返回bytesAvailable()。在设备关闭的情况下调用size()函数将不会反映设备的实际大小,因此调用该函数前保证设备已打开。

顺序存取设备:只能从头开始顺序读写数据,不能指定数据的读写位置。顺序设备没有位置概念,也不支持搜索,只能在数据可用时一次性读取所有数据。典型的顺序设备是Socket

随机存取设备:随机设备就是文件,它们具有大小和当前位置,支持在数据流中向前向后搜索,可以定位到任意位置进行数据的读写。

QT中IO设备的继承类图:

QIODevice
│	随机设备
├── QBuffer
├── QFileDevice
│   ├── QFile
│   │   └── QTemporaryFile
│   └── QSaveFile
│
│	顺序设备
├── QAbstractSocket
│   ├── QTcpSocket
│   │   ├── QSctpSocket
│   │   └── QSslSocket
│   └── QUdpSocket
└── QProcess

IO类继承于QIODevice类,只需要实现自己的writeData()readData()方法。其他读写方法QIODevice都是调用writeData()readData()实现的。

操作流程

在访问设备之前,必须先调用open(),并设置正确的OpenMode(such as ReadOnly or ReadWrite)。你可以用write()putChar()来写入设备。也可以用read(),readLine()来读设备。使用完毕后调用close()

在访问IO类,必须先调用open()方法打开设备,之后才能调用读写方法对类进行操作。结束操作后需要调用close()方法关闭设备。

调度 与 同步

IO类发射readyRead()信号表示有数据可以读取,对应的可以调用bytesAvailable()方法了解可以读取多少字节的数据。
同理,发射bytesWritten()信号表示数据写入完成,对应的可以调用bytesToWrite()方法了解写入了多少字节的数据。

IO类的读写函数是非阻塞的,调用方法后不会等待数据读写完成方法,而是立即返回。

QTcpSocket and QProcess是QIODevice的子类,是异步的,这意味着 I/O 函数 write() or read()的结果总是立即返回,然而,当控件返回到事件循环时,可能会发生与设备本身的通信。

因此QIODevice提供函数在阻塞调用线程和不输入事件循环的同时,允许程序立即执行,这使得QIODevice的子类可以被使用,在没有循环事件或者是单线程的条件下,提供了waitForReadyRead()waitForBytesWriten()方法实现阻塞(在调用读写方法后调用对应的wait…方法实现阻塞)

waitFor...():子类会实现相应的函数为了特殊的操作。

比如QProcess 有个叫waitForStarted()的函数。它将会延迟调用的线程,直到那个process已经启动。

QProcess gzip;
gzip.start("gzip", QStringList() << "-c");
if (!gzip.waitForStarted())
    return false;
 
gzip.write("uncompressed data");
 
QByteArray compressed;
while (gzip.waitForReadyRead())
    compressed += gzip.readAll();

IO类例如QFile,QTcpSocket提供了buffer机制,用于减少底层驱动系统调用,提高访问速度。特别是提高了getChar,putChar方法的速度 。但是在多对象需要读取同一个设备的大批量数据时,buffer会导致内存中存在多个同样的数据副本,内存开销巨大。这个情况,可以 在调用open()方法时设置Unbuffered模式关闭buffer机制。

常用操作

open(OpenMode mode):打开设备。mode参数用于设置读写模式,buffer机制,读写机制等。
    
close():关闭设备
isOpen():判断设备是否被打开。
    
isWriteable:判断设备是否支持写入模式。(Open方法设置的)
isReadable:判断设备是否支持读取。
    
isSequential():判断设备是否是顺序设备
    
isTextModeEnable():Text模式getChar方法将忽略’/r’换行符,返回下个字符。
setTextModeEnable():设置text模式 

打开文件

bool QIODevice::open(QIODevice::OpenMode mode)

描述:以指定的方式打开一个文件。

参数解析:

mode:打开方式

  • QIODevice::NotOpen 不打开
  • QIODevice::ReadOnly 以只读的方式打开.
  • QIODevice::WriteOnly 以只写的方式打开,该模式意味着Truncate,除非与ReadOnly,Append或NewOnly结合使用。
  • QIODevice::ReadWrite 设备以读写的方式打开,写入文件会覆盖之前的内容(打开文件期间多次写入不会覆盖)。
  • QIODevice::Append 以追加模式打开,以便将所有数据写入文件末尾,此模式下不能读文件。
  • QIODevice::Truncate 如果可能,删除文件原有内容。
  • QIODevice::Text 读取时,行尾终止符被转换为'\ n'。 写入时,行尾终止符将转换为本地编码,例如Win32的“\ r \ n”。(常用于文本文件以行为单位的读取)
  • QIODevice::Unbuffered 无缓冲的形式打开,设备中的任何缓冲都会被跳过。
  • NewOnly:只允许打开一个不存在的文件
  • ExistingOnly:只允许打开一个已经存在的文件

设置

调用openMode()函数可以获取当前设备的打开模式。如果在设备打开的情况下改变设备模式,可以调用setOpenMode()函数来更改。

调用setTextModelEnabled()函数可以设置设备模式为Text,这对于自定义处理终止符非常有帮助。调用isTextmodeEnabled()函数可以确定设备模式是否有Text。

读取

读取数据前必须先判断是否有数据可读,有两种方法来确定是否可以读取数据:

  • 调用isReadable()函数;

  • 接收到readyRead()信号。(当有新数据可被读取时会发出该信号)

通常采用信号的方式,然后调用bytesAvailable()函数来确定可读取数据的大小,通常与顺序设备一起使用,来确定缓冲区中分配的字节数。

调用read()函数来读取数据,无法读取时返回-1。

便捷函数有readAll()readLine()/canReadLine()getChar()/ungetChar()

写入

调用isWritable()函数可以判断设备是否设置打开模式中包含了WriteOnly标志,这是一个便捷函数。

写入数据时,一般会先写入缓冲区,然后再从缓冲区写入设备。调用bytesToWrite()函数返回即将写入设备的数据大小,而对于无缓冲的设备则返回0。每次写入设备数据时都会发出bytesWritten()信号。

写入数据到设备中,调用write()函数:

qint64 QIODevice::write(const char *data)
qint64 QIODevice::write(const QByteArray &byteArray)
qint64 QIODevice::write(const char *data, qint64 maxSize)

便捷函数有putChar()函数。

读写位置

对于随机设备来说,每次读写都会导致内部的文件指针偏移;只有随机设备才可以设置/读取读写位置。

bool QIODevice::seek(qint64 pos) //定位文件指针。
    
qint64 QIODevice::pos() const // 获取文件指针位置

事务机制

事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据项的一个程序执行单元(unit)。事务一般由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。

为什么要事务?

事务是为解决数据安全操作提出的,事务控制实际上就是控制数据的安全访问。

用一个简单例子说明:银行转帐业务,账户A要将自己账户上的1000元转到B账户下面,A账户余额首先要减去1000元,然后B账户要增加1000元。假如在中间网络出现了问题,A账户减去1000元已经结束,B因为网络中断而操作失败,那么整个业务失败,必须做出控制,要求A账户转帐业务撤销。这才能保证业务的正确性,完成这个操走就需要事务,将A账户资金减少和B账户资金增加放到同一个事务里,要么全部执行成功,要么全部撤销,这样就保证了数据的安全性。

事务的4个特性(ACID):

  1. 原子性(atomicity):事务是数据库的逻辑工作单位,而且是必须是原子工作单位,对于其数据修改,要么全部执行,要么全部不执行。

  2. 一致性(consistency):事务在完成时,必须是所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。(实例:转账,两个账户余额相加,值不变。)

  3. 隔离性(isolation):一个事务的执行不能被其他事务所影响。

  4. 持久性(durability):一个事务一旦提交,事物的操作便永久性的保存在DB中。即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

一般来说,异步设备的输入流是分段的,数据块可以在任意的时间内到达。要在这种情况下读到完整数据,使用QIODevice的事务机制。

读取设备中的数据时,我们可以调用startTransaction()函数来在读取操作序列中设置一个恢复点。接下来的代码进行一般的读取操作,然后调用commitTransaction()函数来提交所有的操作。例如:

in.startTransaction();
 
QString nextFortune;
in >> nextFortune;
 
if (!in.commitTransaction())
 return;

调用rollbackTransaction()函数来回滚事务,这会将来自源的输入流恢复到startTransaction()时的点。

读写通道

一些顺序设备支持多通道通信,这些通道代表了具有独立排序传递特性的独立数据流。打开设备后,调用readChannelCount()writeChannelCount()函数来确定通道数。调用setCurrentReadChannel()setCurrentWriteChannel()函数来切换通道。

此外,QIODevice还提供额外的信号来处理通道的异步通信。

  • 如果通道有新数据可被设备读取时,发出channelReadyRead()信号。

  • 如果通道有数据写入设备时,发出channelBytesWritten()信号。

  • 关闭读取通道禁止输入流时,会发出readChannelFinished()信号。

错误信息

当设备发生故障时,我们可以调用setErrorString()函数来给设备设置一个错误信息,任何时候都可以调用errorString()函数来查看当前设备的错误信息。

posted @ 2020-04-17 10:24  schips  阅读(1513)  评论(0编辑  收藏  举报