2012-04-01 85 views
11

我有一個用UTF8(語言特定字符)編碼的文本文件。 我需要使用的RandomAccessFile尋求特定位置和讀取。如何使用RandomAccessFile讀取UTF8編碼的文件?

我想讀行由行。

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

回答

4

的API文檔說以下爲readUTF8

讀取來自該文件中的字符串。該字符串已使用修改後的UTF-8格式編碼爲 。

讀取前兩個字節,從當前文件指針 開始讀取,就像readUnsignedShort一樣。此值給出編碼字符串中的以下 字節的編號,而不是生成的 字符串的長度。接下來的字節將被解釋爲編碼爲 字符的字節,並且被轉換爲 字符。

該方法會阻塞,直到讀取所有字節,檢測到流 的末端或引發異常。

是您的字符串,這樣格式化?

這似乎說明你EOF exceptuon。

你的文件是一個文本文件,因此您的實際問題是解碼。

我知道最簡單的回答是:

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)); 

您可以使用RandomAccessFile行動IGNORE | REPLACE | REPORT

編輯

如果你堅持之間選擇,你需要知道該行的確切的偏移,你正打算閱讀。不僅如此,爲了用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中是可變的,這取決於字符串中的字符),如果不是所有的空間都需要它,你可以使用它:

這樣你就可以很容易地計算給定行的偏移量,因爲它們都佔據相同的空間量。

+0

我創建使用的BufferedWriter(新OutputStreamWriter(新FileOutputStream中(..),編碼),其中編碼是UTF8 – kenny 2012-04-01 14:26:11

+1

然後OU不能使用這個文本文件RandomAccessFile來讀取它,你必須使用BufferedReader或FileReader這樣的閱讀器類,並從頭開始閱讀,直到你到達 – 2012-04-01 14:42:49

+1

這一行,這是不高效的,我使用seek來執行分頁。我每次重讀整個文件 – kenny 2012-04-01 15:44:06

3

你不可能這樣做。 seek函數將定位您的一些字節數。無法保證您與UTF-8字符邊界對齊。

+0

,如果我使用建議的參數java -Dfile.encoding = UTF-8? – kenny 2012-04-01 15:16:02

+2

@kenny UTF-8編碼對可變字節數的字符進行編碼,因此跳到文件內的字節偏移量可能會失敗(因爲@tchrist提到過),當您處於字符邊界的開始時,您可能不會到達那裏。如果您知道需要的字符偏移量,可以使用'Reader.skip(long n)'來跳過字符數。這應該是編碼意識。只要確保在'InputStreamReader'上設置你的字符集。 – 2012-04-01 15:44:37

+2

查找UTF-8中的下一個字符很容易。只需跳過[0x80-0xBF]中的所有字節,不在該範圍內的第一個字符將成爲字符的開頭。 (這是Ken Thompson在UTF-8中添加的自同步屬性)。 – ninjalj 2012-04-02 18:52:51

0

我覺得RandomAccessFile的API具有挑戰性。

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

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

要讀取UTF-8安全,我建議你閱讀(部分或全部)的原始字節與length()read(byte[])的組合。然後使用此構造函數將您的UTF-8字節轉換爲Java Stringnew String(byte[], "UTF-8")

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

14

您可以將字符串轉換,通過的readLine讀UTF-8,使用下面的代碼:(UTF8編碼)

Привет из Украины 

控制檯輸出:

MyFile.txt的的

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); 
} 

內容
Line: ÐÑÐ¸Ð²ÐµÑ Ð¸Ð· УкÑÐ°Ð¸Ð½Ñ 
UTF8: Привет из Украины 

+0

感謝您發佈您的解決方案。你能解釋爲什麼'字符串UTF8 =新字符串(Line.getBytes(「UTF-8」),「UTF-8」);'不工作? – thomasb 2016-02-09 14:15:13

+0

@thomasb'getBytes(「UTF-8」)'將轉換內部字節數組。 「ISO-8859-1」是「原始」編碼。 – Matthieu 2017-01-23 15:23:46

0

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

你所描述的實質上是一個數據結構問題。這裏討論的UTF8是一個紅色的鯡魚 - 你會面對同樣的問題,使用固定長度的編碼,如ASCII,因爲你有可變長度的線。你需要的是某種索引。

如果你絕對不能改變文件本身(「字符串文件」) - 似乎是這種情況 - 你總是可以構造一個外部索引。第一次(和第一次只有)字符串文件被訪問,你一直讀取它(順序),記錄每行開始的字節位置,並通過記錄文件結束位置(使生活更簡單)。這可以通過下面的代碼來實現:

myList.add(0); // assuming first string starts at beginning of file 
while ((line = myRandomAccessFile.readLine()) != null) { 
    myList.add(myRandomAccessFile.getFilePointer()); 
} 

,然後寫這些整數到一個單獨的文件(「索引文件」),你會讀回以後每一次啓動程序,並打算訪問字符串文件。要訪問第012個字符串,請從索引文件中選取n th和n+1 th索引(稱這些索引文件爲AB)。然後,您嘗試將A定位在字符串文件中,並讀取B-A字節,然後您可以從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

1

讀通過的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()); 
相關問題