Byte Order Mark vermasselt das Lesen von Dateien in Java

Lesezeit: 13 Minuten

Byte Order Mark vermasselt das Lesen von Dateien in Java
Tom

Ich versuche, CSV-Dateien mit Java zu lesen. Einige der Dateien können am Anfang eine Byte-Order-Markierung haben, aber nicht alle. Wenn vorhanden, wird die Byte-Reihenfolge zusammen mit dem Rest der ersten Zeile gelesen, was zu Problemen mit String-Vergleichen führt.

Gibt es eine einfache Möglichkeit, die Byte-Order-Markierung zu überspringen, wenn sie vorhanden ist?

1646439429 673 Byte Order Mark vermasselt das Lesen von Dateien in Java
Gregor Pakosz

BEARBEITEN: Ich habe eine richtige Veröffentlichung auf GitHub gemacht: https://github.com/gpakosz/UnicodeBOMInputStream


Hier ist eine Klasse, die ich vor einiger Zeit codiert habe, ich habe gerade den Paketnamen vor dem Einfügen bearbeitet. Nichts Besonderes, es ist ziemlich ähnlich zu den Lösungen, die in der Fehlerdatenbank von SUN gepostet wurden. Integrieren Sie es in Ihren Code und Sie sind in Ordnung.

/* ____________________________________________________________________________
 * 
 * File:    UnicodeBOMInputStream.java
 * Author:  Gregory Pakosz.
 * Date:    02 - November - 2005    
 * ____________________________________________________________________________
 */
package com.stackoverflow.answer;

import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;

/**
 * The <code>UnicodeBOMInputStream</code> class wraps any
 * <code>InputStream</code> and detects the presence of any Unicode BOM
 * (Byte Order Mark) at its beginning, as defined by
 * <a href="http://www.faqs.org/rfcs/rfc3629.html">RFC 3629 - UTF-8, a transformation format of ISO 10646</a>
 * 
 * <p>The
 * <a href="http://www.unicode.org/unicode/faq/utf_bom.html">Unicode FAQ</a>
 * defines 5 types of BOMs:<ul>
 * <li><pre>00 00 FE FF  = UTF-32, big-endian</pre></li>
 * <li><pre>FF FE 00 00  = UTF-32, little-endian</pre></li>
 * <li><pre>FE FF        = UTF-16, big-endian</pre></li>
 * <li><pre>FF FE        = UTF-16, little-endian</pre></li>
 * <li><pre>EF BB BF     = UTF-8</pre></li>
 * </ul></p>
 * 
 * <p>Use the {@link #getBOM()} method to know whether a BOM has been detected
 * or not.
 * </p>
 * <p>Use the {@link #skipBOM()} method to remove the detected BOM from the
 * wrapped <code>InputStream</code> object.</p>
 */
public class UnicodeBOMInputStream extends InputStream
{
  /**
   * Type safe enumeration class that describes the different types of Unicode
   * BOMs.
   */
  public static final class BOM
  {
    /**
     * NONE.
     */
    public static final BOM NONE = new BOM(new byte[]{},"NONE");

    /**
     * UTF-8 BOM (EF BB BF).
     */
    public static final BOM UTF_8 = new BOM(new byte[]{(byte)0xEF,
                                                       (byte)0xBB,
                                                       (byte)0xBF},
                                            "UTF-8");

    /**
     * UTF-16, little-endian (FF FE).
     */
    public static final BOM UTF_16_LE = new BOM(new byte[]{ (byte)0xFF,
                                                            (byte)0xFE},
                                                "UTF-16 little-endian");

    /**
     * UTF-16, big-endian (FE FF).
     */
    public static final BOM UTF_16_BE = new BOM(new byte[]{ (byte)0xFE,
                                                            (byte)0xFF},
                                                "UTF-16 big-endian");

    /**
     * UTF-32, little-endian (FF FE 00 00).
     */
    public static final BOM UTF_32_LE = new BOM(new byte[]{ (byte)0xFF,
                                                            (byte)0xFE,
                                                            (byte)0x00,
                                                            (byte)0x00},
                                                "UTF-32 little-endian");

    /**
     * UTF-32, big-endian (00 00 FE FF).
     */
    public static final BOM UTF_32_BE = new BOM(new byte[]{ (byte)0x00,
                                                            (byte)0x00,
                                                            (byte)0xFE,
                                                            (byte)0xFF},
                                                "UTF-32 big-endian");

    /**
     * Returns a <code>String</code> representation of this <code>BOM</code>
     * value.
     */
    public final String toString()
    {
      return description;
    }

    /**
     * Returns the bytes corresponding to this <code>BOM</code> value.
     */
    public final byte[] getBytes()
    {
      final int     length = bytes.length;
      final byte[]  result = new byte[length];

      // Make a defensive copy
      System.arraycopy(bytes,0,result,0,length);

      return result;
    }

    private BOM(final byte bom[], final String description)
    {
      assert(bom != null)               : "invalid BOM: null is not allowed";
      assert(description != null)       : "invalid description: null is not allowed";
      assert(description.length() != 0) : "invalid description: empty string is not allowed";

      this.bytes          = bom;
      this.description  = description;
    }

            final byte    bytes[];
    private final String  description;

  } // BOM

  /**
   * Constructs a new <code>UnicodeBOMInputStream</code> that wraps the
   * specified <code>InputStream</code>.
   * 
   * @param inputStream an <code>InputStream</code>.
   * 
   * @throws NullPointerException when <code>inputStream</code> is
   * <code>null</code>.
   * @throws IOException on reading from the specified <code>InputStream</code>
   * when trying to detect the Unicode BOM.
   */
  public UnicodeBOMInputStream(final InputStream inputStream) throws  NullPointerException,
                                                                      IOException

  {
    if (inputStream == null)
      throw new NullPointerException("invalid input stream: null is not allowed");

    in = new PushbackInputStream(inputStream,4);

    final byte  bom[] = new byte[4];
    final int   read  = in.read(bom);

    switch(read)
    {
      case 4:
        if ((bom[0] == (byte)0xFF) &&
            (bom[1] == (byte)0xFE) &&
            (bom[2] == (byte)0x00) &&
            (bom[3] == (byte)0x00))
        {
          this.bom = BOM.UTF_32_LE;
          break;
        }
        else
        if ((bom[0] == (byte)0x00) &&
            (bom[1] == (byte)0x00) &&
            (bom[2] == (byte)0xFE) &&
            (bom[3] == (byte)0xFF))
        {
          this.bom = BOM.UTF_32_BE;
          break;
        }

      case 3:
        if ((bom[0] == (byte)0xEF) &&
            (bom[1] == (byte)0xBB) &&
            (bom[2] == (byte)0xBF))
        {
          this.bom = BOM.UTF_8;
          break;
        }

      case 2:
        if ((bom[0] == (byte)0xFF) &&
            (bom[1] == (byte)0xFE))
        {
          this.bom = BOM.UTF_16_LE;
          break;
        }
        else
        if ((bom[0] == (byte)0xFE) &&
            (bom[1] == (byte)0xFF))
        {
          this.bom = BOM.UTF_16_BE;
          break;
        }

      default:
        this.bom = BOM.NONE;
        break;
    }

    if (read > 0)
      in.unread(bom,0,read);
  }

  /**
   * Returns the <code>BOM</code> that was detected in the wrapped
   * <code>InputStream</code> object.
   * 
   * @return a <code>BOM</code> value.
   */
  public final BOM getBOM()
  {
    // BOM type is immutable.
    return bom;
  }

  /**
   * Skips the <code>BOM</code> that was found in the wrapped
   * <code>InputStream</code> object.
   * 
   * @return this <code>UnicodeBOMInputStream</code>.
   * 
   * @throws IOException when trying to skip the BOM from the wrapped
   * <code>InputStream</code> object.
   */
  public final synchronized UnicodeBOMInputStream skipBOM() throws IOException
  {
    if (!skipped)
    {
      in.skip(bom.bytes.length);
      skipped = true;
    }
    return this;
  }

  /**
   * {@inheritDoc}
   */
  public int read() throws IOException
  {
    return in.read();
  }

  /**
   * {@inheritDoc}
   */
  public int read(final byte b[]) throws  IOException,
                                          NullPointerException
  {
    return in.read(b,0,b.length);
  }

  /**
   * {@inheritDoc}
   */
  public int read(final byte b[],
                  final int off,
                  final int len) throws IOException,
                                        NullPointerException
  {
    return in.read(b,off,len);
  }

  /**
   * {@inheritDoc}
   */
  public long skip(final long n) throws IOException
  {
    return in.skip(n);
  }

  /**
   * {@inheritDoc}
   */
  public int available() throws IOException
  {
    return in.available();
  }

  /**
   * {@inheritDoc}
   */
  public void close() throws IOException
  {
    in.close();
  }

  /**
   * {@inheritDoc}
   */
  public synchronized void mark(final int readlimit)
  {
    in.mark(readlimit);
  }

  /**
   * {@inheritDoc}
   */
  public synchronized void reset() throws IOException
  {
    in.reset();
  }

  /**
   * {@inheritDoc}
   */
  public boolean markSupported() 
  {
    return in.markSupported();
  }

  private final PushbackInputStream in;
  private final BOM                 bom;
  private       boolean             skipped = false;

} // UnicodeBOMInputStream

Und Sie verwenden es auf diese Weise:

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;

public final class UnicodeBOMInputStreamUsage
{
  public static void main(final String[] args) throws Exception
  {
    FileInputStream fis = new FileInputStream("test/offending_bom.txt");
    UnicodeBOMInputStream ubis = new UnicodeBOMInputStream(fis);

    System.out.println("detected BOM: " + ubis.getBOM());

    System.out.print("Reading the content of the file without skipping the BOM: ");
    InputStreamReader isr = new InputStreamReader(ubis);
    BufferedReader br = new BufferedReader(isr);

    System.out.println(br.readLine());

    br.close();
    isr.close();
    ubis.close();
    fis.close();

    fis = new FileInputStream("test/offending_bom.txt");
    ubis = new UnicodeBOMInputStream(fis);
    isr = new InputStreamReader(ubis);
    br = new BufferedReader(isr);

    ubis.skipBOM();

    System.out.print("Reading the content of the file after skipping the BOM: ");
    System.out.println(br.readLine());

    br.close();
    isr.close();
    ubis.close();
    fis.close();
  }

} // UnicodeBOMInputStreamUsage

  • Entschuldigung für die langen Scrollbereiche, schade, dass es keine Anhangsfunktion gibt

    – Gregory Pakosz

    2. Dezember 2009 um 20:22 Uhr

  • Danke Gregory, das ist genau das, wonach ich suche.

    – Tom

    2. Dezember 2009 um 22:01 Uhr

  • Dies sollte sich in der Kern-Java-API befinden

    – Denis Knjaschew

    7. Dezember 2012 um 16:42 Uhr

  • 10 Jahre sind vergangen und ich bekomme immer noch Karma dafür 😀 Ich sehe dich an Java!

    – Gregory Pakosz

    22. Mai 2015 um 10:33 Uhr

  • Positiv bewertet, da die Antwort einen Verlauf darüber enthält, warum der Dateieingabestream nicht standardmäßig die Option zum Verwerfen von BOM bietet.

    – MxLDevs

    30. April 2018 um 19:09 Uhr

1646439429 727 Byte Order Mark vermasselt das Lesen von Dateien in Java
rescdsk

Die Apache Commons IO Bibliothek hat eine InputStream die Stücklisten erkennen und verwerfen kann: BOMInputStream (Javadoc):

BOMInputStream bomIn = new BOMInputStream(in);
int firstNonBOMByte = bomIn.read(); // Skips BOM
if (bomIn.hasBOM()) {
    // has a UTF-8 BOM
}

Wenn Sie auch unterschiedliche Codierungen erkennen müssen, kann es auch zwischen verschiedenen Byte-Order-Markierungen unterscheiden, z. B. UTF-8 vs. UTF-16 Big + Little Endian – Details unter dem obigen Doc-Link. Sie können dann die erkannten verwenden ByteOrderMark ein wählen Charset um den Stream zu entschlüsseln. (Es gibt wahrscheinlich eine einfachere Möglichkeit, dies zu tun, wenn Sie all diese Funktionen benötigen – vielleicht den UnicodeReader in der Antwort von BalusC?). Beachten Sie, dass es im Allgemeinen keine sehr gute Möglichkeit gibt, zu erkennen, in welcher Codierung einige Bytes enthalten sind, aber wenn der Stream mit einer BOM beginnt, kann dies anscheinend hilfreich sein.

Bearbeiten: Wenn Sie die BOM in UTF-16, UTF-32 usw. erkennen müssen, sollte der Konstruktor sein:

new BOMInputStream(is, ByteOrderMark.UTF_8, ByteOrderMark.UTF_16BE,
        ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_32BE, ByteOrderMark.UTF_32LE)

Stimme dem Kommentar von @martin-charlesworth zu 🙂

  • Überspringt einfach die Stückliste. Sollte die perfekte Lösung für 99 % der Anwendungsfälle sein.

    – atamanroman

    7. Mai 2012 um 7:39 Uhr

  • Ich habe diese Antwort erfolgreich verwendet. Allerdings würde ich das respektvoll hinzufügen boolean arg zum Angeben, ob die BOM eingeschlossen oder ausgeschlossen werden soll. Beispiel: BOMInputStream bomIn = new BOMInputStream(in, false); // don't include the BOM

    – Kevin Meredith

    7. Oktober 2013 um 17:12 Uhr


  • Ich würde auch hinzufügen, dass dies nur UTF-8 BOM erkennt. Wenn Sie alle utf-X-BOMs erkennen möchten, müssen Sie sie an den BOMInputStream-Konstruktor übergeben. BOMInputStream bomIn = new BOMInputStream(is, ByteOrderMark.UTF_8, ByteOrderMark.UTF_16BE, ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_32BE, ByteOrderMark.UTF_32LE);

    – Martin Charlesworth

    26. Juni 2014 um 16:29 Uhr

  • Was den Kommentar von @KevinMeredith betrifft, möchte ich betonen, dass der Konstruktor mit boolean klarer ist, aber der Standardkonstruktor hat die UTF-8-BOM bereits entfernt, wie das JavaDoc vorschlägt: BOMInputStream(InputStream delegate) Constructs a new BOM InputStream that excludes a ByteOrderMark.UTF_8 BOM.

    – WesternGun

    15. September 2017 um 12:18 Uhr


  • Das Überspringen löst die meisten meiner Probleme. Wenn meine Datei mit einer BOM UTF_16BE beginnt, kann ich einen InputReader erstellen, indem ich die BOM überspringe und die Datei als UTF_8 lese? Bisher funktioniert es, ich möchte verstehen, ob es einen Grenzfall gibt? Vielen Dank im Voraus.

    – Bhaskar

    26. Mai 2020 um 21:22 Uhr

Einfachere Lösung:

public class BOMSkipper
{
    public static void skip(Reader reader) throws IOException
    {
        reader.mark(1);
        char[] possibleBOM = new char[1];
        reader.read(possibleBOM);

        if (possibleBOM[0] != '\ufeff')
        {
            reader.reset();
        }
    }
}

Anwendungsbeispiel:

BufferedReader input = new BufferedReader(new InputStreamReader(new FileInputStream(file), fileExpectedCharset));
BOMSkipper.skip(input);
//Now UTF prefix not present:
input.readLine();
...

Es funktioniert mit allen 5 UTF-Kodierungen!

  • Sehr nett Andrej. Aber könnten Sie erklären, warum es funktioniert? Wie passt das Muster 0xFEFF erfolgreich zu UTF-8-Dateien, die ein anderes Muster und 3 Bytes anstelle von 2 zu haben scheinen? Und wie kann dieses Muster mit beiden Endians von UTF16 und UTF32 übereinstimmen?

    – Vahid Pazirandeh

    27. Mai 2014 um 19:08 Uhr

  • Wie Sie sehen können, verwende ich keinen Byte-Stream, sondern einen Zeichen-Stream, der mit dem erwarteten Zeichensatz geöffnet wurde. Wenn also das erste Zeichen aus diesem Stream BOM ist, überspringe ich es. BOM kann für jede Codierung eine andere Byte-Darstellung haben, aber dies ist ein Zeichen. Bitte lesen Sie diesen Artikel, er hilft mir: joelonsoftware.com/articles/Unicode.html

    Benutzer1092126

    28. Mai 2014 um 22:21 Uhr


  • Gute Lösung, überprüfen Sie einfach, ob die Datei nicht leer ist, um IOException in der Skip-Methode vor dem Lesen zu vermeiden. Sie können dies tun, indem Sie if (reader.ready()){ reader.read(possibleBOM) … }

    – Schnee

    17. Juni 2014 um 13:49 Uhr

  • Wie ich sehe, haben Sie 0xFE 0xFF behandelt, das ist die Byte-Order-Markierung für UTF-16BE. Aber was ist, wenn die ersten 3 Bytes 0xEF 0xBB 0xEF sind? (die Byte-Order-Marke für UTF-8). Sie behaupten, dass dies für alle UTF-8-Formate funktioniert. Das könnte wahr sein (ich habe Ihren Code nicht getestet), aber wie funktioniert es dann?

    – bvdb

    7. Juli 2016 um 8:40 Uhr


  • Siehe meine Antwort an Vahid: Ich öffne nicht den Bytestrom, sondern den Zeichenstrom und lese ein Zeichen daraus. Egal, welche utf-Codierung für die Datei verwendet wird – das Bom-Präfix kann durch eine unterschiedliche Anzahl von Bytes dargestellt werden, aber in Bezug auf die Zeichen ist es nur ein Zeichen

    Benutzer1092126

    19. Juli 2016 um 22:58 Uhr


1646439429 587 Byte Order Mark vermasselt das Lesen von Dateien in Java
BalusC

Google-Daten-API hat ein UnicodeReader die automatisch die Codierung erkennt.

Sie können es stattdessen verwenden InputStreamReader. Hier ist ein – leicht komprimierter – Auszug aus seiner Quelle, der ziemlich einfach ist:

public class UnicodeReader extends Reader {
    private static final int BOM_SIZE = 4;
    private final InputStreamReader reader;

    /**
     * Construct UnicodeReader
     * @param in Input stream.
     * @param defaultEncoding Default encoding to be used if BOM is not found,
     * or <code>null</code> to use system default encoding.
     * @throws IOException If an I/O error occurs.
     */
    public UnicodeReader(InputStream in, String defaultEncoding) throws IOException {
        byte bom[] = new byte[BOM_SIZE];
        String encoding;
        int unread;
        PushbackInputStream pushbackStream = new PushbackInputStream(in, BOM_SIZE);
        int n = pushbackStream.read(bom, 0, bom.length);

        // Read ahead four bytes and check for BOM marks.
        if ((bom[0] == (byte) 0xEF) && (bom[1] == (byte) 0xBB) && (bom[2] == (byte) 0xBF)) {
            encoding = "UTF-8";
            unread = n - 3;
        } else if ((bom[0] == (byte) 0xFE) && (bom[1] == (byte) 0xFF)) {
            encoding = "UTF-16BE";
            unread = n - 2;
        } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE)) {
            encoding = "UTF-16LE";
            unread = n - 2;
        } else if ((bom[0] == (byte) 0x00) && (bom[1] == (byte) 0x00) && (bom[2] == (byte) 0xFE) && (bom[3] == (byte) 0xFF)) {
            encoding = "UTF-32BE";
            unread = n - 4;
        } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE) && (bom[2] == (byte) 0x00) && (bom[3] == (byte) 0x00)) {
            encoding = "UTF-32LE";
            unread = n - 4;
        } else {
            encoding = defaultEncoding;
            unread = n;
        }

        // Unread bytes if necessary and skip BOM marks.
        if (unread > 0) {
            pushbackStream.unread(bom, (n - unread), unread);
        } else if (unread < -1) {
            pushbackStream.unread(bom, 0, 0);
        }

        // Use given encoding.
        if (encoding == null) {
            reader = new InputStreamReader(pushbackStream);
        } else {
            reader = new InputStreamReader(pushbackStream, encoding);
        }
    }

    public String getEncoding() {
        return reader.getEncoding();
    }

    public int read(char[] cbuf, int off, int len) throws IOException {
        return reader.read(cbuf, off, len);
    }

    public void close() throws IOException {
        reader.close();
    }
}

1646439430 754 Byte Order Mark vermasselt das Lesen von Dateien in Java
Kevin Meredith

Die Apache Commons IO Bibliothek BOMInputStream wurde bereits von @rescdsk erwähnt, aber ich habe nicht gesehen, wie man eine erhält InputStream ohne die Stückliste.

So habe ich es in Scala gemacht.

 import java.io._
 val file = new File(path_to_xml_file_with_BOM)
 val fileInpStream = new FileInputStream(file)   
 val bomIn = new BOMInputStream(fileInpStream, 
         false); // false means don't include BOM

  • Single arg Konstruktor tut es: public BOMInputStream(InputStream delegate) { this(delegate, false, ByteOrderMark.UTF_8); }. Es schließt aus UTF-8 BOM standardmäßig.

    – Vladimir Vagaytsev

    15. Juli 2016 um 17:05 Uhr


  • Guter Punkt, Wladimir. Ich sehe das in seinen Dokumenten – commons.apache.org/proper/commons-io/javadocs/api-2.2/org/…: Constructs a new BOM InputStream that excludes a ByteOrderMark.UTF_8 BOM.

    – Kevin Meredith

    15. Juli 2016 um 17:10 Uhr

Byte Order Mark vermasselt das Lesen von Dateien in Java
Andreas Baserud

Um die BOM-Zeichen einfach aus Ihrer Datei zu entfernen, empfehle ich die Verwendung von Apache Common IO

public BOMInputStream(InputStream delegate,
              boolean include)
Constructs a new BOM InputStream that detects a a ByteOrderMark.UTF_8 and optionally includes it.
Parameters:
delegate - the InputStream to delegate to
include - true to include the UTF-8 BOM or false to exclude it

Setzen Sie include auf false und Ihre BOM-Zeichen werden ausgeschlossen.

  • Single arg Konstruktor tut es: public BOMInputStream(InputStream delegate) { this(delegate, false, ByteOrderMark.UTF_8); }. Es schließt aus UTF-8 BOM standardmäßig.

    – Vladimir Vagaytsev

    15. Juli 2016 um 17:05 Uhr


  • Guter Punkt, Wladimir. Ich sehe das in seinen Dokumenten – commons.apache.org/proper/commons-io/javadocs/api-2.2/org/…: Constructs a new BOM InputStream that excludes a ByteOrderMark.UTF_8 BOM.

    – Kevin Meredith

    15. Juli 2016 um 17:10 Uhr

Byte Order Mark vermasselt das Lesen von Dateien in Java
Gemeinschaft

Leider nicht. Sie müssen sich identifizieren und überspringen. Diese Seite Details, worauf Sie achten müssen. Siehe auch diese SO-Frage für weitere Details.

940270cookie-checkByte Order Mark vermasselt das Lesen von Dateien in Java

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy