2012-04-01 53 views
11

Tôi có tệp văn bản được mã hóa bằng UTF8 (dành cho các ký tự dành riêng cho ngôn ngữ). Tôi cần sử dụng RandomAccessFile để tìm kiếm vị trí cụ thể và đọc từ đó.Cách đọc tệp được mã hóa UTF8 bằng cách sử dụng RandomAccessFile?

Tôi muốn đọc từng dòng một.

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

Trả lời

4

Các tài liệu API nói như sau cho readUTF8

Đọc trong một chuỗi từ tập tin này. Chuỗi đã được mã hóa bằng cách sử dụng định dạng UTF-8 được sửa đổi .

Hai byte đầu tiên được đọc, bắt đầu từ con trỏ tệp hiện tại, như thể bởi readUnsignedShort. Giá trị này cung cấp số lượng sau đây byte nằm trong chuỗi được mã hóa, không phải là độ dài của chuỗi kết quả. Các byte sau đây được hiểu là mã hóa byte ký tự theo định dạng UTF-8 đã sửa đổi và được chuyển đổi thành ký tự.

Phương pháp này chặn cho đến khi tất cả các byte được đọc, kết thúc của luồng được phát hiện hoặc ngoại lệ được ném.

Chuỗi của bạn có được định dạng theo cách này không?

Điều này dường như giải thích ngoại trừ EOF của bạn.

Tệp của bạn là tệp văn bản để sự cố thực sự của bạn là giải mã.

Câu trả lời đơn giản nhất tôi biết là:

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

Hoặc bạn có thể thiết lập mã hóa hệ thống hiện tại với hệ thống sở hữu file.encoding sang UTF-8.

java -Dfile.encoding=UTF-8 com.jediacademy.Runner arg1 arg2 ... 

Bạn cũng có thể thiết lập nó như một thuộc tính hệ thống khi chạy với System.setProperty(...) nếu bạn chỉ cần nó cho tập tin cụ thể này, nhưng trong một trường hợp như thế này, tôi nghĩ rằng tôi sẽ thích OutputStreamWriter.

Bằng cách đặt thuộc tính hệ thống, bạn có thể sử dụng FileReader và hy vọng rằng nó sẽ sử dụng UTF-8 làm mã hóa mặc định cho tệp của bạn. Trong trường hợp này cho tất cả các tập tin mà bạn đọc và viết.

Nếu bạn có ý định phát hiện lỗi giải mã trong tệp của mình, bạn sẽ bị buộc sử dụng phương pháp InputStreamReader và sử dụng hàm tạo nhận bộ giải mã.

Hơi như

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

Bạn có thể lựa chọn giữa các hành động IGNORE | REPLACE | REPORT

EDIT

Nếu bạn nhấn mạnh trong việc sử dụng RandomAccessFile, bạn sẽ cần phải biết chính xác bù đắp của đường mà bạn đang có ý định đọc.Và không chỉ vậy, để đọc với phương thức readUTF(), bạn nên viết tệp bằng phương thức writeUTF(). Bởi vì phương thức này, như JavaDocs đã nói ở trên, mong đợi một định dạng cụ thể trong đó 2 byte chưa ký đầu tiên đại diện cho độ dài theo byte của chuỗi UTF-8.

Như vậy, nếu bạn làm:

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

Bạn không nên có bất kỳ vấn đề đọc lại từ tập tin này bằng cách sử dụng phương pháp readUTF(), miễn là bạn có thể xác định bù đắp của đường thẳng cho trước mà bạn muốn đọc lại.

Nếu bạn mở tệp jedis.bin, bạn sẽ thấy đó là tệp nhị phân, không phải tệp văn bản.

Bây giờ, tôi biết rằng "Luke\n" là 5 byte trong UTF-8 và "Obiwan\n" là 7 byte trong UTF-8. Và phương thức writeUTF() sẽ chèn 2 byte trước mỗi một trong các chuỗi này. Do đó, trước "Yoda\n" có (5 + 2) + (7 + 2) = 16 byte.

Vì vậy, tôi có thể làm một cái gì đó như thế này để đạt được dòng cuối cùng:

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

Nhưng điều này sẽ không hoạt động nếu bạn đã viết các tập tin với một lớp Writer vì nhà văn không tuân theo các quy tắc định dạng của phương pháp này writeUFT(). Trong trường hợp này, tốt nhất là tệp nhị phân của bạn sẽ được định dạng sao cho tất cả các chuỗi chiếm cùng một lượng không gian (số byte, không phải số ký tự, vì số lượng byte là biến trong UTF-8 tùy thuộc vào các ký tự trong chuỗi của bạn), nếu không phải tất cả các không gian là cần nó bạn pad nó:

Bằng cách đó bạn có thể dễ dàng tính toán bù đắp của một dòng nhất định bởi vì tất cả chúng sẽ chiếm cùng một lượng không gian.

+0

Tôi tạo ra file văn bản này sử dụng BufferedWriter (OutputStreamWriter mới (FileOutputStream mới (..), mã hóa), nơi mã hóa là utf8 – kenny

+1

Sau đó ou không thể sử dụng RandomAccessFile để đọc nó trở lại.Bạn phải sử dụng một lớp người đọc như BufferedReader hoặc FileReader, và đọc từ đầu cho đến khi bạn đạt đến dòng trong câu hỏi –

+1

điều này là không hiệu quả, tôi sử dụng tìm kiếm để phân trang phôi. tôi đọc lại toàn bộ tập tin mỗi lần – kenny

3

Bạn sẽ không thể thực hiện theo cách này. Hàm seek sẽ định vị bạn theo một số byte. Không có gì đảm bảo rằng bạn được căn chỉnh với một ranh giới ký tự UTF-8.

+0

và nếu tôi sử dụng đối số được đề xuất java -Dfile.encoding = UTF-8? Mã hóa – kenny

+2

@kenny UTF-8 mã hóa các ký tự với một số byte thay đổi, do đó bỏ qua một byte bù đắp trong tệp có thể bị lỗi (vì như @tchrist được đề cập), bạn có thể không ở đầu ranh giới ký tự khi bạn đến đó. Nếu bạn biết ký tự bù đắp mà bạn cần, bạn có thể sử dụng 'Reader.skip (long n)' để bỏ qua số ký tự. Điều đó cần được mã hóa nhận thức. Chỉ cần chắc chắn đặt ký tự của bạn trên 'InputStreamReader'. –

+2

Tìm ký tự tiếp theo trong UTF-8 thật dễ dàng. Chỉ cần bỏ qua tất cả các byte trong [0x80-0xBF], cái đầu tiên không nằm trong phạm vi đó sẽ là khởi đầu của một ký tự. (Đây là tài sản tự đồng bộ hóa, mà Ken Thompson đã thêm vào UTF-8). – ninjalj

0

Tôi tìm thấy API cho RandomAccessFile là một thách thức.

Nếu văn bản của bạn thực sự bị giới hạn ở giá trị UTF-8 0-127 (7 bit thấp nhất của UTF-8), thì an toàn là sử dụng readLine(), nhưng đọc kỹ Javadocs: Đó là một phương pháp lạ. Để báo giá:

Phương pháp này đọc liên tục byte từ tệp, bắt đầu từ con trỏ tệp hiện tại, cho đến khi nó đạt đến trình kết thúc dòng hoặc phần cuối của tệp. Mỗi byte được chuyển đổi thành một ký tự bằng cách lấy giá trị của byte cho tám bit thấp hơn của ký tự và thiết lập tám bit cao của ký tự về 0. Do đó, phương thức này không hỗ trợ bộ ký tự Unicode đầy đủ.

Để đọc UTF-8 một cách an toàn, tôi khuyên bạn nên đọc (một số hoặc tất cả) byte thô với sự kết hợp của length()read(byte[]). Sau đó chuyển đổi các byte UTF-8 của bạn thành một Java String với hàm tạo này: new String(byte[], "UTF-8").

Để viết UTF-8 một cách an toàn, trước tiên hãy chuyển đổi Java String thành đúng byte với someText.getBytes("UTF-8"). Cuối cùng, ghi các byte bằng cách sử dụng write(byte[]).

14

Bạn có thể chuyển đổi chuỗi, đọc bởi readLine để UTF8, sử dụng đoạn mã sau:

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

Nội dung myfile.txt: (UTF8 Encoding)

đầu ra
Привет из Украины 

Console:

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

Cảm ơn bạn đã đăng giải pháp của mình. Bạn có thể giải thích tại sao 'Chuỗi UTF8 = chuỗi mới (Line.getBytes (" UTF-8 ")," UTF-8 ");' không hoạt động? – thomasb

+0

@thomasb 'getBytes (" UTF-8 ")' sẽ biến đổi mảng byte nội bộ. 'ISO-8859-1' là mã hóa" thô ". – Matthieu

0

Tôi nhận ra rằng đây là một câu hỏi cũ, nhưng dường như vẫn có một chút quan tâm và không có câu trả lời được chấp nhận.

Điều bạn mô tả về bản chất là một vấn đề về cấu trúc dữ liệu. Thảo luận về UTF8 ở đây là cá trích đỏ - bạn sẽ phải đối mặt với cùng một vấn đề bằng cách sử dụng mã hóa độ dài cố định như ASCII, bởi vì bạn có các dòng độ dài thay đổi. Những gì bạn cần là một số loại chỉ mục.

Nếu bạn hoàn toàn không thể thay đổi chính tệp đó ("tệp chuỗi") - dường như là trường hợp - bạn luôn có thể tạo chỉ mục bên ngoài. Lần đầu tiên (và chỉ lần đầu tiên) tệp chuỗi được truy cập, bạn đọc toàn bộ (theo tuần tự), ghi vị trí byte của đầu mỗi dòng và hoàn tất bằng cách ghi tệp kết thúc vị trí (để làm cho cuộc sống đơn giản hơn). Điều này có thể đạt được bằng cách đoạn mã sau:

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

Sau đó bạn viết những số nguyên vào một file riêng biệt ("chỉ số tập tin"), mà bạn sẽ đọc lại trong mỗi lần tiếp theo bạn khởi động chương trình của bạn và có ý định để truy cập tệp chuỗi. Để truy cập chuỗi thứ tự n, hãy chọn chỉ số thứ nn+1 từ tệp chỉ mục (gọi các số AB) này. Sau đó, bạn tìm kiếm vị trí A trong tệp chuỗi và đọc B-A byte, sau đó bạn giải mã từ UTF8. Ví dụ, để có được dòng 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"); 

Trong nhiều trường hợp, tuy nhiên, nó sẽ là tốt hơn để sử dụng một cơ sở dữ liệu như SQLite, mà tạo ra và duy trì các chỉ số cho bạn. Bằng cách đó, bạn có thể thêm và sửa đổi thêm "dòng" mà không phải tạo lại toàn bộ chỉ mục. Xem https://www.sqlite.org/cvstrac/wiki?p=SqliteWrappers để triển khai Java.

1

Đọc các tập tin thông qua readLine() làm việc cho tôi:

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()); 
Các vấn đề liên quan