Java:从具有缓冲输入的随机访问文件中读取字符串
我之前从未接触过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 }
Java IO API很快就会出现问题;)实际上,我看到了两种从常规本地文件中读取行(以\n
结尾)的方法:
- RandomAccessFile有
getFilePointer()
和seek(long pos)
,但它的readLine()读取非UTF8字符串(甚至不是字节数组),但编码破坏的字符串非常奇怪,并且没有缓冲(这可能意味着每次read*()
调用将被翻译成单个不正确的OSread()
=>相当慢)。 - 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,Oracle已经在这里开始了他们的教程
另请注意,这不是使用Java 7的新ARM语法(它处理基于文件的资源的exception处理),它不能在我拥有的最新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, eg 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(); } }
从RandomAccessFile
开始,使用read
或readFully
获取pos1
和pos2
之间的字节数组。 假设我们已将读取的数据存储在名为rawBytes
的变量中。
然后使用创建BufferedReader
new BufferedReader(new InputStreamReader(new ByteArrayInputStream(rawBytes)))
然后你可以在BufferedReader
上调用readLine
。
警告:这可能会占用更多的内存,因为它可以让BufferedReader
寻找合适的位置,因为它会将所有内容预先加载到内存中。
我认为混淆是由UTF-8编码和双字节字符的可能性引起的。
UTF8不指定单个字符中有多少字节。 我假设您使用的是单字节字符。 例如,412个字节意味着411个字符。 但是如果字符串使用双字节字符,那么你将获得206个字符。
最初的java.io包没有很好地处理这种多字节混淆。 因此,他们添加了更多类来专门处理字符串。 该软件包混合了两种不同类型的文件处理程序(在命名法被整理出来之前,它们可能会混淆)。 流类提供直接数据I / O而无需任何转换。 reader类将文件转换为完全支持多字节字符的字符串。 这可能有助于澄清部分问题。
既然您声明使用的是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和Stack Overflow的大量遍历之后,我想我找到了一个简单的解决方案。
在寻找我在这里调用raFile
RandomAccessFile中的适当位置后,执行以下操作:
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没有被阻止 – 这是它的主要优势。 您可以在互联网上搜索任何“nio java教程”并阅读它。 但它比常规IO更复杂,并且大多数应用程序都不需要它。