有没有一种简单的方法可以避免处理文本编码问题?
13 回答
如果您从 String 开始,您还可以执行以下操作:
new ByteArrayInputStream(inputString.getBytes("UTF-8"))
您无法真正避免处理文本编码问题,但 Apache Commons 中有现有的解决方案:
Reader
至InputStream
:ReaderInputStream
Writer
至OutputStream
:WriterOutputStream
您只需要选择您选择的编码。
好吧,Reader 处理字符,InputStream 处理字节。编码指定您希望如何将字符表示为字节,因此您不能真正忽略这个问题。至于避免问题,我的意见是:选择一个字符集(例如“UTF-8”)并坚持下去。
关于如何实际做到这一点,正如已经指出的那样,“这些类的明显名称是ReaderInputStream和WriterOutputStream。 ”令人惊讶的是,“这些不包含在 Java 库中”,即使“相反”类InputStreamReader和OutputStreamWriter 是包括。
因此,很多人提出了自己的实现,包括Apache Commons IO。根据许可问题,您可能能够在项目中包含 commons-io 库,甚至可以复制部分源代码(可在此处下载)。
如您所见,两个类的文档都指出“JRE 支持的所有字符集编码都得到了正确处理”。
注意这里对其他答案之一的评论提到了这个错误。但这会影响 Apache Ant ReaderInputStream 类(此处),而不是Apache Commons IO ReaderInputStream 类。
另请注意,如果您从 String 开始,您可以跳过创建 StringReader 并使用Commons IO中的 org.apache.commons.io.IOUtils 一步创建 InputStream,如下所示:
InputStream myInputStream = IOUtils.toInputStream(reportContents, "UTF-8");
当然,您仍然需要考虑文本编码,但至少转换是一步完成的。
采用:
new CharSequenceInputStream(html, StandardCharsets.UTF_8);
这种方式不需要预先转换到String
再到byte[]
,这会分配更多的堆内存,以防报告很大。当流被读取时,它会立即从 StringBuffer 中转换为字节。
它使用来自 Apache Commons IO 项目的CharSequenceInputStream 。
这些类的明显名称是 ReaderInputStream 和 WriterOutputStream。不幸的是,这些不包含在 Java 库中。但是,谷歌是你的朋友。
我不确定它是否能解决所有文本编码问题,这简直就是噩梦。
有一个 RFE,但它已关闭,无法修复。
你无法避免文本编码问题,但Apache commons-io有
请注意,这些是彼得对 koders.com 的回答中提到的库,只是指向库的链接而不是源代码。
您是否正在尝试将 a 的内容写入Reader
a OutputStream
?如果是这样,您将更容易将 an 包装起来OutputStream
并将s 从 the写入OutputStreamWriter
s ,而不是尝试将 reader 转换为 an :char
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
您可以使用Cactoos(没有静态方法,只有对象):
您也可以反过来转换:
使用 WriterOutputStream 时的警告 - 它并不总是正确地处理将二进制数据写入文件/与常规输出流相同。我有一个问题,我花了一段时间才找到。
如果可以,我建议使用输出流作为基础,如果需要编写字符串,请使用流周围的 OUTputStreamWriter 包装器来完成。将文本转换为字节比其他方式更可靠,这可能是 WriterOutputStream 不是标准 Java 库的一部分的原因
这是基于 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);
}
}
}
}
}
用于使用 java 提供的内容读取流中的字符串。
InputStream s = new BufferedInputStream( new ReaderInputStream( new StringReader("a string")));