Process.getInputStream()使用哪种编码?

8
在Java程序中,我通过ProcessBuilder创建一个新的Process
args[0] = directory.getAbsolutePath() + File.separator + program;
ProcessBuilder pb = new ProcessBuilder(args);
pb.directory(directory);
final Process process = pb.start();

然后,我使用一个新的 Thread 读取处理标准输出。

new Thread() {
    public void run() {
        BufferedReader reader = new BufferedReader(
            new InputStreamReader(process.getInputStream()));
        String line = "";
        while ((line = reader.readLine()) != null) {
            System.out.println(line);
    }
}.start();

然而,当该过程输出非ASCII字符(例如'é')时,line中会有字符'\uFFFD'
getInputStream返回的InputStream中,编码是什么(我的平台是欧洲的Windows)?
我应该如何更改才能使line包含预期的数据(即'\u00E9'代表'é')?
编辑:我尝试了new InputStreamReader(...,"UTF-8")é变成了\uFFFD

BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8")); - Cris
@Cris如果你想回答,请写一个答案而不是评论。 - rds
8个回答

9
一个InputStream是一个二进制流,因此没有编码。当您创建Reader时,需要知道要使用什么字符编码,这将取决于您调用的程序产生的内容(Java 不会以任何方式转换它)。
如果您没有为InputStreamReader指定任何内容,则它将使用平台默认编码,这可能不合适。有另一个构造函数允许您指定编码。
如果您知道要使用什么编码(并且确实需要知道),请参考另一个构造函数
new InputStreamReader(process.getInputStream(), "UTF-8") // for example

1
正如@AlexR所指出的那样,相同的推理也适用于数据的写入。 - Thilo
1
UTF-8是Java中的默认编码,因此“UTF-8”无法解决问题。解决方案已经接近,只需要使用“Cp1252”或“ISO-8859-1”(取决于getInputStream()返回的内容)。 - rds
2
UTF-8不是Java中的默认编码。实际上,Java没有任何默认编码,它总是使用与平台相关的编码(可以通过环境变量和系统属性进行控制)。这并不是应用程序开发人员通常应该依赖的东西。最好始终明确指定所需的编码。 - Thilo
UTF-16 是 Java 内部字符的标准表示形式,因此使用无符号 16 位的 'char' 原始数据类型。InputStreamReader 将始终转换为 UTF-16。虽然 InputStream 是二进制流,但如果它表示字符,则字节将遵循用于创建资源的任何编码方式。Thilo 提到的 InputStreamReader 构造函数包括一个参数,用于指定该资源的编码方式 - 流应如何处理。 - Matthew Oakley

8
有趣的是,在Windows上运行时:
ProcessBuilder pb = new ProcessBuilder("cmd", "/c dir");
Process process = pb.start();

对于IT技术相关内容,CP437代码页非常有效。

new InputStreamReader(process.getInputStream(), "CP437");

正如其他人所说,InputStream包含平台编码的字符。由于我使用的是现代操作系统,我的编码是UTF-8;而你使用的是Windows,你的编码是CP437。 - rds
1
谢谢,CP437 是唯一适用于我(Windows + 西班牙字符)的字符集名称。 - IvanRF
3
现在应该使用CP850编码。奇怪的是,所有的Windows系统似乎都设置为windows-1252/cp1252(至少在西欧地区),但控制台专门使用CP850编码。CP437是CP850编码的祖先。打开命令提示符并运行“chcp”命令可以告诉您它所使用的确切编码来打印字符数据。 - Etienne Delavennat
另外,用于解析InputStream的编码取决于ProcessBuilder构建的程序。例如:对于cmd,使用CP850;对于一些其他的Windows工具,您可能会直接调用它们(而不是将它们包装在cmd中),使用windows-1252;如果您调用的程序输出UTF-8,则可能使用UTF-8。这是特定于程序的,应在程序文档中查找。 - Etienne Delavennat
1
不错!我已经检查了一些Windows 10的设置。对于各种欧洲设置,它是CP850,但对于默认设置(美国设置),它仍然是CP437。 - jan.supol

4
据我所知,操作系统流是字节流,这里没有字符。 InputStreamReader 构造函数使用 jvm 默认字符集 java.nio.charset.Charset#defaultCharset(),您可以使用另一个构造函数来明确指定字符集。

是的,我必须使用 new InputStreamReader(...,"ISO-8859-1") - rds

2
根据http://www.fileformat.info/info/unicode/char/e9/index.htm,'\uFFFD'是字符'é'的Unicode编码。这实际上意味着您正在正确读取流。您的问题在于写入。
默认情况下,Windows控制台不支持Unicode。因此,如果要测试代码,请打开文件并将流写入其中。但不要忘记设置编码为UTF-8

正确。new PrintWriter(OutputStreamWriter(..., "Cp1252")) 其中Cp1252是带有Windows扩展的Latin-1编码,主要在欧洲西部(法国、德国等地区)使用。 - Joop Eggen
1
为什么当我有字符0xFFFD(又称“替换字符”)时,你要指向我想要的字符(0xE9)?http://www.fileformat.info/info/unicode/char/fffd/index.htm - rds

2

科学的

在Windows上,这完美地运作:

private static final Charset CONSOLE_ENCODING;
static {
    Charset enc = Charset.defaultCharset();
    try {
        String example = "äöüßДŹす";
        String command = File.separatorChar == '/' ? "echo " + example : "cmd.exe /c echo " + example;
        Process exec = Runtime.getRuntime().exec(command);
        InputStream inputStream = exec.getInputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        while (exec.isAlive()) {
            Thread.sleep(100);
        }
        byte[] buff = new byte[inputStream.available()];
        if (buff.length > 0) {
            int count = inputStream.read(buff);
            baos.write(buff, 0, count);
        }

        byte[] array = baos.toByteArray();
        for (Charset charset : Charset.availableCharsets().values()) {
            String s = new String(array, charset);
            if (s.equals(example)) {
                enc = charset;
                break;
            }
        }
    } catch (InterruptedException e) {
        throw new Error("Could not determine console charset.", e);
    } catch (IOException e) {
        throw new Error("Could not determine console charset.", e);
    }
    CONSOLE_ENCODING = enc;
}

根据规范:没有暗示jvm运行时编码会发生改变。我们无法确定在运行时编码是否改变,以及在这种情况下字符集是否仍然正确。

嗯...好主意,但实际上在我的系统上并不起作用(Windows 7 SP1,64位,Java 8 build 71)--没有任何可用的编码可以产生原始字符串。问题似乎是给定的示例字符串甚至没有正确传输到系统,而是产生了“?”字符。除此之外,输出中还会得到额外的"\r\n"行尾符。 - Franz D.

1
如果像我一样,知道要使用哪种编码来处理所有的输入/输出,那么你可以在Java API调用中对某些(不是全部)CreateReader方法进行编码,其他答案已经指出了这一点。但是这将在源代码中硬编码,这可能或可能不是可以接受的。阅读this answer后,我发现了一个更好的方法,它揭示了你可以在JVM启动之前设置所需的编码方式。
java -Dfile.encoding=ISO-8859-1 ...

0

在此使用commons-lang jar文件 - StringEscapeUtils.escapeHtml

BufferedReader br = new BufferedReader(
    new InputStreamReader(StringEscapeUtils.escapeHtml(conn.getInputStream()));

0

我把这个作为评论,但是我看到后面有一个答案,所以现在可能是多余的 :)

BufferedReader br = new BufferedReader(
    new InputStreamReader(conn.getInputStream(), "UTF-8"));

UTF-8 是默认编码。因此,这并没有帮助。 - rds

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