背景
我有一个 InputStream,同时有一个 save(InputStream) 方法将该流所代表的文件上传到文件服务器。
- 该 InputStream 不能再次生成,比如在 Spring 中的 MultipartFile 中(用户上传上来的文件)
- 我们知道 InputStream 也不能被再次读取
- mark reset 的本质是全部读取,大文件肯定不行
那么有没有什么办法既能读取原始 InputStream 中的数据进行检测,又能将原始文件上传到文件服务器呢?
方案1
一个可行的方案是文件中转,先将 MultipartFile 中的流存储到本地,然后本地文件就可以多次生成 InputStream 了。
- 存储到本地 local.data
- 读取 local.data 并检测其内部数据
- 再次用 local.data 生成 InputStream 传递给 save 方法
方案2
方案1肯定是可行的,问题就在于效率比较低,涉及到了好多次的 IO 操作,有没有不转存的方案呢?下面来实现一下:
public class TestStream {
public static void main(String[] args) {
System.out.println("Hello world!");
CheckLineStream checkLineStream = null;
BufferedOutputStream bos = null;
boolean hasError = false;
try {
checkLineStream = new CheckLineStream(new FileInputStream("HasJs.data"), new LineValidator() {
@Override
public boolean validate(String line) {
return !line.contains("/S /JavaScript");
}
});
// 模拟 save 方法,使用 CheckLineStream
bos = new BufferedOutputStream(new FileOutputStream("HasJs-new.data"));
byte[] buffer = new byte[1024];
int len;
while ((len = checkLineStream.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (LineInvalidException e) {
e.printStackTrace();
hasError = true;
} finally {
if (checkLineStream != null) {
try {
checkLineStream.close();
} catch (IOException ignore) {
// ignore
}
}
if (bos != null) {
try {
bos.close();
} catch (IOException ignore) {
// ignore
}
}
}
if (hasError) {
new File("HasJs-new.data").delete();
}
}
/**
* catch LineValidaException to handle error.
*/
public static class CheckLineStream extends InputStream {
private final InputStream inputStream;
private final LineValidator lineValidator;
private StringBuffer sb = new StringBuffer();
public CheckLineStream(InputStream inputStream, LineValidator lineValidator) {
this.inputStream = inputStream;
this.lineValidator = lineValidator;
}
@Override
public int read() throws IOException {
int b = inputStream.read();
if (b == -1) {
return b;
}
char c = (char) b;
if (c == '\n') {
String line = sb.toString();
if (lineValidator != null && !lineValidator.validate(line)) {
throw new LineInvalidException("Invalid line: " + line);
}
sb = new StringBuffer();
} else {
sb.append(c);
}
return b;
}
@Override
public void close() throws IOException {
super.close();
inputStream.close();
}
}
public interface LineValidator {
/**
* Validate line, return true if valid, otherwise return false.
*/
boolean validate(@NotNull String line);
}
public static class LineInvalidException extends RuntimeException {
public LineInvalidException(String message) {
super(message);
}
}
}
参考了 Java IO 流的装饰操作,内部持有原始的流,代理出一个新的流供 svae 使用。
中间如果读满一行则对内容进行检测,检测无问题则继续,有问题就抛出异常。
需要注意的一点就是,最后需要判断是否有异常发生,如果有的话就需要把上传到 save 的文件进行删除操作。
网友评论