我在磁盘上有一个(文本)文件,我需要将其读入一个带有Reader对象的库中。
在阅读此文件时,我想对数据执行正则表达式字符串替换。
我目前的解决方案是将整个文件作为一个String读入内存,执行String替换,然后为此String创建一个StringReader,并将其作为Reader传递回库中。
这适用于大文件(特别是在多个线程中运行),性能是一个问题。
我想做的是让它一次从文件中读取每一行,替换为此子字符串,然后默默地返回给读者的消费者 - 但我不能想到如何做此
有没有更好的方法来完成这项任务?
我正在使用Java 7
我目前解决方案的一个示例如下 - 从'文件'中取代所有'' s然后将流传递到消费者。
public void loadFile(final File file) throws Exception
{
final Pattern regexPattern = Pattern.compile("a");
final String replacementString = "b";
try (BufferedReader cleanedBufferedReader = new BufferedReader(new StringReader(replaceInBufferedReader(new BufferedReader(new FileReader(file)),
regexPattern, replacementString))))
{
new StreamSource(cleanedBufferedReader).doSomething();
}
}
private static String replaceInBufferedReader(final BufferedReader reader, final Pattern pattern, final String replacement) throws IOException
{
final StringBuilder builder = new StringBuilder();
String str;
while ((str = reader.readLine()) != null)
{
builder.append(str).append(System.lineSeparator());
}
return pattern.matcher(builder.toString()).replaceAll(replacement);
}
答案 0 :(得分:3)
您只想将BufferedReader子类化。
class MyBufferedReader extends BufferedReader {
MyBufferedReader(Reader r) {
super(r);
}
@Override
String readLine() {
String line = super.readLine();
// perform replacement here
return line;
}
}
像往常一样打开文件,但不是将其包装在BufferedReader中,而是将其包装在子类中。
try ( Reader r = ...;
BufferedReader br = new MyBufferedReader(r)) {
String line;
while ((line = br.readLine()) != null) {
// use returned line
}
}
<强>更新强>
以下是Reader
,它允许您逐行替换输入流,同时仍向流的用户提供Reader
接口。
在内部,原始流包裹在BufferedReader
中,并且一次读取一行。可以对已经读取的行执行任何期望的变换。然后将变换后的线变为StringReader
。当流的用户调用任何read(...)
操作时,请求将被定向到缓冲的StringReader
以满足。如果StringReader
字符用完,则会加载并转换BufferedReader
的下一行,以继续为read(...)
提供输入。
abstract public class TranslatingReader extends Reader {
private BufferedReader input;
private StringReader output;
public TranslatingReader(Reader in) {
input = new BufferedReader(in);
output = new StringReader("");
}
abstract public String translate(String line);
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
int read = 0;
while (len > 0) {
int nchars = output.read(cbuf, off, len);
if (nchars == -1) {
String line = input.readLine();
if (line == null) {
break;
}
line = tranlate(line);
line += "\n"; // Add the newline which was removed by readLine()
output = new StringReader(line);
} else {
read += nchars;
off += nchars;
len -= nchars;
}
}
if (read == 0)
read = -1;
return read;
}
@Override
public void close() throws IOException {
input.close();
output.close();
}
}
答案 1 :(得分:0)
我希望您拥有的文件不是单一的,因为您使用的是字符阅读器 Reader
。如果数据不是单片的,那么它必须有一些分隔符将文件分成记录。通常这些分隔符是换行符和/或回车符以形成文本行&#39;记录。
根据分隔符将数据拆分为记录,并通过正则表达式传递每条记录。如果是文本行,您可以使用BufferedReader.readLine()
答案 2 :(得分:0)
没有额外覆盖的另一个想法是将Scanner
与您的模式一起用作自定义分隔符。这不会立即读取整个文件,而是在每次迭代时只读取给定模式的部分。非常记忆有效。可能是这样的(你可以根据自己的需要增强它):
PS关于#performance: 我认为这种方法甚至比逐行盲读更有效!有些情况例如:
\n
s。这可以通过错误的导出到文件或在信息检索期间实现)随意看看这个替代解决方案↓
private static String replaceInBufferedReader(String pathToFile){
File some = new File("some.txt");
StringBuilder sb = new StringBuilder();
String replacementString = "b";
String delimiter = "x"; // you can use pattern or regex
try {
// set Scanner's delimiter to the pattern you wanna replace
Scanner sc = new Scanner(some).useDelimiter(delimiter);
while (sc.hasNext()) {
sb.append(sc.next()).append(replacementString);
}
sc.close();
}
catch (FileNotFoundException e) {
e.printStackTrace();
}
return sb.toString(); // or maybe save to new file
}
我用一个8MB的文本文件对它进行了测试,这对它来说是件小事。我使用Writer将其保存为新文件,而不是返回sb.toString()
...
try {
Files.write(Paths.get("some2.txt"),
sb.toString().getBytes(),
StandardOpenOption.CREATE);
}
catch (IOException e) {
e.printStackTrace();
}