Skip to content

Commit 69e6ae1

Browse files
committed
QIODevice: Add QIODevice::readLineInto()
The QByteArray QIODevice::readLine() method allocates new memory (QByteArray) for each line. Introduce 'QIODevice::readLineInto(QByteArray*, qint64 max)' which uses pre-allocated memory to optimize performance. Introduce QIODevicePrivate::skipLine(), similar to readLineData(), to read a line without storing it. Call it in readLineInto() when the line is nullptr and the maxsize is 0 or not specified. [ChangeLog][QtCore][QIODevice] Added readLineInto() an alternative to readLine() that utilizes pre-allocated memory. Task-number: QTBUG-113547 Task-number: QTBUG-103108 Change-Id: I51f24b8664f80652bba3c8fc2cb3af4cf9adbeac Reviewed-by: Marc Mutz <[email protected]> Reviewed-by: Thiago Macieira <[email protected]>
1 parent 032b16a commit 69e6ae1

File tree

5 files changed

+203
-22
lines changed

5 files changed

+203
-22
lines changed

src/corelib/io/qiodevice.cpp

Lines changed: 102 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,45 +1441,90 @@ qint64 QIODevicePrivate::readLine(char *data, qint64 maxSize)
14411441
for reading, or that an error occurred.
14421442
*/
14431443
QByteArray QIODevice::readLine(qint64 maxSize)
1444+
{
1445+
QByteArray result;
1446+
if (!readLineInto(&result, maxSize) && !result.isNull())
1447+
result = QByteArray();
1448+
return result;
1449+
}
1450+
1451+
/*!
1452+
\since 6.9
1453+
1454+
Reads a line from the device, but no more than \a maxSize characters.
1455+
and stores it as a byte array in \a line.
1456+
1457+
\note Reads a line from this device even if \a line is \nullptr.
1458+
1459+
If \a maxSize is 0 or not specified, the line can be of any length,
1460+
thereby enabling unlimited reading.
1461+
1462+
The resulting line can have trailing end-of-line characters ("\n" or "\r\n"),
1463+
so calling QByteArray::trimmed() may be necessary.
1464+
1465+
If no data was currently available for reading, or in case an error occurred,
1466+
this function returns \c{false} and sets \a line to
1467+
\l{QByteArray::isEmpty()}{empty}. Otherwise it returns \c true.
1468+
1469+
Note that the contents of \a line before the call are discarded in any case
1470+
but its \l{QByteArray::}{capacity()} is never reduced.
1471+
1472+
\sa readAll(), readLine(), QTextStream::readLineInto()
1473+
*/
1474+
bool QIODevice::readLineInto(QByteArray *line, qint64 maxSize)
14441475
{
14451476
Q_D(QIODevice);
14461477
#if defined QIODEVICE_DEBUG
1447-
printf("%p QIODevice::readLine(%lld), d->pos = %lld, d->buffer.size() = %lld\n",
1478+
printf("%p QIODevice::readLineInto(%lld), d->pos = %lld, d->buffer.size() = %lld\n",
14481479
this, maxSize, d->pos, d->buffer.size());
14491480
#endif
14501481

1451-
QByteArray result;
1452-
CHECK_READABLE(readLine, result);
1482+
auto emptyResultOnFailure = qScopeGuard([line] {
1483+
if (line)
1484+
line->resize(0);
1485+
});
1486+
1487+
CHECK_READABLE(readLineInto, false);
14531488

14541489
qint64 readBytes = 0;
1490+
14551491
if (maxSize == 0) {
14561492
// Size is unknown, read incrementally.
14571493
maxSize = QByteArray::maxSize() - 1;
14581494

14591495
qint64 readResult;
1460-
do {
1461-
// +1 since d->readLine() actually _writes_ a terminating NUL (### why does it?)
1462-
result.resize(qsizetype(qMin(maxSize, 1 + readBytes + d->buffer.chunkSize())));
1463-
readResult = d->readLine(result.data() + readBytes, result.size() - readBytes);
1464-
if (readResult > 0 || readBytes == 0)
1465-
readBytes += readResult;
1466-
} while (readResult == d->buffer.chunkSize()
1467-
&& result[qsizetype(readBytes - 1)] != '\n');
1496+
if (!line) {
1497+
readBytes = d->skipLine();
1498+
} else {
1499+
do {
1500+
// Leave an extra byte for the terminating null by adding + 1
1501+
line->resize(qsizetype(qMin(maxSize, 1 + readBytes + d->buffer.chunkSize())));
1502+
readResult = d->readLine(line->data() + readBytes, line->size() - readBytes);
1503+
if (readResult > 0 || readBytes == 0)
1504+
readBytes += readResult;
1505+
} while (readResult == d->buffer.chunkSize()
1506+
&& (*line)[qsizetype(readBytes - 1)] != '\n');
1507+
}
14681508
} else {
1469-
CHECK_LINEMAXLEN(readLine, result);
1470-
CHECK_MAXBYTEARRAYSIZE(readLine);
1471-
1472-
result.resize(maxSize);
1473-
readBytes = d->readLine(result.data(), result.size());
1509+
CHECK_LINEMAXLEN(readLineInto, false);
1510+
CHECK_MAXBYTEARRAYSIZE(readLineInto);
1511+
1512+
if (!line){
1513+
readBytes = skip(maxSize);
1514+
} else {
1515+
line->resize(maxSize);
1516+
readBytes = d->readLine(line->data(), line->size());
1517+
}
14741518
}
14751519

14761520
if (readBytes <= 0)
1477-
result.clear();
1478-
else
1479-
result.resize(readBytes);
1521+
return false;
14801522

1481-
result.squeeze();
1482-
return result;
1523+
if (line)
1524+
line->resize(readBytes);
1525+
1526+
emptyResultOnFailure.dismiss();
1527+
return true;
14831528
}
14841529

14851530
/*!
@@ -2030,6 +2075,42 @@ qint64 QIODevicePrivate::skipByReading(qint64 maxSize)
20302075
return readSoFar;
20312076
}
20322077

2078+
/*!
2079+
\internal
2080+
2081+
\since 6.9
2082+
2083+
Reads to the end of the line without storing its content.
2084+
Returns the number of bytes read from the current line including
2085+
the '\n' byte.
2086+
2087+
If an error occurs, -1 is returned. This happens when no bytes
2088+
were read or when trying to read past EOF.
2089+
2090+
\sa readLineData(), skip()
2091+
*/
2092+
qint64 QIODevicePrivate::skipLine()
2093+
{
2094+
char c;
2095+
qint64 readSoFar = 0;
2096+
qint64 lastReadReturn = 0;
2097+
2098+
while ((lastReadReturn = read(&c, 1)) == 1) {
2099+
++readSoFar;
2100+
if (c == '\n')
2101+
break;
2102+
}
2103+
2104+
#if defined QIODEVICE_DEBUG
2105+
printf("%p QIODevicePrivate::skipLine(), pos = %lld, buffer.size() = %lld, "
2106+
"returns %lld\n", this, pos, buffer.size(), readSoFar);
2107+
#endif
2108+
2109+
if (lastReadReturn != 1 && readSoFar == 0)
2110+
return isSequential() ? lastReadReturn : -1;
2111+
return readSoFar;
2112+
}
2113+
20332114
/*!
20342115
\since 6.0
20352116

src/corelib/io/qiodevice.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class Q_CORE_EXPORT QIODevice
7878
QByteArray readAll();
7979
qint64 readLine(char *data, qint64 maxlen);
8080
QByteArray readLine(qint64 maxlen = 0);
81+
bool readLineInto(QByteArray *result, qint64 maxlen = 0);
8182
virtual bool canReadLine() const;
8283

8384
void startTransaction();

src/corelib/io/qiodevice_p.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ class Q_CORE_EXPORT QIODevicePrivate
149149
virtual qint64 peek(char *data, qint64 maxSize);
150150
virtual QByteArray peek(qint64 maxSize);
151151
qint64 skipByReading(qint64 maxSize);
152+
qint64 skipLine();
152153
void write(const char *data, qint64 size);
153154

154155
inline bool isWriteChunkCached(const char *data, qint64 size) const

src/corelib/serialization/qtextstream.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1569,7 +1569,7 @@ QString QTextStream::readLine(qint64 maxlen)
15691569
an error has occurred; otherwise returns \c true. The contents in
15701570
\a line before the call are discarded in any case.
15711571
1572-
\sa readAll(), QIODevice::readLine()
1572+
\sa readAll(), QIODevice::readLine(), QIODevice::readLineInto()
15731573
*/
15741574
bool QTextStream::readLineInto(QString *line, qint64 maxlen)
15751575
{

tests/auto/corelib/io/qiodevice/tst_qiodevice.cpp

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ private slots:
2828
void readLine2_data();
2929
void readLine2();
3030

31+
void readLineInto_Checks_data();
32+
void readLineInto_Checks();
33+
34+
void readLineInto();
35+
3136
void readAllKeepPosition();
3237
void writeInTextMode();
3338
void skip_data();
@@ -530,6 +535,99 @@ void tst_QIODevice::readLine2()
530535
}
531536
}
532537

538+
void tst_QIODevice::readLineInto_Checks_data()
539+
{
540+
QTest::addColumn<bool>("open");
541+
QTest::addColumn<QIODevice::OpenModeFlag>("openModeFlag");
542+
QTest::addColumn<QString>("warningMessage");
543+
544+
QTest::newRow("Device not open") << false << QIODevice::ReadOnly
545+
<< "QIODevice::readLineInto (QBuffer): device not open";
546+
QTest::newRow("Write only") << true << QIODevice::WriteOnly
547+
<< "QIODevice::readLineInto (QBuffer): WriteOnly device";
548+
QTest::newRow("Incorrect maxSize") << true << QIODevice::ReadOnly
549+
<< "QIODevice::readLineInto (QBuffer): Called with maxSize "
550+
"< 2";
551+
}
552+
553+
void tst_QIODevice::readLineInto_Checks()
554+
{
555+
QFETCH(bool, open);
556+
QFETCH(QIODevice::OpenModeFlag, openModeFlag);
557+
QFETCH(QString, warningMessage);
558+
559+
QByteArray data("Try to read this.");
560+
QBuffer buffer(&data);
561+
562+
QByteArray l1 = "Not Empty";
563+
QVERIFY(!l1.isEmpty());
564+
qsizetype cap_before = l1.capacity();
565+
566+
if (open) {
567+
QVERIFY(buffer.open(openModeFlag));
568+
buffer.seek(0);
569+
}
570+
qint64 pos_before = buffer.pos();
571+
572+
QTest::ignoreMessage(QtWarningMsg, warningMessage.toLatin1());
573+
QCOMPARE(buffer.readLineInto(&l1, 1), false);
574+
QVERIFY(l1.isEmpty()); // Make sure readLineInto() makes l1 empty in case an error occurred.
575+
576+
QVERIFY(l1.capacity() >= cap_before); // Capacity should not be reduced.
577+
QCOMPARE(buffer.pos(), pos_before);
578+
}
579+
580+
void tst_QIODevice::readLineInto()
581+
{
582+
QByteArray data ("First line.\r\n");
583+
data.append(QByteArray(100, 'x'));
584+
data.append("\r\n");
585+
data.append(QByteArray(32769, 'y'));
586+
data.append("\r\n");
587+
data.append(QByteArray(16388, 'z'));
588+
data.append("\r\nThe end.");
589+
590+
QBuffer buffer(&data);
591+
QVERIFY(buffer.open(QIODevice::ReadOnly));
592+
QVERIFY(buffer.canReadLine());
593+
buffer.seek(0);
594+
QByteArray l1;
595+
596+
qsizetype cap_before = l1.capacity();
597+
qint64 pos_before = buffer.pos();
598+
QCOMPARE(buffer.readLineInto(&l1, 0), true);
599+
QCOMPARE(l1, "First line.\r\n");
600+
QCOMPARE_GT(l1.capacity(), cap_before);
601+
QVERIFY(buffer.pos() > pos_before);
602+
603+
cap_before = l1.capacity();
604+
pos_before = buffer.pos();
605+
QCOMPARE(buffer.readLineInto(&l1), true);
606+
QCOMPARE(l1.size(), 100 + 2);
607+
QCOMPARE_GE(l1.capacity(), cap_before);
608+
QCOMPARE(buffer.pos(), pos_before + 102);
609+
610+
pos_before = buffer.pos();
611+
QCOMPARE(buffer.readLineInto(nullptr), true); // Read: 32769 'y' + '\r' + '\n'
612+
// but don't store it.
613+
QCOMPARE(buffer.pos(), pos_before + 32769 + 2);
614+
615+
pos_before = buffer.pos();
616+
QCOMPARE(buffer.readLineInto(nullptr, 16388 + 2), true);
617+
QCOMPARE(buffer.pos(), pos_before + 16388 + 2);
618+
619+
pos_before = buffer.pos();
620+
QByteArray *l = nullptr;
621+
QCOMPARE(buffer.readLineInto(l), true); // Read "The end." but don't store it.
622+
QVERIFY(buffer.pos() > pos_before);
623+
624+
cap_before = l1.capacity();
625+
pos_before = buffer.pos();
626+
QCOMPARE(buffer.readLineInto(&l1), false); // End of buffer.
627+
QCOMPARE_EQ(l1.capacity(), cap_before);
628+
QCOMPARE(buffer.pos(), pos_before);
629+
}
630+
533631
class SequentialReadBuffer : public QIODevice
534632
{
535633
public:

0 commit comments

Comments
 (0)