我以前从未有过与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
}

爪哇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())) &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");

    }
}
}

对于@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 在之间获得一个字节阵列 pos1pos2. 。假设我们已经将读取的数据存储在一个名称的变量中 rawBytes.

然后创建您的 BufferedReader 使用

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

那你可以打电话 readLineBufferedReader.

警告:这可能比您可以做的更多记忆使用 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更复杂,并且对于大多数应用程序而言不需要。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top