1. 引言

在文本编码领域,字节顺序标记(BOM)是文件开头的一个特殊标记,用于指明文件的字节顺序和编码方案。对于UTF-8编码,BOM由三个字节组成:0xEF0xBB0xBF。这些字节向软件发出信号,表明文件使用UTF-8编码。

本文将探讨在Java中为文件添加UTF-8 BOM的不同方法,包括字节级和文本级两种操作方式,并确保在处理和解释BOM时保持一致性。

2. 理解UTF-8 BOM

UTF-8 BOM通过特殊的字节序列表明文件采用UTF-8编码。虽然BOM不是必需的,但在某些场景下至关重要,特别是与旧版软件或特定平台交互时,这些平台依赖BOM来检测编码格式。

如前所述,UTF-8 BOM由三个十六进制字节组成:0xEF0xBB0xBF

此外,Unicode字符\uFEFF(即零宽非断行空格,ZWNBSP)也代表这个序列。该Unicode字符同样用于标识BOM的存在。

为确保代码一致性,我们在整个教程中将字节序列和Unicode表示定义为常量:

private static final byte[] UTF8_BOM = {(byte) 0xEF, (byte) 0xBB, (byte) 0xBF};
private static final String UTF8_BOM_UNICODE = "\uFEFF";

本教程将根据操作对象是字节还是字符串,分别使用原始字节或零宽非断行空格字符来添加BOM

3. 使用FileOutputStream和write方法

最简单的BOM添加方式是使用Java的FileOutputStream,它允许直接写入原始字节。这种方法提供对文件字节序列的精确控制,适合底层、面向字节的文件操作。

首先,我们手动在文件开头写入BOM字节(0xEF0xBB0xBF),然后写入UTF-8编码的内容:

private static final String FILE_PATH_OUTPUT_STREAM = "output_with_bom.txt";
private static final String TEST_CONTENT = "This is the content of the file";

@Test
public void givenText_whenAddingBomWithFileOutputStream_thenBOMAdded() throws IOException {
    try (FileOutputStream fos = new FileOutputStream(FILE_PATH_OUTPUT_STREAM)) {
        fos.write(UTF8_BOM);
        fos.write(TEST_CONTENT.getBytes(StandardCharsets.UTF_8));
    }

    String result = Files.readString(Path.of(FILE_PATH_OUTPUT_STREAM), StandardCharsets.UTF_8);
    assertTrue(result.startsWith(UTF8_BOM_UNICODE));
    assertTrue(result.contains(TEST_CONTENT));
}

我们定义文件路径、内容和表示UTF-8 BOM的字节数组。接着在try-with-resources块中打开文件,确保流操作完成后自动关闭。

然后使用write()方法先写入BOM字节,再写入UTF-8编码的内容。

最后通过Files.readString()读取文件,验证文件以BOM开头且包含预期内容。

注意:此方法在字节层面操作。读取内容时,BOM字节会自动转换为其Unicode等效字符

4. 使用Java Writer写入带BOM的UTF-8文件

在Java中写入带BOM的UTF-8文件时,可以利用包装输出流的Writer类。BufferedWriterPrintWriter允许我们在写入文件内容时添加BOM。这些方法处理编码并提供更高级的抽象,简化文件输出。

4.1 使用BufferedWriter和OutputStreamWriter

结合BufferedWriterOutputStreamWriter提供了一种高级方式管理UTF-8文件中的BOM:

private static final String FILE_PATH_BUFFERED_WRITER = "output_with_bom_buffered.txt";

@Test
public void givenText_whenAddingBomWithBufferedWriter_thenBOMAdded() throws IOException {
    try (OutputStreamWriter osw = new OutputStreamWriter(
            new FileOutputStream(FILE_PATH_BUFFERED_WRITER), StandardCharsets.UTF_8);
         BufferedWriter writer = new BufferedWriter(osw)) {

        writer.write(UTF8_BOM_UNICODE);
        writer.write(TEST_CONTENT);
    }

    String result = Files.readString(Path.of(FILE_PATH_BUFFERED_WRITER), StandardCharsets.UTF_8);
    assertTrue(result.startsWith(UTF8_BOM_UNICODE));
    assertTrue(result.contains(TEST_CONTENT));
}

此方法中,我们通过FileOutputStream打开文件,并用OutputStreamWriter创建写入通道(可指定UTF-8编码)。

然后将流包装在BufferedWriter中,实现可控的文件写入。我们使用Unicode转义序列UTF8_BOM_UNICODE在文件开头写入BOM,接着写入实际内容

最后读取内容验证BOM位于文件开头且包含我们的内容。

当文本文件和高级编码管理是优先考虑时,此方法更可取

4.2 使用PrintWriter和OutputStreamWriter

另一种选择是结合PrintWriterOutputStreamWriter。这种方法提供便捷的文本输出格式,尤其适合结构化文本:

private static final String FILE_PATH_PRINT_WRITER = "output_with_bom_print_writer.txt";

@Test
public void givenText_whenUsingPrintWriter_thenBOMAdded() throws IOException {
    try (PrintWriter writer = new PrintWriter(
            new OutputStreamWriter(
              new FileOutputStream(FILE_PATH_PRINT_WRITER), StandardCharsets.UTF_8))) {
        writer.write(UTF8_BOM_UNICODE);
        writer.println(TEST_CONTENT);
    }

    String result = Files.readString(Path.of(FILE_PATH_PRINT_WRITER), StandardCharsets.UTF_8);
    assertTrue(result.startsWith(UTF8_BOM_UNICODE));
    assertTrue(result.contains(TEST_CONTENT));
}

这里OutputStreamWriter再次指定UTF-8编码,而PrintWriter提供写入结构化文本的便捷方法。我们使用write()通过UTF8_BOM_UNICODE手动添加BOM,然后用println()写入内容。

5. 使用Apache Commons IO

Apache Commons IO简化了文件处理,我们可以利用其工具方法处理带BOM的内容写入。虽然仍需手动添加BOM,但库的工具方法简化了文件读写:

private static final String FILE_PATH_COMMONS_IO = "output_with_bom_commons_io.txt";

@Test
public void givenText_whenUsingCommonsIO_thenBOMAdded() throws IOException {
    byte[] bomAndContent = ArrayUtils.addAll(
      UTF8_BOM,
      TEST_CONTENT.getBytes(StandardCharsets.UTF_8)
    );
    FileUtils.writeByteArrayToFile(new File(FILE_PATH_COMMONS_IO), bomAndContent);

    String result = FileUtils.readFileToString(
      new File(FILE_PATH_COMMONS_IO), StandardCharsets.UTF_8
    );
    assertTrue(result.startsWith(UTF8_BOM_UNICODE));
    assertTrue(result.contains(TEST_CONTENT));
}

我们使用Apache Commons Lang的ArrayUtils.addAll()将BOM字节与内容字节合并到一个数组中。然后通过Apache Commons IO的FileUtils.writeByteArrayToFile()一步写入BOM和内容。

FileUtils.readFileToString()将整个文件读取为字符串,方便验证BOM和内容。注意:我们以原始字节形式添加BOM,但读取时会被解释为Unicode字符

当项目中已使用Apache Commons库时,此方法特别有效,它提供高效的文件I/O方法同时简化BOM管理。

6. 总结

本文探讨了在Java中为文件添加UTF-8字节顺序标记(BOM)的多种方法:

基础方案:使用FileOutputStream直接写入BOM字节
高级方案:结合OutputStreamWriterBufferedWriterPrintWriter管理BOM
第三方方案:利用Apache Commons IO简化文件操作

每种方法各有适用场景,选择时需考虑项目需求和技术栈。完整代码示例可在GitHub仓库中获取。


原始标题:How to Add a UTF-8 BOM in Java | Baeldung