【问题标题】:Java Spring returning CSV file encoded in UTF-8 with BOMJava Spring 返回以 UTF-8 编码并带有 BOM 的 CSV 文件
【发布时间】:2018-02-28 00:37:06
【问题描述】:

显然,要让 excel 很好地打开 CSV 文件,它的开头应该有字节顺序标记。 CSV 的下载是通过在控制器中写入HttpServletResponse 的输出流来实现的,因为数据是在请求期间生成的。当我尝试写入 BOM 字节时出现异常 - java.io.CharConversionException: Not an ISO 8859-1 character: [](即使我指定的编码是 UTF-8)。


有问题的控制器方法

@RequestMapping("/monthly/list")
public List<MonthlyDetailsItem> queryDetailsItems(
        MonthlyDetailsItemQuery query,
        @RequestParam(value = "format", required = false) String format,
        @RequestParam(value = "attachment", required = false, defaultValue="false") Boolean attachment,
        HttpServletResponse response) throws Exception 
{   
    // load item list
    List<MonthlyDetailsItem> list = detailsSvc.queryMonthlyDetailsForList(query);
    // adjust format
    format = format != null ? format.toLowerCase() : "json";
    if (!Arrays.asList("json", "csv").contains(format)) format = "json";

    // modify common response headers
    response.setCharacterEncoding("UTF-8");
    if (attachment)
        response.setHeader("Content-Disposition", "attachment;filename=duomenys." + format);

    // build csv
    if ("csv".equals(format)) {
        response.setContentType("text/csv; charset=UTF-8");
        response.getOutputStream().print("\ufeff");
        response.getOutputStream().write(buildMonthlyDetailsItemCsv(list).getBytes("UTF-8"));
        return null;
    }

    return list;
}

【问题讨论】:

  • 您可以试试response.getOutputStream().write("\ufeff".getBytes("UTF-8")); 的 BOM 部分吗?

标签: java spring utf-8 byte-order-mark


【解决方案1】:

这没有多大意义:BOM 用于 UTF-16; UTF-8 没有字节顺序。您使用 setCharacterEncoding 设置的编码用于 getWriter,而不是用于 getOutputStream。

更新:

好的,试试这个:

if ("csv".equals(format)) {
    response.setContentType("text/csv; charset=UTF-8");
    PrintWriter out = response.getWriter();
    out.print("\uFEFF");
    out.print(buildMonthlyDetailsItemCsv(list));
    return null;
}

我假设 buildMonthlyDetailsItemCsv 方法返回一个字符串。

【讨论】:

  • 这不是真的。见en.wikipedia.org/wiki/Byte_order_mark。为 UTF-8 定义了一个 BOM,但这不是强制性的。唉,Java 无法自动处理 UTF-8 BOM。
  • @vanje 对 UTF-8 编码没用。
  • 是的,也许。但这不是重点。有时你必须处理它。无用与否。
  • 关于我的回答,我必须处理由 Excel 创建的文件,这些文件有 BOM,我解释的过程对我有用。 @MauricePerry BOM 值不是 UTF8,因此被 UTF8 忽略,我怀疑这就是 Excel 将其用作元数据(我猜)值的原因。但是程序仍然需要处理它。
【解决方案2】:

我刚刚遇到了同样的问题。对我有用的解决方案是从响应对象中获取输出流并将其写入如下

    // first create an array for the Byte Order Mark
    final byte[] bom = new byte[] { (byte) 239, (byte) 187, (byte) 191 }; 
    try (OutputStream os = response.getOutputStream()) {
        os.write(bom);

        final PrintWriter w = new PrintWriter(new OutputStreamWriter(os, "UTF-8"));
        w.print(data);
        w.flush();
        w.close();
    } catch (IOException e) {
        // logit
    }

所以在 OutputStreamWriter 上指定了 UTF-8。


作为对此的补充,我应该补充一点,同一个应用程序需要允许用户上传文件,这些文件可能有也可能没有 BOM。这可以通过使用类org.apache.commons.io.input.BOMInputStream 来处理,然后使用它来构造org.apache.commons.csv.CSVParser。 BOMInputStream 包含一个方法hasBOM() 来检测文件是否有BOM。 我第一次遇到的一个问题是hasBOM() 方法从底层流中读取(显然!),所以处理这个问题的方法是首先标记流,然后在测试之后如果它没有 BOM,重置流。我用于此的代码如下所示:

try (InputStream is = uploadFile.getInputStream();
        BufferedInputStream buffIs = new BufferedInputStream(is);
        BOMInputStream bomIn = new BOMInputStream(buffIs);) {
      buffIs.mark(LOOKAHEAD_LENGTH);
      // this should allow us to deal with csv's with or without BOMs
      final boolean hasBOM = bomIn.hasBOM();
      final BufferedReader buffReadr = new BufferedReader(
          new InputStreamReader(hasBOM ? bomIn : buffIs, StandardCharsets.UTF_8));

      // if this stream does not have a BOM, then we must reset the stream as the test
      // for a BOM will have consumed some bytes
      if (!hasBOM) {
        buffIs.reset();
      }

      // collect the validated entity details
      final CSVParser parser = CSVParser.parse(buffReadr,
          CSVFormat.DEFAULT.withFirstRecordAsHeader());
      // Do stuff with the parser
      ...
  // Catch and clean up

希望这对某人有所帮助。

【讨论】:

  • 这对我很有帮助,但发现我必须写两次 bom 否则它不会出现在保存的 CSV 文件中。不知道为什么。也许第一个被浏览器使用,第二个保存到用户计算机上的 CSV 文件中。它在 4 种不同的浏览器中保持一致。
猜你喜欢
  • 2012-07-18
  • 2015-07-29
  • 2018-08-03
  • 2020-01-25
  • 1970-01-01
  • 2017-05-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多