Сбой чтения строки в зависимости от размера буфера - PullRequest
0 голосов
/ 19 марта 2020

Мне нужно читать входной поток построчно. Строка считается завершенной только CRLF, но не одним CR или LF. Это исключает BufferedReader readLine() и заставляет меня реализовать собственное решение:

final class LineReader
{
    private final Reader reader;
    private final char[] buffer;
    private final Queue<String> lines = new LinkedList<>();
    private StringBuilder line = new StringBuilder();
    private boolean cr = false;

    LineReader(final Reader reader, final int bufferSize)
    {
        this.reader = reader;
        buffer = new char[bufferSize];
    }

    String readLine() throws IOException
    {
        while (lines.peek() == null)
        {
            final int read = reader.read(buffer);
            if (read == - 1)
            {
                if (line == null)
                {
                    return null;
                }

                // Reached EOF. Return the last line.
                lines.add(line.toString());
                line = null;
                continue;
            }

            // Split the buffer by line.
            int offset = 0;
            for (int i = 0; i < read; i++)
            {
                final char ch = buffer[i];
                if (cr)
                {
                    // Last character was CR.
                    switch (ch)
                    {
                        case '\n':
                            // Found a CRLF.
                            if (i != 0)
                            {
                                line.append(buffer, offset, i - 1 - offset);
                            }

                            // Next line starts at the next character.
                            offset = i + 1;

                            lines.add(line.toString());
                            line = new StringBuilder();

                            cr = false;
                            break;
                        case '\r':
                            break;
                        default:
                            cr = false;
                            break;
                    }
                }
                else if (ch == '\r')
                {
                    cr = true;
                }
            }

            // Append remaining characters to the next line.
            line.append(buffer, offset, read - offset);
        }

        return lines.poll();
    }
}

Первоначально читатель прошел несколько наивных тестов. Однако, как только я начал изменять размер буфера, я заметил, что некоторые тесты не пройдены.

@Test
void readLine() throws IOException
{
    final String[] lines = new String[]{"foo bar", "baz", ""};
    final String str = Stream.of(lines).collect(joining("\r\n"));
    final Collection<Executable> assertions = new LinkedList<>();

    for (int bufferSize = 1; bufferSize <= 10; bufferSize++)
    {
        final LineReader reader = new LineReader(new StringReader(str),
                bufferSize);
        assertions.add(() ->
        {
            for (int i = 0; i < lines.length; i++)
            {
                assertEquals(lines[i], reader.readLine());
            }

            assertNull(reader.readLine());
        });
    }

    assertAll(assertions);
}

В частности, утверждение равенства не выполняется, только если размер буфера установлен в 1, 2, 4 или 8. И еще более странно, что сообщения об ошибках все пустые.

Multiple Failures (4 failures)
>
>
>
>
org.opentest4j.MultipleFailuresError: Multiple Failures (4 failures)
>
>
>
>
    at org.junit.jupiter.api.AssertAll.assertAll(AssertAll.java:80)
    at org.junit.jupiter.api.AssertAll.assertAll(AssertAll.java:54)
...

Я не могу обернуться вокруг этого.

1 Ответ

0 голосов
/ 19 марта 2020

Так что оказалось, что я ошибочно добавляю CR, когда это последний символ в буфере, хотя за ним следует LF. Избыточный CR также вызывал странное усечение сообщений об ошибках в выводе моей консоли. Ниже приведен метод работы:

String readLine() throws IOException
{
    while (lines.peek() == null)
    {
        if (line == null)
        {
            break;
        }

        final int read = reader.read(buffer);
        if (read == - 1)
        {
            lines.add(line.toString());
            line = null;
            continue;
        }

        int offset = 0;
        for (int i = 0; i < read; i++)
        {
            final char ch = buffer[i];

            if (cr)
            {
                if (ch == '\n')
                {
                    if (i != 0)
                    {
                        line.append(buffer, offset, i - 1 - offset);
                    }

                    offset = i + 1;

                    lines.add(line.toString());
                    line = new StringBuilder();

                    cr = false;
                }
                else
                {
                    if (i == 0)
                    {
                        line.append('\r');
                    }

                    if (ch != '\r')
                    {
                        cr = false;
                    }
                }
            }
            else if (ch == '\r')
            {
                cr = true;
            }
        }

        line.append(buffer, offset, read - offset - (cr ? 1 : 0));
    }

    return lines.poll();
}
...