UTF-8 - Stücklistenmarkierung lesen

71

Ich lese eine Datei über einen FileReader - die Datei ist UTF-8-decodiert (mit Stückliste). Mein Problem ist nun: Ich lese die Datei und gebe eine Zeichenfolge aus, aber leider wird auch die Stücklistenmarkierung ausgegeben. Warum tritt das auf?

fr = new FileReader(file);
br = new BufferedReader(fr);
    String tmp = null;
    while ((tmp = br.readLine()) != null) {
    String text;    
    text = new String(tmp.getBytes(), "UTF-8");
    content += text + System.getProperty("line.separator");
}

Ausgabe nach der ersten Zeile

?<style>
onigunn
quelle
7
UTF-8 soll keine Stückliste haben! Dies ist vom Unicode-Standard weder erforderlich noch empfohlen .
Tchrist
27
@tchrist: Bei Microsoft kümmern sie sich nicht um Standards.
Matti Virkkunen
11
@ Matti "nicht empfohlen"! = Nicht-Standard
Bacar
6
@tchrist teilt dies den Personen mit, die die Stückliste beim Speichern in die UTF-8-Dateien (= Microsoft) einfügen.
Dstibbe
5
@tchrist Ich wünschte, die Dinge wären so einfach. Sie erstellen eine Anwendung für die Benutzer, nicht für sich selbst. Und die Benutzer verwenden (teilweise) Microsoft-Software, um ihre Dateien zu erstellen.
Dstibbe

Antworten:

80

In Java müssen Sie die UTF8-Stückliste manuell verwenden, falls vorhanden. Dieses Verhalten ist hier und hier in der Java-Fehlerdatenbank dokumentiert . Derzeit wird es keine Lösung geben, da vorhandene Tools wie JavaDoc oder XML-Parser beschädigt werden. Die Apache IO Commons bietet eineBOMInputStream , diese Situation zu bewältigen.

Schauen Sie sich diese Lösung an: Behandeln Sie die UTF8-Datei mit der Stückliste

RealHowTo
quelle
Sehr spät zum Spiel, aber dies scheint für große Dateien sehr langsam zu sein. Ich habe versucht, einen Puffer zu verwenden. Wenn Sie einen Puffer verwenden, scheint er auch nachfolgende Daten zu hinterlassen.
RocksNwaves
39

Die einfachste Lösung besteht wahrscheinlich darin, das Ergebnis \uFEFFaus der Zeichenfolge zu entfernen , da es aus einem anderen Grund äußerst unwahrscheinlich ist, dass es angezeigt wird.

tmp = tmp.replace("\uFEFF", "");

Siehe auch diesen Guava-Fehlerbericht

finnw
quelle
4
Das Schlechte an "extrem unwahrscheinlich" ist, dass es extrem selten auftaucht, so dass das Auffinden des Fehlers extrem schwierig ist ... :) Seien Sie also äußerst vorsichtig, wenn Sie diesen Code verwenden, wenn Sie glauben, dass Ihre Software erfolgreich und langlebig sein wird. weil früher oder später eine bestehende Situation eintreten wird.
Franz D.
4
FEFFist eine UTF-16-Stückliste. Die UTF-8-Stückliste ist EFBBBF.
Steve Pitchers
4
@StevePitchers, aber wir müssen es nach dem Decodieren abgleichen , wenn es Teil eines String(der immer als UTF-16 dargestellt wird) ist
finnw
Was ist mit \uFFFE(UTF-16, Little-Endian)?
Suzana
@ live-love Und wenn die Datei keine Stückliste hat, haben Sie gerade die erste Zeile abgeschnitten.
Eric Duminil
31

Verwenden Sie die Apache Commons-Bibliothek .

Klasse: org.apache.commons.io.input.BOMInputStream

Anwendungsbeispiel:

String defaultEncoding = "UTF-8";
InputStream inputStream = new FileInputStream(someFileWithPossibleUtf8Bom);
try {
    BOMInputStream bOMInputStream = new BOMInputStream(inputStream);
    ByteOrderMark bom = bOMInputStream.getBOM();
    String charsetName = bom == null ? defaultEncoding : bom.getCharsetName();
    InputStreamReader reader = new InputStreamReader(new BufferedInputStream(bOMInputStream), charsetName);
    //use reader
} finally {
    inputStream.close();
}
Peenut
quelle
Dieser Code funktioniert nur mit der Erkennung und dem Ausschließen von UTF-8-Stücklisten. Überprüfen Sie die Implementierung von bOMInputStream: `` `/ ** * Erstellt einen neuen BOM InputStream, der einen * a {@link ByteOrderMark # UTF_8} erkennt und optional einschließt. * @param delegiert den InputStream, an den delegiert werden soll * @param include true, um die UTF-8-Stückliste einzuschließen, oder * false, um sie auszuschließen * / public BOMInputStream (InputStream-Delegat, boolesches Include) {this (delegate, include, ByteOrderMark.UTF_8); } `` `
czupe
7

Hier ist, wie ich den Apache BOMInputStream verwende, er verwendet einen Try-with-Resources-Block. Das Argument "false" weist das Objekt an, die folgenden Stücklisten zu ignorieren (wir verwenden aus Sicherheitsgründen Textdateien ohne Stückliste, haha):

try( BufferedReader br = new BufferedReader( 
    new InputStreamReader( new BOMInputStream( new FileInputStream(
       file), false, ByteOrderMark.UTF_8,
        ByteOrderMark.UTF_16BE, ByteOrderMark.UTF_16LE,
        ByteOrderMark.UTF_32BE, ByteOrderMark.UTF_32LE ) ) ) )
{
    // use br here

} catch( Exception e)

}
Snakedoctor
quelle
1
kann nie herausfinden, wie man Sachen auf dieser Seite veröffentlicht - endet immer AFU.
Snakedoctor
5

Betrachten Sie UnicodeReader von Google, der all dies für Sie erledigt.

Charset utf8 = Charset.forName("UTF-8"); // default if no BOM present
try (Reader r = new UnicodeReader(new FileInputStream(file), utf8)) {
    ....
}

Maven-Abhängigkeit:

<dependency>
    <groupId>com.google.gdata</groupId>
    <artifactId>core</artifactId>
    <version>1.47.1</version>
</dependency>
Adrian Smith
quelle
Vielen Dank. Es funktioniert gut und auch mit SuperCSV. Das brachte mir ein paar braune Punkte ein. :)
Sacky San
Ausgezeichnet. Sehr einfache Lösung, die für OpenCSV
grizzasd
4

Verwenden Sie Apache Commons IO .

Schauen wir uns zum Beispiel meinen Code an (der zum Lesen einer Textdatei mit lateinischen und kyrillischen Zeichen verwendet wird):

String defaultEncoding = "UTF-16";
InputStream inputStream = new FileInputStream(new File("/temp/1.txt"));

BOMInputStream bomInputStream = new BOMInputStream(inputStream);

ByteOrderMark bom = bomInputStream.getBOM();
String charsetName = bom == null ? defaultEncoding : bom.getCharsetName();
InputStreamReader reader = new InputStreamReader(new BufferedInputStream(bomInputStream), charsetName);
int data = reader.read();
while (data != -1) {

 char theChar = (char) data;
 data = reader.read();
 ari.add(Character.toString(theChar));
}
reader.close();

Als Ergebnis haben wir eine ArrayList mit dem Namen "ari" mit allen Zeichen aus der Datei "1.txt" mit Ausnahme der Stückliste.

Pfotenmann
quelle
1

Es wird hier erwähnt dass dies normalerweise ein Problem mit Dateien unter Windows ist.

Eine mögliche Lösung wäre, die Datei zuerst über ein Tool wie dos2unix auszuführen.

Drake Sobania
quelle
Ja, dos2unix(was Teil von Cygwin ist) hat Optionen zum Hinzufügen ( --add-bom) und Entfernen ( --remove-bom) von bom.
Roman
1

Wenn jemand es mit dem Standard machen möchte, wäre dies ein Weg:

public static String cutBOM(String value) {
    // UTF-8 BOM is EF BB BF, see https://en.wikipedia.org/wiki/Byte_order_mark
    String bom = String.format("%x", new BigInteger(1, value.substring(0,3).getBytes()));
    if (bom.equals("efbbbf"))
        // UTF-8
        return value.substring(3, value.length());
    else if (bom.substring(0, 2).equals("feff") || bom.substring(0, 2).equals("ffe"))
        // UTF-16BE or UTF16-LE
        return value.substring(2, value.length());
    else
        return value;
}
Markus
quelle
0

Der einfachste Weg, die Stückliste zu umgehen

BufferedReader br = new BufferedReader(new InputStreamReader(fis));    
while ((currentLine = br.readLine()) != null) {
                    //case of, remove the BOM of UTF-8 BOM
                    currentLine = currentLine.replace("","");
David
quelle