Introduce QIODevice::skip()

[ChangeLog][QtCore][QIODevice] Added skip() method to improve
performance in read operations.

Change-Id: I79068a3e9df108756abe37ba3d431e27e7413621
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Alex Trotsenko 2017-08-04 15:33:47 +03:00 committed by Tony Sarajärvi
parent 53357f0156
commit ca0df4b269
4 changed files with 217 additions and 0 deletions

View File

@ -1878,6 +1878,128 @@ QByteArray QIODevice::peek(qint64 maxSize)
return d->peek(maxSize); return d->peek(maxSize);
} }
/*!
\since 5.11
Skips up to \a maxSize bytes from the device. Returns the number of bytes
actually skipped, or -1 on error.
This function does not wait and only discards the data that is already
available for reading.
If the device is opened in text mode, end-of-line terminators are
translated to '\n' symbols and count as a single byte identically to the
read() and peek() behavior.
This function works for all devices, including sequential ones that cannot
seek(). It is optimized to skip unwanted data after a peek() call.
For random-access devices, skip() can be used to seek forward from the
current position. Negative \a maxSize values are not allowed.
\sa peek(), seek(), read()
*/
qint64 QIODevice::skip(qint64 maxSize)
{
Q_D(QIODevice);
CHECK_MAXLEN(skip, qint64(-1));
CHECK_READABLE(skip, qint64(-1));
const bool sequential = d->isSequential();
#if defined QIODEVICE_DEBUG
printf("%p QIODevice::skip(%lld), d->pos = %lld, d->buffer.size() = %lld\n",
this, maxSize, d->pos, d->buffer.size());
#endif
if ((sequential && d->transactionStarted) || (d->openMode & QIODevice::Text) != 0)
return d->skipByReading(maxSize);
// First, skip over any data in the internal buffer.
qint64 skippedSoFar = 0;
if (!d->buffer.isEmpty()) {
skippedSoFar = d->buffer.skip(maxSize);
#if defined QIODEVICE_DEBUG
printf("%p \tskipping %lld bytes in buffer\n", this, skippedSoFar);
#endif
if (!sequential)
d->pos += skippedSoFar;
if (d->buffer.isEmpty())
readData(nullptr, 0);
if (skippedSoFar == maxSize)
return skippedSoFar;
maxSize -= skippedSoFar;
}
// Try to seek on random-access device. At this point,
// the internal read buffer is empty.
if (!sequential) {
const qint64 bytesToSkip = qMin(size() - d->pos, maxSize);
// If the size is unknown or file position is at the end,
// fall back to reading below.
if (bytesToSkip > 0) {
if (!seek(d->pos + bytesToSkip))
return skippedSoFar ? skippedSoFar : Q_INT64_C(-1);
if (bytesToSkip == maxSize)
return skippedSoFar + bytesToSkip;
skippedSoFar += bytesToSkip;
maxSize -= bytesToSkip;
}
}
const qint64 skipResult = d->skip(maxSize);
if (skippedSoFar == 0)
return skipResult;
if (skipResult == -1)
return skippedSoFar;
return skippedSoFar + skipResult;
}
/*!
\internal
*/
qint64 QIODevicePrivate::skipByReading(qint64 maxSize)
{
qint64 readSoFar = 0;
do {
char dummy[4096];
const qint64 readBytes = qMin<qint64>(maxSize, sizeof(dummy));
const qint64 readResult = read(dummy, readBytes);
// Do not try again, if we got less data.
if (readResult != readBytes) {
if (readSoFar == 0)
return readResult;
if (readResult == -1)
return readSoFar;
return readSoFar + readResult;
}
readSoFar += readResult;
maxSize -= readResult;
} while (maxSize > 0);
return readSoFar;
}
/*!
\internal
*/
qint64 QIODevicePrivate::skip(qint64 maxSize)
{
// Base implementation discards the data by reading into the dummy buffer.
// It's slow, but this works for all types of devices. Subclasses can
// reimplement this function to improve on that.
return skipByReading(maxSize);
}
/*! /*!
Blocks until new data is available for reading and the readyRead() Blocks until new data is available for reading and the readyRead()
signal has been emitted, or until \a msecs milliseconds have signal has been emitted, or until \a msecs milliseconds have

View File

@ -136,6 +136,7 @@ public:
qint64 peek(char *data, qint64 maxlen); qint64 peek(char *data, qint64 maxlen);
QByteArray peek(qint64 maxlen); QByteArray peek(qint64 maxlen);
qint64 skip(qint64 maxSize);
virtual bool waitForReadyRead(int msecs); virtual bool waitForReadyRead(int msecs);
virtual bool waitForBytesWritten(int msecs); virtual bool waitForBytesWritten(int msecs);

View File

@ -174,6 +174,9 @@ public:
qint64 read(char *data, qint64 maxSize, bool peeking = false); qint64 read(char *data, qint64 maxSize, bool peeking = false);
virtual qint64 peek(char *data, qint64 maxSize); virtual qint64 peek(char *data, qint64 maxSize);
virtual QByteArray peek(qint64 maxSize); virtual QByteArray peek(qint64 maxSize);
qint64 skipByReading(qint64 maxSize);
// ### Qt6: consider replacing with a protected virtual QIODevice::skipData().
virtual qint64 skip(qint64 maxSize);
#ifdef QT_NO_QOBJECT #ifdef QT_NO_QOBJECT
QIODevice *q_ptr; QIODevice *q_ptr;

View File

@ -55,6 +55,10 @@ private slots:
void readAllKeepPosition(); void readAllKeepPosition();
void writeInTextMode(); void writeInTextMode();
void skip_data();
void skip();
void skipAfterPeek_data();
void skipAfterPeek();
void transaction_data(); void transaction_data();
void transaction(); void transaction();
@ -628,6 +632,93 @@ void tst_QIODevice::writeInTextMode()
#endif #endif
} }
void tst_QIODevice::skip_data()
{
QTest::addColumn<bool>("sequential");
QTest::addColumn<QByteArray>("data");
QTest::addColumn<int>("read");
QTest::addColumn<int>("skip");
QTest::addColumn<int>("skipped");
QTest::addColumn<char>("expect");
QByteArray bigData;
bigData.fill('a', 20000);
bigData[10001] = 'x';
bool sequential = true;
do {
QByteArray devName(sequential ? "sequential" : "random-access");
QTest::newRow(qPrintable(devName + "-small_data")) << true << QByteArray("abcdefghij")
<< 3 << 6 << 6 << 'j';
QTest::newRow(qPrintable(devName + "-big_data")) << true << bigData
<< 1 << 10000 << 10000 << 'x';
QTest::newRow(qPrintable(devName + "-beyond_the_end")) << true << bigData
<< 1 << 20000 << 19999 << '\0';
sequential = !sequential;
} while (!sequential);
}
void tst_QIODevice::skip()
{
QFETCH(bool, sequential);
QFETCH(QByteArray, data);
QFETCH(int, read);
QFETCH(int, skip);
QFETCH(int, skipped);
QFETCH(char, expect);
char lastChar = 0;
QScopedPointer<QIODevice> dev(sequential ? (QIODevice *) new SequentialReadBuffer(&data)
: (QIODevice *) new QBuffer(&data));
dev->open(QIODevice::ReadOnly);
for (int i = 0; i < read; ++i)
dev->getChar(nullptr);
QCOMPARE(dev->skip(skip), skipped);
dev->getChar(&lastChar);
QCOMPARE(lastChar, expect);
}
void tst_QIODevice::skipAfterPeek_data()
{
QTest::addColumn<bool>("sequential");
QTest::addColumn<QByteArray>("data");
QByteArray bigData;
for (int i = 0; i < 1000; ++i)
bigData += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
QTest::newRow("sequential") << true << bigData;
QTest::newRow("random-access") << false << bigData;
}
void tst_QIODevice::skipAfterPeek()
{
QFETCH(bool, sequential);
QFETCH(QByteArray, data);
QScopedPointer<QIODevice> dev(sequential ? (QIODevice *) new SequentialReadBuffer(&data)
: (QIODevice *) new QBuffer(&data));
int readSoFar = 0;
qint64 bytesToSkip = 1;
dev->open(QIODevice::ReadOnly);
forever {
QByteArray chunk = dev->peek(bytesToSkip);
if (chunk.isEmpty())
break;
QCOMPARE(dev->skip(bytesToSkip), qint64(chunk.size()));
QCOMPARE(chunk, data.mid(readSoFar, chunk.size()));
readSoFar += chunk.size();
bytesToSkip <<= 1;
}
QCOMPARE(readSoFar, data.size());
}
void tst_QIODevice::transaction_data() void tst_QIODevice::transaction_data()
{ {
QTest::addColumn<bool>("sequential"); QTest::addColumn<bool>("sequential");