7

我以前从未有过使用 Java IO API 的亲密体验,现在我真的很沮丧。我很难相信它是多么的奇怪和复杂,做一个简单的任务是多么的困难。

我的任务:我有 2 个位置(起始字节、结束字节)pos1pos2. 我需要读取这两个字节之间的行(包括开始一个,不包括结束一个)并将它们用作 UTF8 String 对象。

例如,在大多数脚本语言中,它会是一个非常简单的 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()and seek(long pos),但它的readLine()读取非 UTF8 字符串(甚至不是字节数组),但非常奇怪的字符串编码损坏,并且它没有缓冲(这可能意味着每个read*()调用都将被转换为单个 undelying 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”之类的操作)

有没有比自己实施更快的方法?我在监督什么吗?

4

7 回答 7

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。

于 2010-11-29T15:57:46.147 回答
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())) &amp;&amp; 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");

    }
}
}
于 2011-04-14T09:26:26.090 回答
1

对于@Ken Bloom 快速了解 Java 7 版本。注意:我认为这不是最有效的方法,我仍然对 NIO.2 有所了解,Oracle 已经在这里开始了他们的教程

另请注意,这没有使用 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();
    }
}
于 2010-11-29T16:45:12.690 回答
0

从 a 开始RandomAccessFile,使用reador来获取和readFully之间的字节数组。假设我们已将读取的数据存储在名为 的变量中。pos1pos2rawBytes

然后创建您的BufferedReader使用

new BufferedReader(new InputStreamReader(new ByteArrayInputStream(rawBytes)))

然后就可以调用readLineBufferedReader

警告:这可能比你自己BufferedReader寻找正确的位置使用更多的内存,因为它会将所有内容预加载到内存中。

于 2010-11-29T15:43:02.280 回答
0

我认为混淆是由 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 );
...
于 2010-11-29T15:49:53.683 回答
0

我在这里聚会迟到了,但我在自己的项目中遇到了这个问题。

在对 Javadocs 和 Stack Overflow 进行了多次遍历之后,我想我找到了一个简单的解决方案。

在找到我在这里调用的 RandomAccessFile 中的适当位置后raFile,执行以下操作:

FileDescriptor fd = raFile.getFD();
FileReader     fr = new FileReader(fd);
BufferedReader br = new BufferedReader(fr);

然后你应该可以随心所欲地打电话br.readLine()了,这比打电话要快得多raFile.readLine()

我不确定的一件事是 UTF8 字符串是否被正确处理。

于 2014-07-13T15:01:14.500 回答
-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 更复杂,大多数应用程序都不需要它。

于 2010-11-29T15:30:26.370 回答