如何将 Reader 转换为 InputStream 并将 Writer 转换为 OutputStream?
Posted
技术标签:
【中文标题】如何将 Reader 转换为 InputStream 并将 Writer 转换为 OutputStream?【英文标题】:How to convert a Reader to InputStream and a Writer to OutputStream? 【发布时间】:2010-09-08 21:37:17 【问题描述】:有没有一种简单的方法可以避免处理文本编码问题?
【问题讨论】:
【参考方案1】:这些类的明显名称是 ReaderInputStream 和 WriterOutputStream。不幸的是,这些不包含在 Java 库中。然而,谷歌是你的朋友。
我不确定它是否能解决所有文本编码问题,这简直就是噩梦。
There is an RFE, 但它已关闭,不会修复。
【讨论】:
bugs.openjdk.java.net/browse/JDK-4103785 包含评论“我们有一个用于字符集编码的公共 API……没有令人信服的理由来添加这些类”——那么如何在 Java 7 中做到这一点,而无需额外的库,十二年后?【参考方案2】:您无法真正避免处理文本编码问题,但 Apache Commons 中有现有的解决方案:
Reader
到 InputStream
: ReaderInputStream
Writer
到 OutputStream
: WriterOutputStream
您只需要选择您选择的编码。
【讨论】:
仅供参考:ReaderInputStream 代码在读取字节的方式上有一个错误(它不适用于所有编码)。证明:illegalargumentexception.blogspot.com/2009/05/…有一个开放的bug:issues.apache.org/bugzilla/show_bug.cgi?id=40455 您可以在 Apache 的 commons-io 库中找到这些类:commons.apache.org/proper/commons-io @McDowell,您提到的错误在 Apache Ant 的实现中,而不是在 commons-io 中,因此与此答案无关。【参考方案3】:您是否尝试将Reader
的内容写入OutputStream
?如果是这样,您将更容易将OutputStream
包装在OutputStreamWriter
中并将char
s 从Reader
写入Writer
,而不是尝试将阅读器转换为InputStream
:
final Writer writer = new BufferedWriter(new OutputStreamWriter( urlConnection.getOutputStream(), "UTF-8" ) );
int charsRead;
char[] cbuf = new char[1024];
while ((charsRead = data.read(cbuf)) != -1)
writer.write(cbuf, 0, charsRead);
writer.flush();
// don't forget to close the writer in a finally block
【讨论】:
【参考方案4】:另外请注意,如果您从 String 开始,您可以跳过创建 StringReader 并使用来自Commons IO 的 org.apache.commons.io.IOUtils 一步创建 InputStream,如下所示:
InputStream myInputStream = IOUtils.toInputStream(reportContents, "UTF-8");
当然你仍然需要考虑文本编码,但至少转换是一步完成的。
【讨论】:
这个方法基本上是new ByteArrayInputStream(report.toString().getBytes("utf-8"))
,它涉及在内存中分配两个额外的报告副本。如果报告很大,那就不好了。看我的回答。【参考方案5】:
如果您从字符串开始,您还可以执行以下操作:
new ByteArrayInputStream(inputString.getBytes("UTF-8"))
【讨论】:
好的ReaderInputStream
实现需要更少的内存——应该不需要一次将所有字节存储在一个数组中。
我喜欢这个解决方案,因为当您需要对接受(例如)标准输入上的输入的代码进行单元测试时,它可以工作。【参考方案6】:
commons-io 2.0 拥有WriterOutputStream
【讨论】:
【参考方案7】:您无法避免文本编码问题,但 Apache commons-io 有
ReaderInputStream WriterOutputStream请注意,这些是彼得对 koders.com 的回答中提到的库,只是指向库的链接而不是源代码。
【讨论】:
【参考方案8】:好吧,Reader 处理字符,InputStream 处理字节。编码指定您希望如何将字符表示为字节,因此您不能真正忽略这个问题。至于避免问题,我的意见是:选择一个字符集(例如“UTF-8”)并坚持下去。
关于如何实际操作,正如已经指出的那样,“这些类的明显名称是 ReaderInputStream 和 WriterOutputStream。”令人惊讶的是,“这些不包含在 Java 库中”,即使包含了“相反”类 InputStreamReader 和 OutputStreamWriter。
因此,很多人提出了自己的实现,包括 Apache Commons IO。根据许可问题,您可能可以在项目中包含 commons-io 库,甚至可以复制部分源代码(可在here 下载)。
Apache ReaderInputStream: API / source code direct link Apache WriterOutputStream: API / source code direct link如您所见,两个类的文档都声明“JRE 支持的所有字符集编码都得到了正确处理”。
注意对此处其他答案之一的评论提到this bug。但这会影响 Apache Ant ReaderInputStream 类 (here),不 Apache Commons IO ReaderInputStream 类。
【讨论】:
【参考方案9】:使用 WriterOutputStream 时的警告 - 它并不总是能正确处理将二进制数据写入文件/与常规输出流相同。我有一个问题,我花了一段时间才找到。
如果可以,我建议使用输出流作为基础,如果需要编写字符串,请使用流周围的 OUTputStreamWriter 包装器来完成。将文本转换为字节比其他方式更可靠,这可能是 WriterOutputStream 不是标准 Java 库的一部分的原因
【讨论】:
【参考方案10】:用途:
new CharSequenceInputStream(html, StandardCharsets.UTF_8);
这种方式不需要预先转换为String
,然后再转换为byte[]
,这会分配更多的堆内存,以防报告很大。当流被读取时,它会即时转换为字节,直接从 StringBuffer 中读取。
它使用来自 Apache Commons IO 项目的CharSequenceInputStream。
【讨论】:
【参考方案11】:仅使用 java 提供的内容来读取流中的字符串。
InputStream s = new BufferedInputStream( new ReaderInputStream( new StringReader("a string")));
【讨论】:
ReaderInputStream 在 Apache Commons IO 中。【参考方案12】:你可以使用Cactoos(没有静态方法,只有对象):
new InputStreamOf(reader)
new OutputStreamTo(writer)
你也可以反过来转换:
new ReaderOf(inputStream)
new WriterTo(outputStream)
【讨论】:
【参考方案13】:这是一个简单的基于 UTF-8 编码的 WriterOutputStream 和 ReaderInputStream 的源代码。最后经过测试。
// https://www.woolha.com/tutorials/deno-utf-8-encoding-decoding-examples
public class WriterOutputStream extends OutputStream
final Writer writer;
int count = 0;
int codepoint = 0;
public WriterOutputStream(Writer writer)
this.writer = writer;
@Override
public void write(int b) throws IOException
b &= 0xFF;
switch (b >> 4)
case 0b0000:
case 0b0001:
case 0b0010:
case 0b0011:
case 0b0100:
case 0b0101:
case 0b0110:
case 0b0111:
count = 1;
codepoint = b;
break;
case 0b1000:
case 0b1001:
case 0b1010:
case 0b1011:
codepoint <<= 6;
codepoint |= b & 0b0011_1111;
break;
case 0b1100:
case 0b1101:
count = 2;
codepoint = b & 0b0001_1111;
break;
case 0b1110:
count = 3;
codepoint = b & 0b0000_1111;
break;
case 0b1111:
count = 4;
codepoint = b & 0b0000_0111;
break;
if (--count == 0)
writer.write(codepoint);
public class ReaderInputStream extends InputStream
final Reader reader;
int count = 0;
int codepoint;
public ReaderInputStream(Reader reader)
this.reader = reader;
@Override
public int read() throws IOException
if (count-- > 0)
int r = codepoint >> (count * 6);
r &= 0b0011_1111;
r |= 0b1000_0000;
return r;
codepoint = reader.read();
if (codepoint < 0)
return -1;
if (codepoint > 0xFFFF)
return 0;
if (codepoint < 0x80)
return codepoint;
if (codepoint < 0x800)
count = 1;
int v = (codepoint >> 6) | 0b1100_0000;
return v;
count = 2;
int v = (codepoint >> 12) | 0b1110_0000;
return v;
以及验证 65536 个字符中的每一个是否正确编码和解码的测试用例,以及验证它是否与 Java 编码匹配。代理验证(2 个字符编码)被忽略,因为这是在 Java 中处理的。
@Test
public void testAll() throws IOException
for (char i = 0; i < 0xFFFF; i++)
CharArrayReader car = new CharArrayReader(new char[] i );
ReaderInputStream rtoi = new ReaderInputStream(car);
byte[] data = IO.read(rtoi);
CharArrayWriter caw = new CharArrayWriter();
try (WriterOutputStream wtoo = new WriterOutputStream(caw))
wtoo.write(data);
char[] translated = caw.toCharArray();
assertThat(translated.length).isEqualTo(1);
assertThat((int) translated[0]).isEqualTo(i);
if (!Character.isSurrogate((char) i))
try (InputStream stream = new ByteArrayInputStream(data))
caw = new CharArrayWriter();
IO.copy(data, caw);
translated = caw.toCharArray();
assertThat(translated.length).isEqualTo(1);
assertThat((int) translated[0]).isEqualTo(i);
【讨论】:
以上是关于如何将 Reader 转换为 InputStream 并将 Writer 转换为 OutputStream?的主要内容,如果未能解决你的问题,请参考以下文章
无法将类型为“System.DateTime”的对象强制转换为类型“System.String”