Java调用Graphviz dot工具时,Runtime.exec传入文件名无输出但echo传递输入正常的问题排查
Java调用Graphviz dot工具时,Runtime.exec传入文件名无输出但echo传递输入正常的问题排查
这种情况我之前踩过坑,大概率是两个常见问题在搞鬼——要么是dot找不到你的目标文件,要么是你没处理进程的错误输出导致进程卡住了,咱们一步步来排查:
1. 先确认文件路径是否真的正确
当你用bash -c执行命令时,Java进程的当前工作目录可能和你预期的不一样(比如IDE的运行目录、打包后的执行目录),导致dot找不到FILE_NAME.dot。
你可以先打印Java的当前工作目录,看看文件是不是在这个路径下:
System.out.println("Java进程当前工作目录:" + System.getProperty("user.dir"));
如果文件不在这个目录里,要么把文件移过去,要么直接用绝对路径指定文件,比如:
Runtime.getRuntime().exec(new String[]{"bash", "-c", "dot -Tsvg /Users/xxx/Projects/FILE_NAME.dot"});
另外,也可以先在Java里检查文件是否存在,避免瞎猜:
File dotFile = new File("FILE_NAME.dot"); System.out.println("文件是否存在:" + dotFile.exists()); System.out.println("文件绝对路径:" + dotFile.getAbsolutePath());
2. 必须处理进程的错误输出流!
这是最容易被忽略的点:Runtime.exec启动的进程,标准输出和错误输出是两个独立的流。如果dot执行时出错(比如找不到文件),错误信息会写到错误流里,如果你没读取这个流,缓冲区满了之后进程就会卡住,根本不会输出任何内容。
你之前的代码只处理了标准输入流,完全没管错误流,所以哪怕dot报错了你也看不到。解决方法有两种:
方法一:用ProcessBuilder合并错误流到标准输出
ProcessBuilder比Runtime.exec更灵活,能直接把错误流合并到标准输出,这样你只需要读一个流就能同时拿到正常输出和错误信息:
ProcessBuilder pb = new ProcessBuilder("bash", "-c", "dot -Tsvg FILE_NAME.dot"); pb.redirectErrorStream(true); // 合并错误流到标准输出 Process process = pb.start(); // 接下来读取process.getInputStream(),就能看到所有输出(包括错误) InputStream inputStream = process.getInputStream(); BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); OutputStream outputStream = new FileOutputStream("out.txt"); byte[] buffer = new byte[8 * 1024]; int bytesRead; while ((bytesRead = bufferedInputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } outputStream.close(); bufferedInputStream.close();
方法二:单独开线程读取错误流
如果坚持用Runtime.exec,就得单独开个线程读取错误流,避免阻塞:
Process process = Runtime.getRuntime().exec(new String[]{"bash", "-c", "dot -Tsvg FILE_NAME.dot"}); // 开线程读错误流 new Thread(() -> { try (InputStream errStream = process.getErrorStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(errStream))) { String line; while ((line = reader.readLine()) != null) { System.err.println("dot错误输出:" + line); // 打印错误信息,方便排查 } } catch (IOException e) { e.printStackTrace(); } }).start(); // 正常读取标准输出 InputStream inputStream = process.getInputStream(); // ... 后面的写入代码和你原来的一样
3. 尽量直接调用dot,避免bash中转
其实没必要通过bash -c来执行dot,直接调用dot命令更简单,也能减少路径相关的问题:
Process process = Runtime.getRuntime().exec(new String[]{"dot", "-Tsvg", "FILE_NAME.dot"});
这样dot会直接接收文件名参数,避免bash解析命令时可能出现的问题。
按照上面的步骤排查,你应该能找到问题所在——大概率是路径不对,或者没处理错误流导致进程卡住了。
备注:内容来源于stack exchange,提问作者dg428
相关产品推荐
相关产品推荐

