如何安全地读取可能是二进制的文本文件?

时间:2012-03-08 22:15:36

标签: java out-of-memory

我们有一些Java代码处理用户提供的文件,方法是使用BufferedReader.readline()遍历文件以读取每一行。

问题在于,当用户上传具有极长行的文件(如任意二进制JPG等)时,这可能会导致内存不足问题。即使是第一个readline()也可能无法返回。 我们希望在OOM之前拒绝长行的文件。

是否有标准的Java习语来处理这个问题,或者我们只是更改为read()并编写我们自己的readLine()安全版本?

4 个答案:

答案 0 :(得分:1)

您需要自己逐个字符地阅读文件(或按块分块)(通过某种形式的read()),然后在遇到换行符时将这些行形成为字符串。这样,如果在遇到换行符之前遇到了一些最大字符数,就可以抛出异常(避免OOM错误)。

如果您使用Reader实例,那么实现此代码应该不会太困难,只需从Reader读取到缓冲区(您分配到最大可能的行长度),然后在遇到时将缓冲区转换为String换行符(如果不换行则抛出异常)。

答案 1 :(得分:1)

似乎没有任何方法可以为BufferedReader.readLine()设置行长度限制,因此它会在将其提供给代码之前累积整行,无论该行多长。

因此,您必须自己完成线分割部分,并在线条太长时放弃。

您可以使用以下内容作为起点:

class LineTooLongException extends Exception {}

class ShortLineReader implements AutoCloseable {
    final Reader reader;

    final char[] buf = new char[8192];
    int nextIndex = 0;
    int maxIndex = 0;
    boolean eof;

    public ShortLineReader(Reader reader) {
        this.reader = reader;
    }

    public String readLine() throws IOException, LineTooLongException {
        if (eof) {
            return null;
        }
        for (;;) {

            for (int i = nextIndex; i < maxIndex; i++) {
                if (buf[i] == '\n') {
                    String result = new String(buf, nextIndex, i - nextIndex);
                    nextIndex = i + 1;
                    return result;
                }
            }
            if (maxIndex - nextIndex > 6000) {
                throw new LineTooLongException();
            } 
            System.arraycopy(buf, nextIndex, buf, 0, maxIndex - nextIndex);
            maxIndex -= nextIndex;
            nextIndex = 0;
            int c = reader.read(buf, maxIndex, buf.length - maxIndex);
            if (c == -1) {
                eof = true;
                return new String(buf, nextIndex, maxIndex - nextIndex);
            } else {
                maxIndex += c;
            }
        }
    }

    @Override
    public void close() throws Exception {
        reader.close();
    }
}

public class Test {

    public static void main(String[] args) throws Exception {
        File file = new File("D:\\t\\output.log");
//      try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) {
//          for (int i = 0; i < 10000000; i++) {
//              fos.write(65);
//          }
//      }

        try (ShortLineReader r = new ShortLineReader(new FileReader(file))) {
            String s;
            while ((s = r.readLine()) != null) {
                System.out.println(s);
            }
        }
    }

}

注意:这假设是unix样式的行终止。

答案 2 :(得分:0)

使用BufferedInputStream读取二进制数据而不是BufferedReader ... 例如,如果它是一个图像文件,使用ImageIO和InputStream就可以这样做..

 File file = new File("image.gif");
image = ImageIO.read(file);

InputStream is = new BufferedInputStream(new FileInputStream("image.gif"));
image = ImageIO.read(is);
希望它有所帮助...

答案 3 :(得分:0)

似乎没有明确的方法,只能做一些事情:

  1. 检查文件标题。 jMimeMagic似乎是一个非常好的库。

  2. 检查文件包含的字符类型。基本上对文件的第一个“x”字节进行统计分析,并使用它来估计其余内容。

  3. 检查文件中的换行符'\ n'或'\ r',二进制文件通常不包含换行符。

  4. 希望有所帮助。