将InputStream中的Latin-1内容转换为UTF-8字符串

8
我需要将InputStream的内容转换为字符串。难点在于输入编码,即Latin-1。我尝试了几种方法和代码片段,使用String、getBytes、char[]等来处理编码问题,但是似乎都没有起作用。
最终,我想出了下面可行的解决方案。不过,即使对于Java来说,这个代码看起来仍然有些冗长。所以问题是:
是否有更简单、更优雅的方法来实现这里所做的工作?
private String convertStreamToStringLatin1(java.io.InputStream is)
        throws IOException {

    String text = "";

    // setup readers with Latin-1 (ISO 8859-1) encoding
    BufferedReader i = new BufferedReader(new InputStreamReader(is, "8859_1"));

    int numBytes;
    CharBuffer buf = CharBuffer.allocate(512);
    while ((numBytes = i.read(buf)) != -1) {
        text += String.copyValueOf(buf.array(), 0, numBytes);
        buf.clear();
    }

    return text;
}
5个回答

7
首先,对于您已经采取的方法,有一些批评意见。当您仅需要一个 char[512] 时,不应该不必要地使用 NIO 的 CharBuffer。您也不需要在每次迭代中清除缓冲区。

int numBytes;
final char[] buf = new char[512];
while ((numBytes = i.read(buf)) != -1) {
    text += String.copyValueOf(buf, 0, numBytes);
}

你还应该知道,仅使用这些参数构造一个String也会产生相同的效果,因为构造函数也会复制数据。

子数组的内容被复制;对字符数组的后续修改不会影响新创建的字符串。


你可以使用动态的 ByteArrayOutputStream,它会增长内部缓冲区以容纳所有数据。然后,你可以使用从 toByteArray 中获取的整个 byte[] 解码为一个 String
优点是推迟解码到最后避免单独解码片段;虽然这可能适用于简单的字符集,如ASCII或ISO-8859-1,但对于多字节方案,如UTF-8和UTF-16,它将无法工作。这意味着在将来更改字符编码更加容易,因为代码不需要修改。
private static final String DEFAULT_ENCODING = "ISO-8859-1";

public static final String convert(final InputStream in) throws IOException {
  return convert(in, DEFAULT_ENCODING);
}

public static final String convert(final InputStream in, final String encoding) throws IOException {
  final ByteArrayOutputStream out = new ByteArrayOutputStream();
  final byte[] buf = new byte[2048];
  int rd;
  while ((rd = in.read(buf, 0, 2048) >= 0) {
    out.write(buf, 0, rd);
  }
  return new String(out.toByteArray(), 0, encoding);
}

感谢您的重要评论。您的第一个解决方案就像我所寻找的一样。然而,我可以理解您第二个解决方案的观点,它非常适用于一般情况。我猜这也是为什么您的示例中缓冲区大小为2048字节的原因? - cyroxx
2048字节的缓冲区只是个人偏好;您可以使用任何在运行时间和内存消耗之间提供合理权衡的内容。 - obataku

3

我不认为这有多复杂。我曾经以稍微不同的方式做过一次...如果你已经有一个字符串,你可以这样做:

new String(originalString.getBytes(), "ISO-8859-1");

所以类似这样的东西也可以运作:
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
  sb.append(line + "\n");
}
is.close();
return new String(sb.toString().getBytes(), "ISO-8859-1");

编辑:我应该补充一下,这只是您已经运作良好的解决方案的替代品。当涉及到在Java中转换流时,并不会更简单,所以请继续使用它。


这里有许多改进。首先,在reader.readLine未找到行终止符的情况下,它将附加一个不是原本存在的尾随\n。此外,BufferedReader将自动使用默认系统编码。更好的想法是仅使用StandardCharsets.ISO_8859_1构造InputStreamReader,因此您可以一步获取正确解码的字符串使用StringBuilder.toString - obataku
1
关于\n:我接受这个改进,谢谢。我并没有注意到 InputStream->String 的转换,只是为了完成示例而已。处理编码的不同方式仍然可以,我认为有很多方法可以达到目的。;-) 但是,正如我所说,这只是一种替代方案。任何像commonsIO这样的工具都可以清理代码,并且本质上相同,但是需要依赖于额外的库。如果您经常使用它,那么就有意义了...这是个人选择的问题。 - Blacklight

1

我刚刚发现这个答案对于将InputStream转换为String的问题也适用于我的问题,请参见下面的代码。无论如何,我非常感谢你们迄今为止给出的答案。

private String convertStreamToString(InputStream is, String charsetName) {
    try {
        return new java.util.Scanner(is, charsetName).useDelimiter("\\A").next();
    } catch (java.util.NoSuchElementException e) {
        return "";
    }
}

因此,为了从Latin-1进行编码,请这样调用:

String message = convertStreamToString(is, "8859_1");

你应该知道Scanner在内部为分隔符编译了一个正则表达式Pattern。这种方法确实有趣且巧妙,但也可能不可取。 - obataku
我想更深入地了解这个问题:那种模式有什么问题吗?它不应该是相当轻量级的吗? - cyroxx
它似乎是一个有趣的解决方案,但滥用了“Scanner”。在你链接到的答案中,他们说得很好...这是一个愚蠢的“Scanner”技巧。 - obataku

0

Guava的IO包非常好用。

Files.toString(yourFile, CharSets.ISO_8859_1)

或者从流中获取

new String(ByteStreams.toByteArray(stream), CharSets.ISO_8859_1)

0

如果您不想自己编写代码,可以看看Apache Commons IO项目,IOUtils.toString(InputStream input, String encoding),它似乎可以实现您想要的功能。我自己没有尝试过这种方法,但Java文档中指出:“使用指定的字符编码将InputStream的内容作为字符串获取。”


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