我以前从未接触过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
结尾)的方法:
getFilePointer()
和seek(long pos)
,但它是readLine()读取非UTF8字符串(甚至不是字节数组),但编码损坏的字符串很奇怪,它没有缓冲(这可能意味着每个read*()
调用将被转换为单个不正常的操作系统read()
=&gt;相当慢。)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为增量进行四舍五入。
我是否真的必须自己实现它,即文件阅读器界面:
\n
”等操作)有没有比自己实施更快的方法?我在监督什么吗?
答案 0 :(得分:6)
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。
答案 1 :(得分:6)
我使用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");
}
}
}
答案 2 :(得分:1)
对于@Ken Bloom快速浏览Java 7版本。注意:我认为这不是最有效的方法,我仍然在理解NIO.2,Oracle已经开始了他们的教程here
另请注意,这不是使用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();
}
}
答案 3 :(得分:0)
从RandomAccessFile
开始,使用read
或readFully
获取pos1
和pos2
之间的字节数组。假设我们已将读取的数据存储在名为rawBytes
的变量中。
然后使用
创建BufferedReader
new BufferedReader(new InputStreamReader(new ByteArrayInputStream(rawBytes)))
然后,您可以在readLine
上致电BufferedReader
。
警告:这可能会占用更多内存,而不是让BufferedReader
寻找到正确的位置本身,因为它会将所有内容预先加载到内存中。
答案 4 :(得分:0)
我认为混淆是由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 );
...
答案 5 :(得分:0)
我来这里参加派对很晚,但我在自己的项目中遇到了这个问题。
经过Javadocs和Stack Overflow的大量遍历后,我想我找到了一个简单的解决方案。
在我在这里调用raFile
的RandomAccessFile中寻找合适的位置后,请执行以下操作:
FileDescriptor fd = raFile.getFD();
FileReader fr = new FileReader(fd);
BufferedReader br = new BufferedReader(fr);
然后,您应该可以拨打br.readLine()
内容,这比调用raFile.readLine()
要快得多。
我不确定的一件事是UTF8字符串是否被正确处理。
答案 6 :(得分:-1)
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更复杂,大多数应用程序都不需要。