2012-04-01 21 views
11

Ho un file di testo codificato con UTF8 (per caratteri specifici della lingua). Devo usare RandomAccessFile per cercare una posizione specifica e leggere da.Come leggere il file con codifica UTF8 utilizzando RandomAccessFile?

Voglio leggere linea per linea.

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

risposta

4

la documentazione API dicono quanto segue per readUTF8

Legge in una stringa da questo file. La stringa è stata codificata utilizzando un formato UTF-8 modificato .

I primi due byte vengono letti, a partire dal puntatore del file corrente, come se readUnsignedShort. Questo valore indica il numero dei seguenti byte nella stringa codificata, non la lunghezza della stringa risultante. I seguenti byte vengono quindi interpretati come byte che codificano i caratteri nel formato UTF-8 modificato e vengono convertiti in caratteri .

Questo metodo blocca fino a quando non vengono letti tutti i byte, viene rilevata la fine del flusso oppure viene generata un'eccezione.

La stringa è stata formattata in questo modo?

Questo sembra spiegare il tuo EOF eccetto.

Il file è un file di testo, quindi il problema reale è la decodifica.

La risposta più semplice che conosco è:

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

oppure è possibile impostare la codifica del sistema corrente con la proprietà di sistema file.encoding a UTF-8.

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

Si può anche impostare come una proprietà di sistema in fase di esecuzione con System.setProperty(...) se è necessario solo per questo file specifico, ma in un caso come questo penso io preferirei il OutputStreamWriter.

Impostando la proprietà di sistema è possibile utilizzare FileReader e prevedere che utilizzerà UTF-8 come codifica predefinita per i file. In questo caso per tutti i file che leggi e scrivi.

Se si desidera rilevare errori di decodifica nel file, si sarà costretti a utilizzare l'approccio InputStreamReader e utilizzare il costruttore che riceve un decodificatore.

Un po 'come

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

Si può scegliere tra le azioni IGNORE | REPLACE | REPORT

EDIT

Se ti ostini ad utilizzare RandomAccessFile, si avrebbe bisogno di sapere l'esatta offset della linea che si intendono leggereE non solo, per leggere con il metodo readUTF(), dovresti aver scritto il file con il metodo writeUTF(). Poiché questo metodo, come JavaDocs indicato sopra, si aspetta una formattazione specifica in cui i primi 2 byte senza segno rappresentano la lunghezza in byte della stringa UTF-8.

Come tale, se si fare:

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

non dovreste avere problemi di lettura indietro da questo file utilizzando il metodo readUTF(), fino a quando è possibile determinare l'offset della linea di data che si desidera rileggi.

Se si apre il file jedis.bin si noterà è un file binario, non un file di testo.

Ora, so che "Luke\n" è 5 byte in UTF-8 e "Obiwan\n" è 7 byte in UTF-8. E che il metodo writeUTF() inserirà 2 byte davanti a ciascuna di queste stringhe. Pertanto, prima di "Yoda\n" ci sono (5 + 2) + (7 + 2) = 16 byte.

Così, ho potuto fare qualcosa di simile per raggiungere l'ultima riga:

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

Ma questo non funzionerà se hai scritto il file con una classe Writer perché gli scrittori non seguono le regole di formattazione del metodo writeUFT().

In un caso come questo, la cosa migliore sarebbe che il file binario sarebbe essere formattato in modo tale che tutte le stringhe occupavano la stessa quantità di spazio (numero di byte, non il numero di characteres, perché il numero di byte è variabile in UTF-8 a seconda dei caratteri nella stringa), se non è tutto lo spazio è necessario lo si riempie:

In questo modo è possibile calcolare facilmente l'offset di una determinata linea perché tutti occuperebbero il stessa quantità di spazio.

+0

Ho creato questo file di testo utilizzando BufferedWriter (nuova OutputStreamWriter (nuova FileOutputStream (..), la codifica), dove la codifica è utf8 – kenny

+1

Poi ou non possono usare RandomAccessFile per leggerlo. Devi usare una classe di lettore come BufferedReader o FileReader e leggere dall'inizio fino a raggiungere la riga nella domanda –

+1

questo non è efficiente, io cerco di preformare il paging. per rileggere l'intero file ogni volta. – kenny

3

Non si sarà in grado di andare in questo modo. La funzione seek ti posizionerà in base al numero di byte. Non è garantito che tu sia allineato a un limite di caratteri UTF-8.

+0

e se utilizzo l'argomento suggerito java -Dfile.encoding = UTF-8? – kenny

+2

@kenny La codifica UTF-8 codifica i caratteri con un numero variabile di byte, quindi saltare a un offset di byte all'interno del file probabilmente fallirà (poiché come @tchrist menzionato) potresti non trovarti all'inizio del confine di un personaggio quando arrivare lì. Se conosci l'offset di caratteri di cui hai bisogno, puoi usare 'Reader.skip (long n)' per saltare il numero di caratteri. Questo dovrebbe essere codificato. Assicurati di impostare il set di caratteri su 'InputStreamReader'. –

+2

Trovare il prossimo carattere in UTF-8 è facile. Basta saltare tutti i byte in [0x80-0xBF], il primo non in quell'intervallo sarà l'inizio di un personaggio. (Questa è la proprietà di auto-sincronizzazione, che Ken Thompson ha aggiunto a UTF-8). – ninjalj

0

Trovo che l'API per RandomAccessFile sia impegnativa.

Se il testo è in realtà limitato a UTF-8 valori 0-127 (il più basso 7 bit di UTF-8), allora è sicuro da usare readLine(), ma leggere attentamente queste Javadocs: Questo è un metodo strano. Per quotare:

Questo metodo legge in successione i byte dal file, a partire dal puntatore del file corrente, finché non raggiunge un terminatore di riga o la fine del file. Ogni byte viene convertito in un carattere prendendo il valore del byte per gli otto bit inferiori del carattere e impostando gli otto bit più alti del carattere su zero. Pertanto, questo metodo non supporta il set di caratteri Unicode completo.

Per leggere UTF-8 in modo sicuro, vi consiglio di leggere (alcuni o tutti i byte) prime con una combinazione di length() e read(byte[]). Quindi converti i tuoi byte UTF-8 in un Java String con questo costruttore: new String(byte[], "UTF-8").

Per scrivere in modo sicuro UTF-8, convertire prima Java String in byte corretti con someText.getBytes("UTF-8"). Infine, scrivi i byte usando write(byte[]).

14

È possibile convertire stringa, letto da readLine a UTF8, utilizzando codice seguente:

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

Contenuto MyFile.txt: (UTF8 codifica)

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

Console:

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

Grazie per aver pubblicato la tua soluzione. Potresti spiegare perché 'String UTF8 = new String (Line.getBytes (" UTF-8 ")," UTF-8 ");' non funziona? – thomasb

+0

@thomasb 'getBytes (" UTF-8 ")' trasformerà l'array di byte interno. 'ISO-8859-1' è la codifica" raw ". – Matthieu

0

Mi rendo conto che questa è una domanda vecchia, ma sembra ancora avere un certo interesse e nessuna risposta accettata.

Quello che stai descrivendo è essenzialmente un problema di strutture dati. La discussione di UTF8 qui è un'aringa rossa: dovresti affrontare lo stesso problema usando una codifica a lunghezza fissa come ASCII, perché hai linee di lunghezza variabile. Quello di cui hai bisogno è una specie di indice.

Se non è assolutamente possibile modificare il file stesso (il "file di stringhe") - come sembra essere il caso - è sempre possibile costruire un indice esterno. La prima volta (e solo la prima volta) si accede al file di stringa, lo si legge fino in fondo (in sequenza), si registra la posizione di byte dell'inizio di ogni riga e si termina registrando la fine del file posizione (per semplificare la vita). Ciò può essere ottenuto dal codice seguente:

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

È quindi scrivere questi numeri interi in un file separato ("file indice"), che potrete leggere di nuovo in ogni momento successivo si avvia il programma e l'intenzione di accedere al file di stringhe. Per accedere alla stringa n, selezionare l'indice n e l'indice n+1 dall'indice (chiamare questi A e B). Quindi si cerca di posizionare A nel file di stringhe e leggere B-A byte, che verranno decodificati da UTF8. Per esempio, per ottenere la linea 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"); 

In molti casi, tuttavia, sarebbe meglio utilizzare un database come SQLite, che crea e mantiene l'indice per voi. In questo modo, puoi aggiungere e modificare "linee" extra senza dover ricreare l'intero indice. Vedere https://www.sqlite.org/cvstrac/wiki?p=SqliteWrappers per le implementazioni Java.

1

Leggere il file tramite readLine() ha lavorato per me:

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()); 
Problemi correlati