我正在从文本文件中读取字符的连续行。文件中字符的编码可能不是单字节。
在某些时候,我想获得下一行开始的文件位置,以便我可以稍后重新打开该文件并快速返回 。
是否有一种简单的方法可以同时使用标准Java库?
如果没有,那么什么是合理的解决方法?
理想的解决方案是处理多种字符编码。这包括UTF-8,其中不同的字符可以用不同的字节数表示。理想的解决方案主要依赖于受信任且支持良好的库。最理想的是标准Java库。第二好的是Apache或Google库。解决方案必须是可扩展的。将整个文件读入内存不是一种解决方案。返回某个位置不应要求在线性时间内读取所有先前字符。
对于第一个要求,BufferedReader.readLine()
很有吸引力。但缓冲显然会干扰获得有意义的文件位置。
不太明显,InputStreamReader
也可以提前读取,干扰获取文件位置。来自InputStreamReader documentation:
为了有效地将字节转换为字符,可以从底层流中读取比满足当前读取操作所需的更多字节。
方法RandomAccessFile.readLine()
reads a single byte per character。
通过取字符的低八位的字节值并将字符的高八位设置为零,将每个字节转换为字符。因此,此方法不支持完整的Unicode字符集。
答案 0 :(得分:8)
如果您从BufferedReader
构建FileReader
并保持代码可以访问FileReader
的实例,则应该可以通过调用以下内容获取下一行的位置:
fileReader.getChannel().position();
致电bufferedReader.readLine()
后。
BufferedReader
可以使用大小为1的输入缓冲区构建,如果您愿意为位置精度交换性能增益。
替代解决方案 自己跟踪字节会出现什么问题:
long startingPoint = 0; // or starting position if this file has been previously processed
while (readingLines) {
String line = bufferedReader.readLine();
startingPoint += line.getBytes().length;
}
无论基础标记或缓冲如何,这都可以使字节数精确到您已处理的内容。你必须考虑你的理货中的行结尾,因为它们被剥离了。
答案 1 :(得分:3)
此部分解决方法仅处理使用7位ASCII或UTF-8编码的文件。一般解决方案的答案仍然是可取的(正如批评此解决方案一样)。
在UTF-8中:
总而言之,这两点意味着我们可以读取一行读取字节而不是字符,然后解码该行。
为避免缓冲问题,我们可以使用RandomAccessFile
。该类提供了读取行,获取/设置文件位置的方法。
这里是使用RandomAccessFile将下一行读作UTF-8的代码草图。
protected static String
readNextLineAsUTF8( RandomAccessFile in ) throws IOException {
String rv = null;
String lineBytes = in.readLine();
if ( null != lineBytes ) {
rv = new String( lineBytes.getBytes(),
StandardCharsets.UTF_8 );
}
return rv;
}
然后可以在调用该方法之前立即从RandomAccessFile获取文件位置。给定in
引用的RandomAccessFile:
long startPos = in.getFilePointer();
String line = readNextLineAsUTF8( in );
答案 2 :(得分:3)
案例似乎是由VTD-XML解决的,这是一个能够快速解析大型XML文件的库:
最后一个java VTD-XML ximpleware实现,目前2.13 http://sourceforge.net/projects/vtd-xml/files/vtd-xml/提供了一些代码,用于在每次调用其IReader实现的getChar()方法后保留一个字节偏移量。
VTDGen.java和VTDGenHuge.java中提供了各种caracter编码的IReader实现
为以下编码提供了IReader实现
ASCII;
ISO_8859_1
ISO_8859_10
ISO_8859_11
ISO_8859_12
ISO_8859_13
ISO_8859_14
ISO_8859_15
ISO_8859_16
ISO_8859_2
ISO_8859_3
ISO_8859_4
ISO_8859_5
ISO_8859_6
ISO_8859_7
ISO_8859_8
ISO_8859_9
UTF_16BE
UTF_16LE
UTF8;
WIN_1250
WIN_1251
WIN_1252
WIN_1253
WIN_1254
WIN_1255
WIN_1256
WIN_1257
WIN_1258
答案 3 :(得分:1)
我建议java.io.LineNumberReader
。您可以设置并获取行号,因此可以在某个行索引处继续。
由于它是BufferedReader
,它还能够处理UTF-8。
答案 4 :(得分:1)
解决方案A
其他任何问题都是你必须绝对确保你从未读过EOL角色。
readChar()返回 char 而非字节。所以你不必担心字符宽度。
从此文件中读取字符。此方法从文件中读取两个字节,从当前文件指针开始。
[...]
此方法将一直阻塞,直到读取两个字节,检测到流的末尾,或者抛出异常。
通过使用RandomAccessFile而不是Reader,您放弃了Java为您解码文件中的字符集的能力。 BufferedReader会自动执行此操作。
有几种方法可以解决这个问题。一种是自己检测编码,然后使用正确的read *()方法。另一种方法是使用BoundedInput流。
此问题中有一个Java: reading strings from a random access file with buffered input
答案 5 :(得分:1)
RandomAccessFile has a function: seek(long pos) Sets the file-pointer offset, measured from the beginning of this file, at which the next read or write occurs.
答案 6 :(得分:1)
最初,我发现安迪·托马斯(https://stackoverflow.com/a/30850145/556460)建议的方法最合适。
但遗憾的是,在文件行包含非拉丁字符的情况下,我无法成功将字节数组(取自RandomAccessFile.readLine
)转换为正确的字符串。
所以我通过编写类似于RandomAccessFile.readLine
本身的函数来重写该方法,该函数从行收集数据而不是字符串,而是直接收集字节数组,然后从字节数组构造所需的String。
所以下面的代码完全满足了我的需求(在Kotlin中)。
调用该函数后,file.channel.position()
将返回下一行的确切位置(如果有):
fun RandomAccessFile.readEncodedLine(charset: Charset = Charsets.UTF_8): String? {
val lineBytes = ByteArrayOutputStream()
var c = -1
var eol = false
while (!eol) {
c = read()
when (c) {
-1, 10 -> eol = true // \n
13 -> { // \r
eol = true
val cur = filePointer
if (read() != '\n'.toInt()) {
seek(cur)
}
}
else -> lineBytes.write(c)
}
}
return if (c == -1 && lineBytes.size() == 0)
null
else
java.lang.String(lineBytes.toByteArray(), charset) as String
}