Java:从具有缓冲输入的随机访问文件中读取字符串

我之前从未接触过Java IO API的经验,现在我真的很沮丧。 我发现很难相信它有多奇怪和复杂,做一个简单的任务有多难。

我的任务:我有2个位置(起始字节,结束字节), pos1pos2 。 我需要读取这两个字节之间的行(包括起始字节,不包括结尾字节),并将它们用作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*()调用将被翻译成单个不正确的OS 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,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开始,使用readreadFully获取pos1pos2之间的字节数组。 假设我们已将读取的数据存储在名为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更复杂,并且大多数应用程序都不需要它。