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

这篇文章提供了一个解决方案来检索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使用的charset是否可能不是默认的字符集,并且这个调用可能会失败?

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

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

可以将其分为两部分:

  1. 窗户部分
    从java开始执行Windows命令 – 外部到“Windows land”中的jvm。 当java Runtime类执行一个windows命令时,它使用DLL作为控制台,因此对Windows来说就像命令在控制台中运行一样
    问:当我在控制台中运行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字符集名称之间的完整映射可以从这里派生- 代码页标识符(Windows)
    • 但是,实际上可以添加以下前缀之一来实现映射:
      “”(无)用于ISO,“IBM”或“x-IBM”用于OEM,“windows-”或“x-windows-”用于Microsoft / Windows。
      例如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! – 很好玩。

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

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

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

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

 Map ansi2oem = new HashMap(); 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-1251IBM866对。

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


Windows上有两种编码: ANSIOEM

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

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

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

当我在俄罗斯时,我的系统有以下编码: Windows-1251和CP866 。
如果我将任务列表的输出捕获到文件中,则该文件无法正确显示西里尔字符:

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

您无法更改任务列表使用的编码。


但是,可以更改cmd的输出编码。 如果您通过/u切换到它,它将以UTF-16编码输出所有内容。

 cmd /c echo Hi>echo.txt 

echo.txt的大小为4个字节: Hi为2个字节,新行为2个字节( \r\n )。

 cmd /u /c echo Hi>echo.txt 

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

为什么不通过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); } } } 

我在别处发布了类似的答案。

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

对于Charset,您可以随时向操作系统查询支持的字符集,并根据您的需要获取编码器或解码器 。

[编辑]让我们把它分解; 没有办法知道给定String的字节在哪个编码中,所以你唯一的选择是获取那些字节,根据需要改变排序(如果你曾经在这样一个过程可以给你一个数组的环境中不同排序的字节,使用ByteBuffer来处理),并使用支持的多个CharsetDecoders将字节解码为合理的输出。

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

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