以charset安全的方式获取Windows上的进程列表

ass*_*ias 8 java list process character-encoding

这篇文章提供了一个解决方案来检索Windows下正在运行的进程列表.实质上它确实:

String cmd = System.getenv("windir") + "\\system32\\" + "tasklist.exe";
Process p = Runtime.getRuntime().exec(cmd);
InputStreamReader isr = new InputStreamReader(p.getInputStream());
BufferedReader input = new BufferedReader(isr);
Run Code Online (Sandbox Code Playgroud)

然后读取输入.

它看起来和工作得很好,但我想知道任务列表使用的字符集是否可能不是默认的字符集,并且此调用可能会失败?

例如,关于不同可执行文件的另一个问题表明它可能会导致一些问题.

如果是这种情况,有没有办法确定适当的字符集是什么?

Gle*_*est 12

可以将其分为两部分:

  1. windows部分
    从java开始执行Windows命令 - 外部到"Windows land"中的jvm.当java Runtime类执行一个windows命令时,它使用DLL作为控制台,因此对Windows来说就像命令在控制台中运行一样Q:当我在控制台中
    运行C:\ windows\system32\tasklist.exe时,是什么结果的字符编码(Windows术语中的"代码页")?

    • 不带参数的windows"chcp"命令给出了控制台的活动代码页码(例如850用于Multilingual-Latin-1,1252用于Latin-1).请参阅Windows Microsoft代码页, Windows OEM代码页, Windows ISO代码页
      默认系统代码页最初是根据您的系统区域设置设置的(键入systeminfo以查看此信息或控制面板 - >区域和语言).
    • Windows OS/.NET函数getACP()也提供此信息

  2. java部分:
    如何解码来自windows代码页"x"的java字节流(例如850或1252)?

    • Windows代码页码和等效的java charset名称之间的完整映射可以从这里派生- 代码页标识符(Windows)
    • 但是,实际上可以添加以下前缀之一来实现映射:
      ""(无)用于ISO,"IBM"或"x-IBM"用于OEM,"windows-"或"x-windows-"用于Microsoft /视窗.
      例如ISO-8859-1或IBM850或windows-1252

完整解决方案

    String cmd = System.getenv("windir") + "\\system32\\" + "chcp.com";
    Process p = Runtime.getRuntime().exec(cmd);
    // Use default charset here - only want digits which are "core UTF8/UTF16"; 
    // ignore text preceding ":"
    String windowsCodePage = new Scanner(
        new InputStreamReader(p.getInputStream())).skip(".*:").next();

    Charset charset = null;
    String[] charsetPrefixes = 
        new String[] {"","windows-","x-windows-","IBM","x-IBM"};
    for (String charsetPrefix : charsetPrefixes) {
        try {
            charset = Charset.forName(charsetPrefix+windowsCodePage);
            break;
        } catch (Throwable t) {
        }
    }
    // If no match found, use default charset
    if (charset == null) charset = Charset.defaultCharset();

    cmd = System.getenv("windir") + "\\system32\\" + "tasklist.exe";
    p = Runtime.getRuntime().exec(cmd);
    InputStreamReader isr = new InputStreamReader(p.getInputStream(), charset);
    BufferedReader input = new BufferedReader(isr);

    // Debugging output
    System.out.println("matched codepage "+windowsCodePage+" to charset name:"+
            charset.name()+" displayName:"+charset.displayName());
    String line;
    while ((line = input.readLine()) != null) {
           System.out.println(line);
    }
Run Code Online (Sandbox Code Playgroud)

谢谢Q!- 很好玩.


Ale*_*nov 5

实际上,所使用的字符集tasklist始终从系统默认值不同.

另一方面,只要输出限制为ASCII,就可以非常安全地使用默认值.通常,可执行模块的名称中只包含ASCII字符.

因此,要获得正确的字符串,您必须将(ANSI)Windows代码页转换为OEM代码页,并将后者作为charset传递给InputStreamReader.

似乎这些编码之间没有全面的映射.可以使用以下映射:

Map<String, String> ansi2oem = new HashMap<String, String>();
ansi2oem.put("windows-1250", "IBM852");
ansi2oem.put("windows-1251", "IBM866");
ansi2oem.put("windows-1252", "IBM850");
ansi2oem.put("windows-1253", "IBM869");

Charset charset = Charset.defaultCharset();
String streamCharset = ansi2oem.get(charset.name());
if (streamCharset) {
    streamCharset = charset.name();
}
InputStreamReader isr = new InputStreamReader(p.getInputStream(),
                                              streamCharset);
Run Code Online (Sandbox Code Playgroud)

这种方法适合我windows-1251IBM866配对.

要获取Windows使用的当前OEM编码,您可以使用GetOEMCP函数.返回值取决于" 区域和语言"控制面板中" 管理"选项卡上的非Unicode程序的语言设置.需要重新启动才能应用更改.


Windows上有两种编码:ANSIOEM.

前者由在GUI模式下运行的非Unicode应用程序使用.
后者由控制台应用程序使用.控制台应用程序无法显示无法在当前OEM编码中表示的字符.

由于tasklist是控制台模式应用程序,其输出始终采用当前的OEM编码.

对于英语系统,该对通常是Windows-1252CP850.

因为我在俄罗斯,我的系统有以下编码:Windows-1251CP866.
如果我将输出捕获tasklist到文件中,则该文件无法正确显示西里尔字符:

在记事本中查看时,我得到的???????????? (嗨!).
µTorrent显示为?Torrent.

您无法更改使用的编码tasklist.


但是,可以更改输出编码cmd.如果您将/u开关传递给它,它将以UTF-16编码输出所有内容.

cmd /c echo Hi>echo.txt
Run Code Online (Sandbox Code Playgroud)

大小echo.txt为4个字节:Hi两个字节用于新行(\r\n)的两个字节.

cmd /u /c echo Hi>echo.txt
Run Code Online (Sandbox Code Playgroud)

现在大小echo.txt为8个字节:每个字符用两个字节表示.