解析文件的方式有两种,一种是流式解析,一种是对象式解析(开源的文件解析库采用指针式解析的不多,暂不讨论)。
对于复杂文件,更多的是用对象式解析,那么当这个文件足够大的时候,就会发生OOM。
对于用户操作的文件,有时并不能直接限制文件大小(比如在非主要业务中的操作),有时需要对内容做截断处理(比如只允许1G,只忽略超出的内容)。
在这种业务场景下,就需要对解析进行限制内存。那如何限制呢?
我大概有两种思路:
可以解析完再截断,如果解析过程中OOM,视为不可解析。
采用不可变令牌桶,发完令牌视为文件读取完,不在允许解析。
对于第一种做法来说,会有一个非常严重的问题:只支持单线程。
为什么只支持单线程呢?
假设我们有这样的一个例子:
每个文件允许1G,JAVA虚拟机有4G堆内存,有两个线程。
线程A读取文件,文件长5G,当读到3.5G时,恰好线程B读一个1G的文件,读到了700M。
在这个条件下,系统会OOM,线程A、B都会收到并忽略。但是线程B的1G大小的文件是合法的,不是吗?
除此之外,无论结果对错,系统都多下了非常多的数据,对吧
那对于第二种思路怎么实现呢?答案很简单,只要用装饰器模式装饰一下就可以了~
附Java的实现代码如下:
public class SizeLimitInputStream extends InputStream {
private long maxCharCount;
private InputStream inputStream;
private long readedCharCount = 0L;
public SizeLimitInputStream(InputStream inputStream, int maxDocumentMBSize) {
this.inputStream = inputStream;
this.maxCharCount = maxDocumentMBSize * 1024L * 1024L;
}
@Override
public int read() throws IOException {
if (readedCharCount < maxCharCount) {
return updateCountAndReturn(inputStream.read());
} else {
return -1;
}
}
@Override
public int read(byte[] b) throws IOException {
long length = Long.min(maxCharCount - readedCharCount, b.length);
if (length > 0) {
return updateCountAndReturn(inputStream.read(b, 0, (int) length));
} else {
return -1;
}
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
long length = Long.min(maxCharCount - readedCharCount, len);
if (length > 0) {
return updateCountAndReturn(inputStream.read(b, off, (int) length));
} else {
return -1;
}
}
@Override
public long skip(long n) throws IOException {
return inputStream.skip(n);
}
@Override
public int available() throws IOException {
return inputStream.available();
}
@Override
public void close() throws IOException {
inputStream.close();
}
@Override
public synchronized void mark(int readlimit) {
inputStream.mark(readlimit);
}
@Override
public synchronized void reset() throws IOException {
inputStream.reset();
}
@Override
public boolean markSupported() {
return inputStream.markSupported();
}
private int updateCountAndReturn(int count) {
readedCharCount += count;
return count;
}
}