如何使用RandomAccessFile读取UTF8编码的文件?

ken*_*nny 13 java unicode io utf-8 textfield

我有用UTF8编码的文本文件(用于特定于语言的字符).我需要使用RandomAccessFile来寻找特定的位置并从中读取.

我想逐行阅读.

String str = myreader.readLine(); //returns wrong text, not decoded 
String str myreader.readUTF(); //An exception occurred: java.io.EOFException
Run Code Online (Sandbox Code Playgroud)

小智 17

您可以使用以下代码将readLine读取的字符串转换为UTF8:

public static void main(String[] args) throws IOException {
    RandomAccessFile raf = new RandomAccessFile(new File("MyFile.txt"), "r");
    String line = raf.readLine();
    String utf8 = new String(line.getBytes("ISO-8859-1"), "UTF-8");
    System.out.println("Line: " + line);
    System.out.println("UTF8: " + utf8);
}
Run Code Online (Sandbox Code Playgroud)

MyFile.txt的内容:(UTF-8编码)

?????? ?? ???????
Run Code Online (Sandbox Code Playgroud)

控制台输出:

Line: ÐÑÐ¸Ð²ÐµÑ Ð¸Ð· УкÑаинÑ
UTF8: ?????? ?? ???????
Run Code Online (Sandbox Code Playgroud)


Edw*_*rzo 4

API 文档对 readUTF8 做了以下说明

从此文件中读入字符串。该字符串已使用修改后的 UTF-8 格式进行编码。

从当前文件指针开始读取前两个字节,就像通过 readUnsignedShort 一样。该值给出编码字符串中后续字节的数量,而不是结果字符串的长度。随后的字节将被解释为以修改后的 UTF-8 格式编码字符的字节,并转换为字符。

此方法会阻塞,直到读取所有字节、检测到流末尾或引发异常。

你的字符串是这样格式化的吗?

这似乎可以解释您的 EOF 异常。

您的文件是文本文件,因此您的实际问题是解码。

我知道的最简单的答案是:

try(BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("jedis.txt"),"UTF-8"))){

    String line = null;
    while( (line = reader.readLine()) != null){
        if(line.equals("Obi-wan")){
            System.out.println("Yay, I found " + line +"!");
        }
    }
}catch(IOException e){
    e.printStackTrace();
}
Run Code Online (Sandbox Code Playgroud)

或者您可以使用系统属性将当前系统编码设置file.encoding为 UTF-8。

java -Dfile.encoding=UTF-8 com.jediacademy.Runner arg1 arg2 ...
Run Code Online (Sandbox Code Playgroud)

如果您只需要该特定文件,您也可以在运行时将其设置为系统属性System.setProperty(...),但在这种情况下,我认为我更喜欢OutputStreamWriter.

通过设置系统属性,您可以使用FileReaderUTF-8 作为文件的默认编码。在本例中,适用于您读取和写入的所有文件。

如果您打算检测文件中的解码错误,您将被迫使用该InputStreamReader方法并使用接收解码器的构造函数。

有点像

CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
decoder.onMalformedInput(CodingErrorAction.REPORT);
decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
BufeferedReader out = new BufferedReader(new InpuStreamReader(new FileInputStream("jedis.txt),decoder));
Run Code Online (Sandbox Code Playgroud)

您可以在操作之间进行选择IGNORE | REPLACE | REPORT

编辑

如果您坚持使用RandomAccessFile,您将需要知道您要读取的行的确切偏移量。不仅如此,为了使用readUTF()方法读取,您应该使用方法写入文件writeUTF()。因为正如 JavaDocs 上面所述,此方法需要特定的格式,其中前 2 个无符号字节表示 UTF-8 字符串的字节长度。

因此,如果您这样做:

try(RandomAccessFile raf = new RandomAccessFile("jedis.bin", "rw")){

    raf.writeUTF("Luke\n"); //2 bytes for length + 5 bytes
    raf.writeUTF("Obiwan\n"); //2 bytes for length + 7 bytes
    raf.writeUTF("Yoda\n"); //2 bytes for lenght + 5 bytes

}catch(IOException e){
    e.printStackTrace();
}
Run Code Online (Sandbox Code Playgroud)

readUTF()只要您可以确定要读回的给定行的偏移量,使用 方法 从此文件读回就不会有任何问题。

如果您打开该文件,jedis.bin您会发现它是一个二进制文件,而不是文本文件。

现在,我知道"Luke\n"UTF-8 中是 5 个字节,"Obiwan\n"UTF-8 中是 7 个字节。该writeUTF()方法将在每个字符串前面插入 2 个字节。因此,前面"Yoda\n"有(5+2) + (7+2) = 16个字节。

所以,我可以这样做来到达最后一行:

try (RandomAccessFile raf = new RandomAccessFile("jedis.bin", "r")) {

    raf.seek(16);
    String val = raf.readUTF();
    System.out.println(val); //prints Yoda

} catch (IOException e) {
    e.printStackTrace();
}
Run Code Online (Sandbox Code Playgroud)

但是,如果您使用类编写文件,则这将不起作用,Writer因为编写者不遵循该方法的格式设置规则writeUFT()

在这种情况下,最好的办法是二进制文件的格式是所有字符串占用相同的空间量(字节数,而不是字符数,因为字节数在 UTF 中是可变的) 8 取决于字符串中的字符),如果不是所有的空间都需要它,你可以填充它:

这样您就可以轻松计算给定线的偏移量,因为它们都占用相同的空间量。