Java:带有缓冲输入的随机访问文件中读取字符串
-
29-09-2019 - |
题
我以前从未有过与Java IO API的亲密经验,现在我真的很沮丧。我发现很难相信它是多么奇怪和复杂,以及完成一项简单任务的难度。
我的任务:我有2个职位(启动字节,结束字节), pos1
和 pos2
. 。我需要读取这两个字节之间的行(包括启动一个字节,不包括结尾),并将它们用作UTF8字符串对象。
例如,在大多数脚本语言中,这将是非常简单的1-2-3 liner(在Ruby中,但对于Python,Perl等,它本质上是相同的):
f = File.open("file.txt").seek(pos1)
while f.pos < pos2 {
s = f.readline
# do something with "s" here
}
爪哇io apis很快就会陷入困境;)实际上,我看到了两种阅读行的方法(以 \n
)来自常规本地文件:
- RandomAccessfile 拥有
getFilePointer()
和seek(long pos)
, ,但这是 readline() 读取非UTF8字符串(甚至不是字节阵列),但是编码损坏的字符串非常奇怪,并且没有缓冲(这可能意味着每个read*()
呼叫将翻译成单个非处方操作系统read()
=>相当慢)。 - BufferedReader 很棒
readLine()
方法,甚至可以与skip(long n)
, ,但是它无法确定已经读过的字节数,也没有提及文件中的当前位置。
我尝试使用类似的东西:
FileInputStream fis = new FileInputStream(fileName);
FileChannel fc = fis.getChannel();
BufferedReader br = new BufferedReader(
new InputStreamReader(
fis,
CHARSET_UTF8
)
);
...然后使用 fc.position()
要获得当前的文件阅读位置, fc.position(newPosition)
设置一个,但在我的情况下似乎不起作用:看起来它返回了通过BufferedReader或类似的东西进行缓冲区预填充的位置 - 这些计数器似乎以16k的增量汇总。
我真的必须一个人自己实现,即读取文件接口,该界面将:
- 允许我在文件中获取/设置位置
- 缓冲文件读取操作
- 允许阅读UTF8字符串(或至少允许“阅读所有内容直到下一本”之类的操作
\n
")
有比我自己实施更快的方法吗?我是在监督什么吗?
解决方案
import org.apache.commons.io.input.BoundedInputStream
FileInputStream file = new FileInputStream(filename);
file.skip(pos1);
BufferedReader br = new BufferedReader(
new InputStreamReader(new BoundedInputStream(file,pos2-pos1))
);
如果你不在乎 pos2
, ,那么您不需要Apache Commons IO。
其他提示
我写了此代码以使用RandomAccessFiles读取UTF-8
//File: CyclicBuffer.java
public class CyclicBuffer {
private static final int size = 3;
private FileChannel channel;
private ByteBuffer buffer = ByteBuffer.allocate(size);
public CyclicBuffer(FileChannel channel) {
this.channel = channel;
}
private int read() throws IOException {
return channel.read(buffer);
}
/**
* Returns the byte read
*
* @return byte read -1 - end of file reached
* @throws IOException
*/
public byte get() throws IOException {
if (buffer.hasRemaining()) {
return buffer.get();
} else {
buffer.clear();
int eof = read();
if (eof == -1) {
return (byte) eof;
}
buffer.flip();
return buffer.get();
}
}
}
//File: UTFRandomFileLineReader.java
public class UTFRandomFileLineReader {
private final Charset charset = Charset.forName("utf-8");
private CyclicBuffer buffer;
private ByteBuffer temp = ByteBuffer.allocate(4096);
private boolean eof = false;
public UTFRandomFileLineReader(FileChannel channel) {
this.buffer = new CyclicBuffer(channel);
}
public String readLine() throws IOException {
if (eof) {
return null;
}
byte x = 0;
temp.clear();
while ((byte) -1 != (x = (buffer.get())) && x != '\n') {
if (temp.position() == temp.capacity()) {
temp = addCapacity(temp);
}
temp.put(x);
}
if (x == -1) {
eof = true;
}
temp.flip();
if (temp.hasRemaining()) {
return charset.decode(temp).toString();
} else {
return null;
}
}
private ByteBuffer addCapacity(ByteBuffer temp) {
ByteBuffer t = ByteBuffer.allocate(temp.capacity() + 1024);
temp.flip();
t.put(temp);
return t;
}
public static void main(String[] args) throws IOException {
RandomAccessFile file = new RandomAccessFile("/Users/sachins/utf8.txt",
"r");
UTFRandomFileLineReader reader = new UTFRandomFileLineReader(file
.getChannel());
int i = 1;
while (true) {
String s = reader.readLine();
if (s == null)
break;
System.out.println("\n line " + i++);
s = s + "\n";
for (byte b : s.getBytes(Charset.forName("utf-8"))) {
System.out.printf("%x", b);
}
System.out.printf("\n");
}
}
}
对于@ken Bloom,Java 7版本很快就可以了。注意:我认为这不是最有效的方法,我仍然在Nio.2围绕Nio。 这里
另请注意,这不是使用Java 7的新ARM语法(它照顾了基于文件的资源的例外处理),它在我拥有的最新OpenJDK构建中无法使用。但是,如果人们想看语法,请告诉我。
/*
* Paths uses the default file system, note no exception thrown at this stage if
* file is missing
*/
Path file = Paths.get("C:/Projects/timesheet.txt");
ByteBuffer readBuffer = ByteBuffer.allocate(readBufferSize);
FileChannel fc = null;
try
{
/*
* newByteChannel is a SeekableByteChannel - this is the fun new construct that
* supports asynch file based I/O, e.g. If you declared an AsynchronousFileChannel
* you could read and write to that channel simultaneously with multiple threads.
*/
fc = (FileChannel)file.newByteChannel(StandardOpenOption.READ);
fc.position(startPosition);
while (fc.read(readBuffer) != -1)
{
readBuffer.rewind();
System.out.println(Charset.forName(encoding).decode(readBuffer));
readBuffer.flip();
}
}
从a开始 RandomAccessFile
并使用 read
或者 readFully
在之间获得一个字节阵列 pos1
和 pos2
. 。假设我们已经将读取的数据存储在一个名称的变量中 rawBytes
.
然后创建您的 BufferedReader
使用
new BufferedReader(new InputStreamReader(new ByteArrayInputStream(rawBytes)))
那你可以打电话 readLine
在 BufferedReader
.
警告:这可能比您可以做的更多记忆使用 BufferedReader
寻求正确的位置本身,因为它将所有内容都预付在记忆中。
我认为混乱是由UTF-8编码和双字节字符的可能性引起的。
UTF8未指定单个字符中的字节有多少个字节。我从您的帖子中假设您正在使用单个字节字符。例如,412个字节意味着411个字符。但是,如果字符串使用双字节字符,您将获得206个字符。
原始的java.io软件包无法很好地处理这种多字节混乱。因此,他们添加了更多的课程来专门处理字符串。该软件包混合了两种不同类型的文件处理程序(它们可能会混淆,直到命名法被整理出来为止)。这 溪流 课程提供直接数据I/O,无需任何转换。这 读者 类将文件转换为字符串,并全力支持多字节字符。这可能有助于阐明部分问题。
由于您说您正在使用UTF-8字符,因此您需要读者类。在这种情况下,我建议Filereader。 FileReader中的Skip()方法允许您传递X字符,然后开始阅读文本。另外,我更喜欢超载read()方法,因为它允许您一次抓取所有文本。
如果您假设自己的“字节”是个体角色,请尝试这样的事情:
FileReader fr = new FileReader( new File("x.txt") );
char[] buffer = new char[ pos2 - pos ];
fr.read( buffer, pos, buffer.length );
...
我在这里参加聚会迟到,但是我在自己的项目中遇到了这个问题。
经过大量的Javadocs和堆栈溢出,我想我找到了一个简单的解决方案。
在寻找随机访问中适当的位置之后,我在这里打电话 raFile
, , 请执行下列操作:
FileDescriptor fd = raFile.getFD();
FileReader fr = new FileReader(fd);
BufferedReader br = new BufferedReader(fr);
那你应该能够打电话 br.readLine()
满足您的内容,这比打电话快得多 raFile.readLine()
.
我不确定的一件事是是否正确处理UTF8字符串。
Java IO API非常灵活。不幸的是,有时灵活性使其详细。这里的主要思想是,有许多流,作家和读者可以实现包装纸。例如,BufferedInputStream包装任何其他InputStream。输出流也是如此。
流和读者/作家之间的区别在于,流在读者/作家使用角色时与字节合作。
幸运的是,某些流,作家和读者具有简化编码的方便构造函数。如果您想阅读文件,您只需要说
InputStream in = new FileInputStream("/usr/home/me/myfile.txt");
if (in.markSupported()) {
in.skip(1024);
in.read();
}
它并不像您害怕那样复杂。
频道是不同的。它是所谓的“新IO”或Nio的一部分。新的IO没有被阻止 - 这是其主要优势。您可以在Internet中搜索任何“ Nio Java教程”,并阅读有关它的内容。但是它比常规IO更复杂,并且对于大多数应用程序而言不需要。