您的用例是标准流/阅读器实现中不完全可用的工具的混合体。
特别之处在于您从读取器进程(加密文件的行)开始,然后每一行都必须回退到基于字节的进程(密码操作),并且您希望输出具有字符语义( reader) 传递给 CSV 解析器。这个 char/byte/char 部分不是微不足道的。
我将使用的过程是创建另一个阅读器,我称之为LineByLineProcessingReader,它允许逐行使用输入,然后对其进行处理,然后将每行的输出作为阅读器提供。
这个过程对于这个目的真的无关紧要。您的流程实现将是十六进制解码,然后解密,然后转换回字符串,但它很可能只是任何东西。
棘手的部分在于使流程符合 Reader API。
当符合 Reader API 时,您可以选择扩展 FilterReader 或 Reader 本身。通常是过滤器版本。
我选择不扩展过滤器版本,因为总的来说,我的过程是永远不会暴露原始文件的内容。由于过滤器的实现总是回退到原始读者的实现,因此这种制作将意味着重新实现所有内容,这会产生大量工作。
另一方面,如果我直接覆盖Reader,我只有一个方法可以正确,因为Reader真正要表达的是read(buf, o, c)方法,所有其他的都是在它之上实现的。
我选择实现它的策略类似于在某些数据源上创建自己的Iterator 实现。我预取输入文件的一行,并将其作为Reader 提供在一个内部变量中。
完成后,我只需要确保每次完全读取前一个读取器变量(例如当前行)时始终预取此读取器变量,而不是之前。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
public class LineByLineProcessingReader extends Reader {
/** What to use as a line break. Becuase bufferedReader does not report the line breaks, we insert them manually using this */
final String lineBreak;
/** The original input being read */
final BufferedReader input;
/** A reader for the current processed line. */
private StringReader currentReader;
private boolean closedOrFinished = false;
/**
* Creates a reader that will ingest an input line by line,
* then process it (default implementation is a no-op),
* then recreate a reader for it, until there is no more line.
*
* @param in a Reader object providing the underlying stream.
* @throws NullPointerException if <code>in</code> is <code>null</code>
*/
protected LineByLineProcessingReader(BufferedReader in, String lineBreak) {
this.input = in;
this.lineBreak = lineBreak;
}
public int read(char[] cbuf, int off, int len) throws IOException {
ensureNextLine();
// Check end of input
if (currentReader == null) {
return -1;
}
int read = currentReader.read(cbuf, off, len);
// Edge case : if current reader was at its end
if (read < 0) {
currentReader = null;
// Recurse to go fetch next line.
return read(cbuf, off, len);
}
// General case, we have our result.
// We may have read less than was asked (in length), but it's contractually OK.
return read;
}
/**
* Advances the underlying input to the next line, and makes it available
* for reading inside the {@link #currentReader}
*/
private void ensureNextLine() throws IOException {
// Do not try to read if closed or already finished
if (closedOrFinished) {
return;
}
// Check if there is still data to be read
if (currentReader != null) {
return;
}
String nextLine = input.readLine();
if (nextLine == null) {
// Nothing was left to read, we are bailing out.
currentReader = null;
closedOrFinished = true;
return;
}
// We have a new line, process it and publish it as a reader
String processedLine = processRawLine(nextLine);
currentReader = new StringReader(processedLine+lineBreak);
}
/**
* Performs a process of the raw line read from the underlying source.
* @param rawLine the raw line read
* @return a processed line
*/
protected String processRawLine(String rawLine) {
return rawLine;
}
@Override
public void close() throws IOException {
input.close();
closedOrFinished = true;
}
}
剩下要做的就是将您的解密过程插入processLine 方法中。
对课程的快速测试(您可能需要进一步检查)。
import junit.framework.TestCase;
import org.junit.Assert;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class LineByLineProcessingReaderTest extends TestCase {
public void testRead() throws IOException {
String input = "a\nb";
// Reading a char one by one
try (Reader r = new LineByLineProcessingReader(new BufferedReader(new StringReader(input)), "\n")) {
String oneByOne = readExcatlyCharByChar(r, 3);
Assert.assertEquals(input, oneByOne);
}
// Reading lines
List<String> lines = readAllLines(
new LineByLineProcessingReader(
new BufferedReader(new StringReader(input)),
"\n"
)
);
Assert.assertEquals(Arrays.asList("a", "b"), lines);
String[] moreComplexInput = new String[] {"Two households, both alike in dignity",
"In fair Verona, where we lay our scene",
"From ancient grudge break to new mutiny",
"Where civil blood makes civil hands unclean." +
"From forth the fatal loins of these two foes" +
"A pair of star-cross'd lovers take their life;" +
"Whose misadventured piteous overthrows" +
"Do with their death bury their parents' strife." +
"The fearful passage of their death-mark'd love",
"And the continuance of their parents' rage",
"Which, but their children's end, nought could remove",
"Is now the two hours' traffic of our stage;" +
"The which if you with patient ears attend",
"What here shall miss, our toil shall strive to mend."};
lines = readAllLines(new LineByLineProcessingReader(
new BufferedReader(new StringReader(String.join("\n", moreComplexInput))), "\n") {
@Override
protected String processRawLine(String rawLine) {
return rawLine.toUpperCase();
}
});
Assert.assertEquals(Arrays.stream(moreComplexInput).map(String::toUpperCase).collect(Collectors.toList()), lines);
}
private String readExcatlyCharByChar(Reader reader,int numberOfReads) throws IOException {
int nbRead = 0;
try (StringWriter output = new StringWriter()) {
while (nbRead < numberOfReads) {
int read = reader.read();
if (read < 0) {
throw new IOException("Expected " + numberOfReads + " but were only " + nbRead + " available");
}
output.write(read);
nbRead++;
}
return output.toString();
}
}
private List<String> readAllLines(Reader reader) throws IOException {
try (BufferedReader b = new BufferedReader(reader)) {
return b.lines().collect(Collectors.toList());
}
}
}