前言

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. oracle分区表分区栏位NULL值测试

    实验在分区栏位为NULL时,分区表的反应 1.创建普通的分区表 CREATE TABLE MONKEY.TEST_PART_NULL_NORMAL ( ID NUMBER, ADD_DATE DATE ...

  2. JAVA编程中button按钮,actionlistener和mouseClicked区别

    在java的编程中,对于按钮button 有两个事件: 1.actionPerformed 2.mouseClicked 区别: actionPerformed:一般事件,仅侦听鼠标左键的单击事件,右 ...

  3. Django-http协议

    Http协议:超文本传输协议(应用层程序).它是客户端和服务端请求和应答的标准.Http的请求响应模型:1.客户端连接到web服务器一个http客户端,与web服务器的http端口(默认是80)建立了 ...

  4. [APUE] 进程环境

    APUE 一书的第七章学习笔记. 进程终止 有 8 种方式可以使得进程终止,5 种为正常方式: Return from main Calling exit() Calling _exit or _Ex ...

  5. Ubuntu对接GlusterFS

    存储节点部署示例环境,仅供参考 主机名 IP 系统 gfs01 10.10.10.13 Ubuntu 16.04.3 LTS gfs02 10.10.10.14 Ubuntu 16.04.3 LTS ...

  6. 前端工程构建之谈:gulp3要不要升级到Gulp4

    关于升级还是不升级,这是一个哲学问题. gulp4的语法更加现代,支持ES6的大部分写法,使用exports的方式去暴露任务组合,更加灵活和便捷. gulp4同时也提供了很多强大的API,例如para ...

  7. udp 连接

    在今天的内容里,我对 UDP 套接字调用 connect 方法进行了深入的分析.之所以对 UDP 使用 connect,绑定本地地址和端口,是为了让我们的程序可以快速获取异步错误信息的通知,同时也可以 ...

  8. 苹果 M1 芯片 OpenSSL 性能测试

    Apple M1(MacBook Air 2020) type 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes md2 0.00 0.00 0.00 ...

  9. 调度 GMP

    小结: 1. 当M从P的本地运行队列获取G时, 如果发现本地队列为空会尝试从其他P盗取一半的G过来,这个机制叫做Work Stealing, 2. Q M一定需要p吗? A 不一定.M正在执行原生代码 ...

  10. 在项目中如何自定义的Eslint配置

    一.设置js风格的缩进为4个空格 在你的前端项目中找到.eslintrc.js文件,如图 module.exports = { root: true, parserOptions: { parser: ...