簡體   English   中英

如何使用 RandomAccessFile 讀取 UTF8 編碼的文件?

[英]How to read UTF8 encoded file using RandomAccessFile?

我有用 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 格式中的字節編碼字符並轉換為字符。

此方法會阻塞,直到讀取所有字節、檢測到流的結尾或引發異常。

你的字符串是這樣格式化的嗎?

這似乎可以解釋您的 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 字符邊界對齊。

一旦您定位在給定的行上(這意味着您已經回答了問題的第一部分,請參閱@martinjs 答案),您可以閱讀整行並使用@Matthieu 的答案中給出的語句從中創建一個String . 但是要檢查有問題的陳述是否正確,我們必須問自己 4 個問題。 這不是不言而喻的。

請注意,如果您需要隨機快速訪問多行,則獲取行首的問題可能需要分析文本以構建索引。

讀取一行並將其轉換為String的語句是:

String utf8 = new String(raf.readLine().getBytes("ISO-8859-1"), "UTF-8");
  1. 什么是 UTF-8 中的字節? 這意味着允許哪些值。 一旦我們回答了問題 2,我們就會發現這個問題實際上毫無用處。
  2. readLine() UTF-8 字節 → UTF-16 字節可以嗎? 是的。 因為如果最高有效字節 (MSB) 為 0,則 UTF-16 會賦予從 0 到 255 的所有整數以 2 個字節編碼的含義。 readLine()保證了這一點。
  3. getBytes("ISO-8859-1") 以 UTF-16 編碼的char (每個字符有 1 或 2 個char (代碼單元)的 Java String )→ ISO-8859-1 字節好嗎? 是的。 Java 字符串中字符的代碼點≤ 255,ISO-8859-1 是“原始”編碼,這意味着它可以將每個字符編碼為單個字節。
  4. new String(..., "UTF-8") ISO-8859-1 字節 → UTF-8 字節可以嗎? 是的。 由於原始字節來自 UTF-8 編碼文本並已按原樣提取,因此它們仍代表以 UTF-8 編碼的文本。

關於 ISO-8859-1 的原始性質,其中每個字節(值 0 到 255)都映射到一個字符上,我在@Matthieu 對答案的評論下方復制/粘貼。

請參閱有關 ISO-8859-1 的“原始”編碼概念的問題 請注意 ISO/IEC 8859-1(定義了 191 個字節)和 ISO-8859-1(定義了 256 個字節)之間的區別。 您可以在RFC1345 中找到 ISO-8859-1 的定義,並看到控制代碼 C0 和 C1 被映射到 ISO/IEC 8859-1 的 65 個未使用字節上。

我意識到這是一個老問題,但它似乎仍然有一些興趣,並且沒有被接受的答案。

你所描述的本質上是一個數據結構問題。 此處對 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個索引(稱為AB )。 然后您在字符串文件中尋找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

通過 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 的 readUTF() 方法將當前指針的前兩個字節視為字節大小,在當前位置的兩個字節之后,將被讀取並作為字符串返回。

為了使此方法起作用,應使用 writeUTF() 方法寫入內容,因為它使用當前位置后的前兩個字節來保存內容大小,然后寫入內容。 否則,大多數時候你會得到 EOFException。

有關詳細信息,請參閱http://www.zoftino.com/java-random-access-files

我發現RandomAccessFile的 API 很有挑戰性。

如果您的文本實際上僅限於 UTF-8 值 0-127(UTF-8 的最低 7 位),那么使用readLine()是安全的,但請仔細閱讀這些 Javadoc:這是一種奇怪的方法。 報價:

此方法從文件中連續讀取字節,從當前文件指針開始,直到到達行終止符或文件末尾。 通過取字符低八位的字節值並將字符的高八位設置為零,將每個字節轉換為字符。 因此,此方法不支持完整的 Unicode 字符集。

為了安全地讀取 UTF-8,我建議您使用length()read(byte[])的組合讀取(部分或全部)原始字節。 然后使用以下構造函數將您的 UTF-8 字節轉換為 Java Stringnew String(byte[], "UTF-8")

要安全地編寫 UTF-8,首先使用someText.getBytes("UTF-8")將您的 Java String轉換為正確的字節。 最后,使用write(byte[])

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM