前言

Java虽然五脏俱全但总有软肋,譬如获取CPU等硬件信息,当然我们可以通过JNI调用C/C++来获取,但对于对C/C++和Windows API不熟的码农是一系列复杂的学习和踩坑过程。那能不能通过简单一些、学习成本低一些的方式呢?答案是肯定的,在功能实现放在首位的情况下,借他山之石是最简洁有力的做法。

认识java.lang.Runtime#exec方法

作用:用于调用外部程序,并重定向外部程序的标准输入、标准输出和标准错误到缓冲池。功能就是和windows的“运行”一样。

重载方法说明

Runtime#exec(String command);
Runtime#exec(String command, String[] envp);
Runtime#exec(String command, String[] envp, File workdir);
Runtime#exec(String[] cmdArray);
Runtime#exec(String[] cmdArray, String[] envp);
Runtime#exec(String[] cmdArray, String[] envp, File workdir);
  1. String[] envp 作为调用命令前设置的会话级环境变量。

    1.1. 变量作用域:命令运行结束后,通过该参数设置的环境变量将失效;

    1.2. 设置方式:variableName=variableValue,如Process proc = r.exec("cmd /c dir > %dest%", new String[]{"dest=c:\\dir.txt"});
  2. File workdir 用于设置当前工作目录,譬如我们需要执行位于D:\tools下的echo.exe程序,那么可以这样调用Process proc = r.exec("echo.exec", null, new File("D:\\tools"));
  3. String command 即为需要调用的外部程序,以及命令行参数等。Windows下调用系统命令,像dir等命令是由cmd解析器解释执行的,因此若直接写"dir"则会被认为在当前工作目录下有一个"dir.exe"文件,那么当然会执行失败;在Linux下调用ls等是同样道理,因此请按如下方式调用cmd和shell命令:

    3.1. 调用CMD命令的方式为Process proc = r.exec(String.format("cmd /c %s", "cmd命令,如dir、type等")),若要启动一个新的Console执行命令,只需要将dir改写为start dir即可;

    3.2. 调用Shell命令的方式为Process proc = r.exec(String.format("/bin/sh -c %s", "shell命令,如ls、cat等")),若要启动一个新的Terminal执行命令,只需要将ls改写为xterm -e ls即可;
  4. String[] cmdArray 功能和String command一样,但命令行的每个部分将作被独立分隔出来作为数组中的元素。如cmd /c dir必须分割为new String[]{"cmd", "/c", "dir"},而不能分割为new String[]{"cmd /c", "dir"}

输入重定向

废话少说,直接看代码!

try {
String cmd = "cmd /c start cmd.exe";
Process child = Runtime.getRuntime().exec(cmd);
OutputStream output = child.getOutputStream();
output.write("cd C:/ /r/n".getBytes());
output.flush();
output.write("dir /r/n".getBytes());
output.close();
}
catch (IOException e) {}

输出重定向

注意:不支持直接使用>或>>执行标准输出重定向。

String cmd = "/path/to/getipconfig.bat"; // 自己写的bat脚本文件,里面包含ipconfig /all命令。
Process p; try {
p = Runtime.getRunTime().exec(cmd);
InputStream input = p.getInputStream();
InputStreamReader streamReader = new InputStreamReader(input);
BufferedReader bufReader = new BufferedReader(streamReader);
String line = null; while ((line = bufReader.readLine()) != null) {
System.out.println(line);
}
}
catch (IOException e) {}

为什么不能使用>>>实现输出重定向呢?

通过Process实例.getInputStream()Process实例.getErrorStream()获取的输入流和错误信息流是缓冲池是当前Java程序提供的,而不是直接获取外部程序的标准输出流和标准错误流。

即通过Runtime#exec调用外部程序时,外部程序的标准输出流和标准错误流已经被Java程序接管。那么在命令中企图通过>>>实现输出重定向显然已无效果。



另外,缓冲池的容量是一定的,因此若外部程序在运行过程中不断向缓冲池输出内容,当缓冲池填满,那么外部程序将暂停运行直到缓冲池有空位可接收外部程序的输出内容为止。(采用xcopy命令复制大量文件时将会出现该问题)

解决办法就是当前的Java程序不断读取缓冲池的内容,从而为腾出缓冲池的空间。如:

Runtime r = Runtime.getRuntime();
try{
Process proc = r.exec("cmd /c dir"); // 假设该操作为造成大量内容输出
// 采用字符流读取缓冲池内容,腾出空间
BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream(), "gbk")));
String line = null;
while ((line = reader.readLine()) != null){
System.out.println(line);
} // 或采用字节流读取缓冲池内容,腾出空间
// ByteArrayOutputStream pool = new ByteArrayOutputStream();
// byte[] buffer = new byte[1024];
// int count = -1;
// while ((count = proc.getInputStream().read(buffer)) != -1){
// pool.write(buffer, 0, count);
// buffer = new byte[1024];
// }
// System.out.println(pool.toString("gbk")); int exitVal = proc.waitFor();
System.out.println(exitVal == 0 ? "成功" : "失败");
}
catch(Exception e){
e.printStackTrace();
}

注意:外部程序在执行结束后将会自动关闭,否则不管是字符流还是字节流均由于既读不到数据,又读不到流结束符而出现阻塞Java进程运行的情况。

简化输入输出重定向的java.lang.ProcessBuilder

功能和java.lang.runtime#exec一样,只是java.lang.ProcessBuilder仅接收命令行以数组形式传递给java.lang.ProcessBuilder#command()而已。

基本使用

ProcessBuilder pb = new ProcessBuilder();
// pb.command("cmd /c dir"); 这样会报错
// 或者写到new ProcessBuilder("cmd", "/c", "dir")
pb.command("cmd", "/c", "dir");
try {
Process process = pb.start();
// 后面都是操作Process实例的方法
}
catch (IOException e){}

重定向

public ProcessBuilder redirectInput(File file)  // 输入重定向
public ProcessBuilder redirectOutput(File file) // 输出重定向
public ProcessBuilder redirectError(File file) // 异常重定向

示例

try {
ProcessBuilder pb = new ProcessBuilder("cmd");
// 在标准输入中,通过换行符分隔多行命令。
// commands.txt的内容为
// javac Demo.java
// java Demo
File commands = new File("/path/to/commands.txt");
File error = new File("/path/to/error");
File output = new File("/path/to/output"); pd.redirectInput(commands);
pd.redirectOutput(output);
pd.redirectError(error); pd.start();
}
catch(Exception e) {
e.printStackTrace();
}

java.lang.ProcessAPI说明

// 以非阻塞方式获取子进程执行的返回值(习惯0表示正常结束)。若子进程尚未完成时调用该方法,则会报异常`java.lang.IllegalThreadStateException`
int exitValue()
// 以阻塞方式获取子进程执行的返回值。若进程尚未完成则会等待子进程完成后才恢复当前线程。
// 问题:若子进程无法正常关闭,则会导致Java线程一直挂起;
// 返回值为子进程的退出码
int waitFor()。
// 如果超时前子进程结束,那么返回`true` ,否则返回`false`
boolean waitFor(long timeout, TimeUnit unit)
// 强行终止子进程,但调用后子进程不会马上被终止,所以立即调`boolean isAlive()`方法可能会返回`true`,因此需配合`waitFor`使用。
void destory()
// 默认实现为调用`void destory()`方法。从JDK1.8开始提供。
Process destoryForcibly()
// 如果子进程还没结束则返回`true` 。从JDK1.8开始提供。
boolean isAlive()
// 获取子进程的异常输出流,如果子进程以`ProcessBuilder`创建,且通过`ProcessBuilder.redirectError`设置重定向,那么该方法返回`null`
InputStream getErrorStream()
// 获取子进程的标准输出流,如果子进程以`ProcessBuilder`创建,且通过`ProcessBuilder.redirectOutput`设置重定向,那么该方法返回`null`
InputStream getInputStream()
// 获取子进程的标准输入流,如果子进程以`ProcessBuilder`创建,且通过`ProcessBuilder.redirectInput`设置重定向,那么该方法返回`null`
OutputStream getOutputStream()

总结

尊重原创,转载请注明来自:https://www.cnblogs.com/fsjohnhuang/p/14396218.html _肥仔John

Java魔法堂:调用外部程序的更多相关文章

  1. Java魔法堂:类加载器入了个门

    一.前言 <Java魔法堂:类加载机制入了个门>中提及整个类加载流程中只有加载阶段作为码农的我们可以入手干预,其余均由JVM处理.本文将记录加载阶段的核心组件——类加载器的相关信息,以便日 ...

  2. 【转】Java魔法堂:String.format详解

    Java魔法堂:String.format详解     目录     一.前言    二.重载方法     三.占位符     四.对字符.字符串进行格式化     五.对整数进行格式化     六. ...

  3. Java魔法堂:打包知识点之jar

    一.前言    通过eclipse导出jar包十分方便快捷,但作为码农岂能满足GUI的便捷呢?所以一起来CLI吧! 二.JAR包 JAR包是基于ZIP文件格式,用于将多个.java文件和各种资源文件, ...

  4. Java魔法堂:找外援的利器——Runtime.exec详解

    一.前言 Java虽然五脏俱全但总有软肋,譬如获取CPU等硬件信息,当然我们可以通过JNI调用C/C++来获取,但对于对C/C++和Windows API不熟的码农是一系列复杂的学习和踩坑过程.那能不 ...

  5. Java借助Runtime调用外部程序阻塞的代码

    有时候在java代码中会调用一些外部程序,比如SwfTools来转换swf.ffmpeg来转换视频等.如果你的代码这样写:Runtime.getRuntime().exec(command),会发现程 ...

  6. Java魔法堂:枚举类型详解

    一.前言 Java的枚举类型相对C#来说具有更灵活可配置性,Java的枚举类型可以携带更多的信息. // C# enum MyColor{ RED = , BLUE = } Console.Write ...

  7. Java魔法堂:解读基于Type Erasure的泛型

    一.前言 还记得JDK1.4时遍历列表的辛酸吗?我可是记忆犹新啊,那时因项目需求我从C#转身到Java的怀抱,然后因JDK1.4少了泛型这样语法糖(还有自动装箱.拆箱),让我受尽苦头啊,不过也反映自己 ...

  8. Java魔法堂:类加载机制入了个门

    一.前言 当在CMD/SHELL中输入 $ java Main<CR><LF> 后,Main程序就开始运行了,但在运行之前总得先把Main.class及其所依赖的类加载到JVM ...

  9. Java魔法堂:URI、URL(含URL Protocol Handler)和URN

    一.前言 过去一直搞不清什么是URI什么是URL,现在是时候好好弄清楚它们了!本文作为学习笔记,以便日后查询,若有纰漏请大家指正! 二.从URI说起    1. 概念 URI(Uniform Reso ...

随机推荐

  1. java中如何踢人下线?封禁某个账号后使其会话立即掉线!

    需求场景 封禁账号是一个比较常见的业务需求,尤其是在论坛.社区类型的项目中,当出现了违规用户时我们需要将其账号立即封禁. 常规的设计思路是:在设计用户表时增加一个状态字段,例如:status,其值为1 ...

  2. linux查看文件夹和磁盘内存及服务器对应的ip

    多进程统计cpu 数目 n_cpu = multiprocessing.cpu_count() print(n_cpu) 查看文件夹占用磁盘空间 du -h --max-depth=1 /path 查 ...

  3. C++ STL 优先队列 (priority_queue)

    std::priority_queue <queue> 优先队列   优先队列是一种容器适配器,根据某些严格的弱排序标准,使其第一个元素始终包含的最大元素.   这种特性类似于堆,它可以在 ...

  4. webapi Swagger 配置 services.BuildServiceProvider() 报警 ASP0000 问题处理

    问题起源 网上的常见配置 Swagger 配置 在Startup类的 ConfigureServices 使用 services.BuildServiceProvider() ,其中有段代码如下: v ...

  5. (04)-Python3之--字典(dict)操作

    1.定义 字典的关键字:dict 字典由多个键和其对应的值构成的 键-值 对组成,每个键值对用冒号 : 分割,每个键值对之间用逗号 , 分割,整个字典包括在花括号 {} 中. {key1:value1 ...

  6. (转载)微软数据挖掘算法:Microsoft 线性回归分析算法(11)

    前言 此篇为微软系列挖掘算法的最后一篇了,完整该篇之后,微软在商业智能这块提供的一系列挖掘算法我们就算总结完成了,在此系列中涵盖了微软在商业智能(BI)模块系统所能提供的所有挖掘算法,当然此框架完全可 ...

  7. libevent源码学习之event

    timer event libevent添加一个间隔1s持续触发的定时器如下: struct event_base *base = event_base_new(); struct event *ti ...

  8. List对象集合根据组合属性进行差集运算

    背景   当List是一个基本数据类型的集合的时候,进行集合运算还比较方便,但是有这么一些业务场景,比如某个用户权限变化的列表,或者取数据的变化结果,当时有时候用笨方法多循环两次也是可以的,只不过代码 ...

  9. 转载,Pandas 数据统计用法

    pandas模块为我们提供了非常多的描述性统计分析的指标函数,如总和.均值.最小值.最大值等,我们来具体看看这些函数: 1.随机生成三组数据import numpy as npimport panda ...

  10. Language Guide (proto3) | proto3 语言指南(四)枚举类型

    枚举类型 定义消息类型时,可能希望其中一个字段只包含预定义值列表中的一个.例如,假设您想为每个SearchRequest添加一个corpus(语料库)字段,其中语料库的值可以是UNIVERSAL.WE ...