Runtime.exec()卡住因子进程stdout/stderr未读导致缓冲区阻塞,须用BufferedReader在独立线程中持续读取;Windows路径含空格需传String[]数组;编译运行需按package重建目录结构并用全限定名;ProcessBuilder无沙箱能力,需结合ulimit、临时目录等系统级约束;输出比对前须trim()和归一化空白及换行符。

Java里用Runtime.exec()跑用户代码,为什么总卡住或读不到输出?
因为Runtime.exec()启动的子进程默认不自动消费stdout和stderr流,缓冲区一满就阻塞——你等它结束,它在等你读流。
- 必须显式调用
process.getInputStream()和process.getErrorStream()并持续读取,推荐用BufferedReader逐行读,别用readAllBytes()(JDK 9+才支持,且不防超长输出) - 别只等
process.waitFor(),要在等待前/中启动独立线程读流,否则死锁高发 - 注意:Windows下
javac和java命令路径含空格(如"C:Program Files..."),必须传String[]数组,不能拼接字符串,否则解析失败
编译Java代码时,如何避免ClassNotFoundException或NoClassDefFoundError?
用户提交的是单个.java文件,但可能引用自定义类、静态内部类,或用了package声明——编译和运行时的类路径稍有偏差就会崩。
- 编译时用
javac -d <out_dir> <source_file>,确保生成的.class按包结构落在out_dir下 - 运行时用
java -cp <out_dir> <main_class_name>,注意main_class_name是全限定名(含package),不是文件名 - 如果用户代码有
package p1.p2;,源文件必须放在p1/p2/子目录下,否则javac报错;更稳妥的做法是:解压到临时目录后,按package声明重建路径再编译
用ProcessBuilder做沙箱隔离,哪些参数真能起作用?
ProcessBuilder本身不提供沙箱,它只是进程启动器。所谓“隔离”,靠的是操作系统级约束 + 启动参数组合,不是加个inheritIO()就完事。
- 禁用继承标准流:
pb.inheritIO()必须删掉,否则用户程序能直接打印到你的服务器控制台 - 限制资源:Linux下必须配合
ulimit(通过bash -c "ulimit -t 1; ulimit -v 65536; exec ..."包装命令),Windows无等效机制,建议只在Linux部署 - 工作目录必须设为干净临时目录:
pb.directory(tempDir),防止用户代码读写宿主文件系统 - 别信
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT)——它和inheritIO()一样危险,输出会混进日志
比对输出时,为什么assertEquals(expected, actual)总报错,但肉眼看一模一样?
用户程序输出末尾可能带
(Windows)、
(Linux/macOS),或有多余空格、空行;而你从InputStream读出来的字节流没做归一化。
立即学习“Java免费学习笔记(深入)”;
- 统一用
String.trim().replaceAll("\s+", " ")处理两端空白和中间多空格(适合多数OJ题型) - 严格比对行序时,用
Lines拆分后逐行trim()再比较,别用split(" ")(忽略兼容性) - 注意编码:
new String(bytes, StandardCharsets.UTF_8)显式指定,别依赖平台默认编码,否则中文乱码导致比对失败
真正难的不是跑起来,是让每次执行都可预测:临时目录权限、JVM启动参数、ulimit生效范围、Windows/Linux换行符处理——这些细节漏一个,评测结果就不可重现。










