如何使用RandomAccessFile读取UTF8编码的文件?
我有用UTF8编码的文本文件(用于特定于语言的字符)。 我需要使用RandomAccessFile来寻找特定的位置并从中读取。
我想逐行阅读。
String str = myreader.readLine(); //returns wrong text, not decoded String str myreader.readUTF(); //An exception occurred: java.io.EOFException
您可以使用以下代码将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); }
MyFile.txt的内容:(UTF-8编码)
Привет из Украины
控制台输出:
Line: ÐÑÐ¸Ð²ÐµÑ Ð¸Ð· УкÑÐ°Ð¸Ð½Ñ UTF8: Привет из Украины
API文档为readUTF8说了以下内容
从此文件中读取字符串。 该字符串已使用修改的UTF-8格式进行编码。
从当前文件指针开始读取前两个字节,就像readUnsignedShort一样。 此值给出编码字符串中的后续字节数,而不是结果字符串的长度。 然后将以下字节解释为以修改的UTF-8格式编码字符的字节,并将其转换为字符。
此方法将阻塞,直到读取所有字节,检测到流的末尾,或者抛出exception。
你的字符串是这样格式化的吗?
这似乎可以解释你的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(); }
或者,您可以使用系统属性file.encoding
将当前系统编码设置为UTF-8。
java -Dfile.encoding=UTF-8 com.jediacademy.Runner arg1 arg2 ...
如果你只需要这个特定的文件,你也可以在运行时使用System.setProperty(...)
将它设置为系统属性,但在这种情况下,我想我更喜欢OutputStreamWriter
。
通过设置系统属性,您可以使用FileReader
并期望它将使用UTF-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));
您可以选择IGNORE | REPLACE | REPORT
之间的操作 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(); }
只要您可以确定要读回的给定行的偏移量,就不应该使用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(); }
但是如果您使用Writer
类编写文件,这将无效,因为编写器不遵循方法writeUFT()
的格式规则。
在这种情况下,最好的是你的二进制文件的格式将使所有字符串占用相同的空间量(字节数,而不是字符数,因为字节数在UTF中是可变的 – 8取决于你的字符串中的字符),如果不是所有空间都需要你填写它:
这样,您可以轻松计算给定线的偏移量,因为它们都将占用相同的空间量。
你不可能这样做。 seek
函数将为您定位一些字节数。 无法保证您与UTF-8字符边界对齐。
通过readLine()读取文件对我有用:
RandomAccessFile raf = new RandomAccessFile( ... ); String line; while ((line = raf.readLine()) != null) { String utf = new String(line.getBytes("ISO-8859-1")); ... } // my file content has been created with: raf.write(myStringContent.getBytes());
我发现RandomAccessFile
的API具有挑战性。
如果你的文本实际上限制为UTF-8值0-127(UTF-8的最低7位),那么使用readLine()
是安全的,但仔细阅读那些Javadoc:这是一种奇怪的方法。 去引用:
此方法从文件开始连续读取字节,从当前文件指针开始,直到它到达行终止符或文件末尾。 通过取字符的低八位的字节值并将字符的高八位设置为零,将每个字节转换为字符。 因此,此方法不支持完整的Unicode字符集。
为了安全地读取UTF-8,我建议您使用length()
和read(byte[])
的组合读取(部分或全部)原始字节。 然后使用以下构造函数将UTF-8字节转换为Java String
: new String(byte[], "UTF-8")
。
要安全地编写UTF-8,首先使用someText.getBytes("UTF-8")
将Java String
转换为正确的字节。 最后,使用write(byte[])
。
我意识到这是一个古老的问题,但它似乎仍有一些兴趣,而且没有被接受的答案。
您所描述的内容本质上是一个数据结构问题。 这里对UTF8的讨论是一个红色的鲱鱼 – 使用固定长度编码(如ASCII)会遇到同样的问题,因为你有可变长度的行。 你需要的是某种索引。
如果你绝对无法改变文件本身(“字符串文件”) – 似乎是这种情况 – 你总是可以构造一个外部索引。 第一次(也是第一次)访问字符串文件时,您将一直读取(顺序),记录每行开头的字节位置,并通过记录文件结束位置结束(让生活更简单)。 这可以通过以下代码实现:
myList.add(0); // assuming first string starts at beginning of file while ((line = myRandomAccessFile.readLine()) != null) { myList.add(myRandomAccessFile.getFilePointer()); }
然后将这些整数写入一个单独的文件(“索引文件”),您将在以后的每个启动程序并打算访问该字符串文件时读回该文件。 要访问第n
个字符串,请从索引文件中选择第n
和第n+1
个索引(称为A
和B
)。 然后,您可以在字符串文件中查找A
并读取BA
字节,然后从UTF8进行解码。 例如,要获得第i
行:
myRandomAccessFile.seek(myList.get(i)); byte[] bytes = new byte[myList.get(i+1) - myList.get(i)]; myRandomAccessFile.readFully(bytes); String result = new String(bytes, "UTF-8");
但是,在许多情况下,最好使用SQLite这样的数据库,它可以为您创建和维护索引。 这样,您可以添加和修改额外的“行”,而无需重新创建整个索引。 有关Java实现,请参阅https://www.sqlite.org/cvstrac/wiki?p=SqliteWrappers 。
RandomAccessFile的readUTF()方法将当前指针的前两个字节视为从当前位置的两个字节后的字节大小,以字符串forms读取并返回。
为了使此方法起作用,应使用writeUTF()方法编写内容,因为它使用当前位置之后的前两个字节来保存内容大小,然后写入内容。 否则,大多数时候你会得到EOFException。
有关详细信息,请参见http://www.zoftino.com/java-random-access-files 。