读取到文件结束后,BufferedReader重置失败

时间:2014-07-07 21:16:47

标签: java bufferedreader reset eof

我有一个BufferedReader包装在一个文件上,我想标记一个地方,然后使用reset()返回到这个位置。我已经读过java api,它表示mark(readlimit),当读取太多字节时,重置将失败。所以我认为我可以设置一个很大的限制。

但是,如果我有代码

BufferedReader br=...;
br.mark(1024000); // large but not as large as file. 
while(br.ready()){
    //keep reading. to find some stuff. 
}
//now the br.ready() is false
br.reset() // It will fail with mark invalid exception

我认为问题是当br到达文件末尾时,br不再准备好,并且重置失败....我可以设法继续阅读直到最后一行并停止,但那我该怎么做?

我发现一个丑陋的解决方案是使用PushbackReader,以保存我读取的所有内容并在while循环后推回。我想知道是否有更好的解决方案。

2 个答案:

答案 0 :(得分:4)

我认为你错过了明确陈述的documentation of mark()

  

参数:
  readAheadLimit - 限制在保留标记的同时可以读取的字符数。读取这么多字符后,尝试重置流可能会失败。

因此,如果您想要完全阅读流,然后reset(),则需要使用与文件其余部分一样大的参数调用mark()

但正如BufferedReader.html#mark(int)的文档添加

  

大于输入缓冲区大小的限制值将导致分配一个大小不小于限制的新缓冲区。因此,应谨慎使用大值。

因此,如果需要关注内存,请考虑是否可以合并搜索和其他处理步骤,或者在两个步骤之间重新打开源代码。当然还有一种方法可以利用FileChannel,它能够自由搜索任何给定的文件,但不会为你提供字符或字符串。

你可以使用这个类的getReadCharacters()reopenAt(BigInteger)(没有经过适当测试的直接替代BufferedReader对文件的处理):

import java.io.*;
import java.math.BigInteger;
import java.nio.charset.Charset;

/**
 * Created by TheConstructor for http://stackoverflow.com/a/24620470/1266906.
 */
public class MarkableFileReader extends Reader {
    /**
     * Cached instance of {@link java.math.BigInteger} of value
     * {@link Long#MAX_VALUE} (used in {@link #skip(java.math.BigInteger)})
     */
    public static final BigInteger LONG_MAX_VALUE                    = BigInteger.valueOf(Long.MAX_VALUE);
    /**
     * Default value of {@link #reopenOnResetThreshold} (10 MiB)
     */
    public static final int        DEFAULT_REOPEN_ON_RESET_THRESHOLD = 10 * 1024 * 1024;
    /**
     * Initialize the line-reading-buffer to this size
     */
    public static final int        EXPECTED_LINE_LENGTH              = 80;

    private final File           file;
    private final Charset        charset;
    private       BufferedReader reader;
    private       BigInteger     readCharacters;
    private       BigInteger     mark;
    private       boolean        reopenOnReset;
    private final int            reopenOnResetThreshold;
    private final BigInteger     reopenOnResetThresholdBI;
    /**
     * {@link java.io.BufferedReader#readLine()} is implemented to skip the
     * {@code '\n'} of an {@code "\r\n"} only with the next read. The same
     * behaviour is implemented here.
     */
    private       boolean        skipLf;
    private       boolean        skipLfMark;

    public MarkableFileReader(String fileName) throws FileNotFoundException {
        this(fileName, null);
    }

    public MarkableFileReader(String fileName, Charset charset) throws FileNotFoundException {
        this(fileName, charset, DEFAULT_REOPEN_ON_RESET_THRESHOLD);
    }

    public MarkableFileReader(String fileName, Charset charset, int reopenOnResetThreshold)
            throws FileNotFoundException {
        this(new File(fileName), charset, reopenOnResetThreshold);
    }

    public MarkableFileReader(File file) throws FileNotFoundException {
        this(file, null, DEFAULT_REOPEN_ON_RESET_THRESHOLD);
    }

    public MarkableFileReader(File file, Charset charset, int reopenOnResetThreshold) throws FileNotFoundException {
        super();
        this.file = file;
        this.charset = charset;
        this.mark = null;
        this.skipLfMark = false;
        this.reopenOnReset = false;
        this.reopenOnResetThreshold = Math.max(0, reopenOnResetThreshold);
        this.reopenOnResetThresholdBI = BigInteger.valueOf(this.reopenOnResetThreshold);
        initReader();
    }

    private void initReader() throws FileNotFoundException {
        final FileInputStream fileInputStream = new FileInputStream(file);
        final InputStreamReader inputStreamReader = (charset == null) ?
                                                    new InputStreamReader(fileInputStream) :
                                                    new InputStreamReader(fileInputStream, charset);
        reader = new BufferedReader(inputStreamReader);
        this.readCharacters = BigInteger.ZERO;
        this.reopenOnReset = true;
        this.skipLf = false;
    }

    private void incrementReadCharacters() {
        this.readCharacters = this.readCharacters.add(BigInteger.ONE);
    }

    private void incrementReadCharacters(final long characters) {
        if(characters != -1) {
            this.readCharacters = this.readCharacters.add(BigInteger.valueOf(characters));
        }
    }

    @Override
    public int read() throws IOException {
        synchronized (lock) {
            final int read = reader.read();
            if (read != -1) {
                incrementReadCharacters();
            }
            if (skipLf && read == '\n') {
                skipLf = false;
                return read();
            }
            return read;
        }
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        synchronized (lock) {
            if ((off < 0) || (len < 0) ||
                    ((off + len) > cbuf.length) || ((off + len) < 0)) {
                throw new IndexOutOfBoundsException();
            } else if (len == 0) {
                return 0;
            }
            if(skipLf) {
                int firstChar = read();
                if (firstChar == -1) {
                    return 0;
                }
                cbuf[off] = (char) firstChar;
                if (len > 1) {
                    final int read = reader.read(cbuf, off + 1, len - 1);
                    incrementReadCharacters(read);
                    return read + 1;
                } else {
                    return 1;
                }
            } else {
                final int read = reader.read(cbuf, off, len);
                incrementReadCharacters(read);
                return read;
            }
        }
    }

    /**
     * Reads a line of text.  A line is considered to be terminated by any one
     * of a line feed ('\n'), a carriage return ('\r'), or a carriage return
     * followed immediately by a linefeed.
     * <p>Note: this is not directly proxied to
     * {@link java.io.BufferedReader#readLine()} as we need to know how many
     * characters compose the line-ending for {@link #getReadCharacters()} to
     * return correct numbers</p>
     *
     * @return A String containing the contents of the line, not including
     * any line-termination characters, or null if the end of the
     * stream has been reached
     * @throws IOException
     *         If an I/O error occurs
     * @see java.nio.file.Files#readAllLines(java.nio.file.Path, java.nio.charset.Charset)
     * @see java.io.BufferedReader#readLine()
     */
    public String readLine() throws IOException {
        synchronized (lock) {
            final CharArrayWriter charArrayWriter = new CharArrayWriter(EXPECTED_LINE_LENGTH);
            int lastRead = read();
            if(lastRead == -1) {
                return null;
            }
            while (lastRead != -1 && lastRead != '\r' && lastRead != '\n') {
                charArrayWriter.write(lastRead);
                lastRead = read();
            }
            if(lastRead == '\r') {
                skipLf = true;
            }
            return charArrayWriter.toString();
        }
    }

    @Override
    public long skip(long n) throws IOException {
        if (n < 0L) {
            throw new IllegalArgumentException("skip value is negative");
        }
        if(n == 0L) {
            return 0L;
        }
        synchronized (lock) {
            if(skipLf) {
                int read = read();
                if (read == -1) {
                    return 0;
                }
                final long skip = reader.skip(n - 1);
                incrementReadCharacters(skip);
                return skip + 1;
            } else {
                final long skip = reader.skip(n);
                incrementReadCharacters(skip);
                return skip;
            }
        }
    }

    @Override
    public boolean ready() throws IOException {
        synchronized (lock) {
            return reader.ready();
        }
    }

    @Override
    public boolean markSupported() {
        return true;
    }

    @Override
    public void mark(int readAheadLimit) throws IOException {
        if(readAheadLimit < 0) {
            throw new IllegalArgumentException("readAheadLimit needs to be 0 or greater");
        }
        synchronized (lock) {
            mark = readCharacters;
            skipLfMark = skipLf;
            reopenOnReset = false;
            if (reader.markSupported()) {
                if (readAheadLimit >= reopenOnResetThreshold) {
                    reader.mark(reopenOnResetThreshold);
                } else {
                    reader.mark(readAheadLimit);
                }
            }
        }
    }

    @Override
    public void reset() throws IOException {
        synchronized (lock) {
            if (mark == null) {
                throw new IOException("call mark() first");
            }
            final BigInteger readSinceMark = readCharacters.subtract(mark);
            if (reopenOnReset ||
                    readSinceMark.compareTo(reopenOnResetThresholdBI) >= 0 ||
                    !reader.markSupported()) {
                if (!reopenAt(mark)) {
                    throw new IOException("reopening at position failed");
                }
            } else {
                reader.reset();
                readCharacters = mark;
            }
            skipLf = skipLfMark;
        }
    }

    @Override
    public void close() throws IOException {
        synchronized (lock) {
            reader.close();
        }
    }

    public BigInteger getReadCharacters() {
        synchronized (lock) {
            return readCharacters;
        }
    }

    public boolean reopenAt(final BigInteger position) throws IOException {
        synchronized (lock) {
            if (reader != null) {
                reader.close();
            }
            initReader();
            BigInteger skip = skip(position);
            return skip.equals(position);
        }
    }

    public BigInteger skip(final BigInteger n) throws IOException {
        synchronized (lock) {
            BigInteger remaining = n;
            while (remaining.compareTo(BigInteger.ZERO) > 0) {
                long skip = skip(remaining.min(LONG_MAX_VALUE).longValue());
                remaining = remaining.subtract(BigInteger.valueOf(skip));
                if (skip < 1) {
                    break;
                }
            }
            return n.subtract(remaining);
        }
    }
}

答案 1 :(得分:0)

  

大但不像文件

那么大

这就是问题所在。使它变大或变大。你不能比标记量子更进一步重新设定。

但你根本不应该重新读取文件,这是糟糕的设计和糟糕的编程。编译器只能通过读取一次源文件来编译程序,编译要比你必须执行的任何过程复杂得多。