【问题标题】:Convert Latin-1 content of InputStream into UTF-8 String将 InputStream 的 Latin-1 内容转换为 UTF-8 字符串
【发布时间】:2012-08-04 23:44:17
【问题描述】:

我需要将 InputStream 的内容转换为字符串。这里的难点在于输入编码,即Latin-1。我尝试了几种方法并使用 String、getBytes、char[] 等对 sn-ps 进行编码,以便直接进行编码,但似乎没有任何效果。

最后,我想出了下面的可行解决方案。但是,这段代码对我来说似乎有点冗长,即使对于 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;
}

【问题讨论】:

    标签: java string character-encoding inputstream


    【解决方案1】:

    首先,对您已经采取的方法提出一些批评。当您只需要 char[512] 时,您不应该不必要地使用 NIO CharBuffer。您也不需要在每次迭代时都 clear 缓冲区。

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

    您还应该知道,只有 constructing a 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 字节的原因?
    • 2048字节的缓冲区只是个人喜好;您可以使用任何为运行时间和内存消耗提供合理折衷的方法。
    【解决方案2】:

    我不明白它怎么能更简单。我做了一次有点不同..如果你已经有一个字符串,你可以这样做:

    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 中转换 Streams 时,它不会简单得多,所以去吧。 :)

    【讨论】:

    • 这里有很多改进。首先,在reader.readLine 没有找到行终止符的情况下,这不会产生准确的文本;它将附加一个最初不存在的尾随\n。此外,BufferedReader 会自动使用默认的系统编码。将InputStreamReader 构造为使用StandardCharsets.ISO_8859_1 是一个更好的主意,因此您可以一步使用StringBuilder.toString 来获取正确解码的字符串。
    • 关于\n:我接受了改进谢谢,我并没有真正关注InputStream->String转换,只是为了完成示例。处理编码的不同方式仍然可以恕我直言,罗马也有很多方式。 ;-) 但就像我说的那样,这只是一种选择。任何像 commonsIO 这样的实用程序都会清理代码,但本质上是相同的,并且依赖于一个额外的库。如果您更频繁地使用它是有道理的.. 个人选择的问题。
    【解决方案3】:

    如果你不想自己动手,你可以看看 apache commons io 项目,IOUtils.toString(InputStream input, String encoding),它似乎可以满足你的需求。我自己没有尝试过这种方法,但 java 文档指出“使用指定的字符编码将 InputStream 的内容作为字符串获取。”

    【讨论】:

      【解决方案4】:

      Guava 的 IO 包这种方式真的很不错。

      Files.toString(yourFile, CharSets.ISO_8859_1)
      

      或来自流

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

      【讨论】:

        【解决方案5】:

        我刚刚发现this answer到问题Read/convert an InputStream to a 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作为分隔符。这种方法确实很有趣也很漂亮,但也可能不可取。
        • 我想对此有更多的了解:这种模式有什么问题?它不应该是相当轻量级的吗?
        • 这似乎是一个有趣的解决方案,但却滥用了Scanner。在您链接到的答案中,他们说得很好... 愚蠢的Scanner 把戏
        猜你喜欢
        • 2011-05-17
        • 2010-10-12
        • 1970-01-01
        • 2015-03-26
        • 2021-07-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多