9

这篇文章提供了一个在 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);

然后读取输入。

它看起来和工作都很棒,但我想知道 tasklist 使用的字符集是否有可能不是默认字符集并且此调用可能会失败?

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

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

4

4 回答 4

12

可以把它分成两部分:

  1. windows部分
    从java你正在执行一个windows命令——在“windowsland”中的jvm外部。当 java Runtime 类执行 windows 命令时,它使用控制台的 DLL & 所以在 windows 中看起来好像命令在控制台中运行
    Q:当我在控制台中运行 C:\windows\system32\tasklist.exe 时,什么是结果的字符编码(Windows 术语中的“代码页”)?

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

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

    • Windows 代码页号和等效的 java 字符集名称之间的完整映射可以从这里派生 - Code Page Identifiers (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);
    }

谢谢你的Q!- 很好玩。

于 2012-11-20T23:01:35.080 回答
5

实际上,所使用的字符集tasklist总是与系统默认不同。

另一方面,只要输出限制为ASCII ,使用默认值是非常安全的。通常可执行模块的名称中只有 ASCII 字符。

因此,要获得正确的字符串,您必须将 (ANSI) Windows 代码页转换为 OEM 代码页,并将后者作为字符集传递给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);

这种方法对我windows-1251很有效IBM866

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


Windows 上有两种编码:ANSIOEM

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

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

对于英文系统,这对通常是Windows-1252CP850

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

在记事本中查看时,我得到ЏаЁўҐв而不是Привет (嗨!) 。
µTorrent显示为зTorrent

您不能更改tasklist.


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

cmd /c echo Hi>echo.txt

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

cmd /u /c echo Hi>echo.txt

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

于 2012-11-19T21:45:34.513 回答
3

为什么不通过JNA使用 Windows API ,而不是生成进程?像这样:

import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.Tlhelp32;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.WinNT;
import com.sun.jna.win32.W32APIOptions;
import com.sun.jna.Native; 

public class ListProcesses {
    public static void main(String[] args) {
        Kernel32 kernel32 = (Kernel32) Native.loadLibrary(Kernel32.class, W32APIOptions.UNICODE_OPTIONS);
        Tlhelp32.PROCESSENTRY32.ByReference processEntry = new Tlhelp32.PROCESSENTRY32.ByReference();          

        WinNT.HANDLE snapshot = kernel32.CreateToolhelp32Snapshot(Tlhelp32.TH32CS_SNAPPROCESS, new WinDef.DWORD(0));
        try  {
            while (kernel32.Process32Next(snapshot, processEntry)) {             
                System.out.println(processEntry.th32ProcessID + "\t" + Native.toString(processEntry.szExeFile));
            }
        }
        finally {
            kernel32.CloseHandle(snapshot);
        }
    } 
}

我在其他地方发布了类似的答案。

于 2012-11-20T17:28:31.723 回答
0

有一种更好的方法可以检查正在运行的进程,甚至可以通过 java 运行 OS 命令:ProcessProcessBuilder

关于 Charset,您可以随时查询操作系统支持的 charset,根据需要获取EncoderDecoder 。

[编辑]让我们分解一下;无法知道给定 String 的字节在哪种编码中,因此您唯一的选择是获取这些字节,根据需要改变顺序(如果您曾经处于这样的环境中,其中一个进程可以为您提供一个数组不同顺序的字节,使用 ByteBuffer 处理),并使用支持的多个 CharsetDecoder 将字节解码为合理的输出。

这太过分了,需要您估计给定的输出可能是 UTF-8、UTF-16 或任何其他编码。但至少您可以使用其中一种可能的字符集解码给定的输出,然后尝试使用处理后的输出来满足您的需要。

由于我们讨论的是由运行 JVM 本身的同一操作系统运行的进程,因此您的输出很可能是 availableCharsets() 方法返回的字符集编码之一。

于 2012-11-12T19:00:46.237 回答