读取UTF-8 - BOM标记

93

我通过FileReader读取文件,该文件已用UTF-8解码(带BOM)。现在我的问题是:我读取文件并输出一个字符串,但不幸的是BOM标记也被输出了。为什么会发生这种情况?

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

第一行之后的输出

?<style>

7
UTF-8 不应该有 BOM!这是 Unicode 标准既不必要也不建议的做法。 - tchrist
38
在微软,他们不关注标准。 - Matti Virkkunen
12
“不推荐”并不等同于“非标准”。 - bacar
8
@tchrist 告诉那些在保存 UTF-8 文件时加入 BOM(=Microsoft)的人吧。 - dstibbe
7
@tchrist 我希望事情能够这么简单。你是为用户创建应用程序,而不是为自己创建。而用户使用(部分)微软软件来创建他们的文件。 - dstibbe
显示剩余6条评论
9个回答

97
在Java中,如果存在UTF8 BOM,则必须手动消耗它。这种行为在Java错误数据库中有记录,此处此处。目前不会有修复,因为它会破坏现有的工具,如JavaDoc或XML解析器。Apache IO Commons提供了一个BOMInputStream来处理这种情况。

非常晚才开始,但对于大文件来说似乎非常慢。我尝试使用缓冲区。如果使用缓冲区,似乎还会留下某种尾随数据。 - rocksNwaves

52

最简单的解决方法可能就是从字符串中删除结果中的\uFEFF,因为它极不可能出现其他原因。

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

还请参见这个Guava bug报告


4
“极不可能”的坏处在于它非常罕见,因此定位缺陷非常困难... :) 因此,如果您认为您的软件将会成功并且长久使用,请在使用此代码时非常谨慎,因为迟早会发生任何现有的情况。 - Franz D.
8
"FEFF" 是 UTF-16 的 BOM(字节顺序标记)。UTF-8 的 BOM 则是 "EFBBBF"。 - Steve Pitchers
6
@StevePitchers 但是我们必须在解码后与它进行匹配,当它是String的一部分时(String始终以UTF-16表示)。 - finnw
\uFFFE(UTF-16,小端序)怎么样? - Suzana
仅在文件开头替换它有什么问题,而不是在文件的任何位置替换它? - Joseph Budin
显示剩余2条评论

40

使用Apache Commons库

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

示例用法:

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

http://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/input/BOMInputStream.html - bmoc
1
此代码仅适用于UTF-8 BOM检测和排除。请检查bOMInputStream的实现:/** * 构造一个新的BOM InputStream,它检测到 * {@link ByteOrderMark#UTF_8} 并可选择包含它。 * @param delegate 要委托的InputStream * @param include true 包括UTF-8 BOM 或 false 排除它 */ public BOMInputStream(InputStream delegate, boolean include) { this(delegate, include, ByteOrderMark.UTF_8); } - czupe

9

以下是我使用Apache BOMInputStream的方法,它使用try-with-resources块。 "false"参数告诉对象忽略以下BOM(我们出于安全原因使用“无BOM”文本文件,哈哈):

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)

}

2
我永远无法弄清楚如何在这个网站上发布内容 - 总是出现问题。 - snakedoctor
如果你需要一个字符串,那么你可以跳过 BufferedReaderInputStreamReader,而是使用 commons.io.IOUtilsString xml = IOUtils.toString(bomInputStream, StandardCharsets.UTF_8) - mihca

8
考虑使用来自Google的UnicodeReader,它可以为您完成所有这些工作。 点击此处 了解更多信息。
Charset utf8 = StandardCharsets.UTF_8;  // default if no BOM present
try (Reader r = new UnicodeReader(new FileInputStream(file), utf8.name())) {
    ....
}

Maven依赖:

<dependency>
    <groupId>com.google.gdata</groupId>
    <artifactId>core</artifactId>
    <version>1.47.1</version>
</dependency>

1
谢谢。它很好用,而且还可以与SuperCSV一起使用。这为我赢得了一些额外的好感。 :) - Sacky San
1
非常好。对于OpenCSV来说非常简单易行的解决方案。 - grizzasd

7

使用Apache Commons IO

例如,让我们看一下我的代码(用于读取同时包含拉丁字母和西里尔字母的文本文件):

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

因此,我们有一个名为“ari”的ArrayList,其中包含来自文件“1.txt”的所有字符,但不包括BOM。


3
如果有人想要使用标准进行操作,这是一种方法:
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;
}

2

这里提到了Windows上的文件通常会出现这个问题。

一个可能的解决方案是先通过类似dos2unix这样的工具运行该文件。


是的,dos2unix(它是cygwin的一部分)有添加(--add-bom)和删除(--remove-bom)bom的选项。 - Roman

1
我找到的最简单的绕过BOM的方法是:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接