准确判断操作系统应使用toLowerCase()后startsWith()匹配:isWindows=osName.startsWith("windows"),isMac=osName.startsWith("mac os x")||startsWith("darwin"),isLinux=osName.startsWith("linux")。

Java 本身跨平台,但 System.getProperty("os.name") 返回的值和实际系统行为差异,才是环境适配真正的坑点。
怎么准确判断 Windows / macOS / Linux
别信 os.name 的字面意思——它返回的是 "Windows 10"、"Mac OS X"(注意不是 "macOS")、"Linux",甚至旧版 JVM 可能返回 "SunOS"。直接用 startsWith() 更可靠:
String osName = System.getProperty("os.name").toLowerCase();
boolean isWindows = osName.startsWith("windows");
boolean isMac = osName.startsWith("mac os x") || osName.startsWith("darwin");
boolean isLinux = osName.startsWith("linux");
其中 "darwin" 是 macOS 内核名,某些精简 JVM(如 Alpine 上的 OpenJDK)可能返回这个而非 "Mac OS X"。
- 永远用
toLowerCase()统一处理,避免大小写干扰 - 不要用
equals("Windows")这类硬匹配 -
os.arch(如"amd64"、"aarch64")可配合判断是否 ARM 架构,影响本地库加载
路径分隔符与文件系统行为差异
File.separator 和 Paths.get() 能解决斜杠问题,但更隐蔽的是大小写敏感性与特殊字符限制:
立即学习“Java免费学习笔记(深入)”;
- Windows 文件系统默认不区分大小写;Linux/macOS 区分——
new File("A.txt")和new File("a.txt")在 Windows 可能指向同一文件,在 Linux 则是两个 - Windows 禁止文件名含
: " / \ | ? *;macOS 对:有特殊处理(显示为空格,实际存储仍为:) - 推荐统一用
Paths.get("a", "b", "c")构造路径,它自动适配分隔符,且比字符串拼接 +File.separator更安全
本地命令执行时的 shell 差异
用 Runtime.getRuntime().exec() 或 ProcessBuilder 调外部命令时,Windows 和 Unix-like 系统的 shell 解析逻辑完全不同:
- Windows 默认走
cmd.exe /c,不支持ls -l这类命令;Linux/macOS 默认走/bin/sh,不支持dir - 带管道或重定向(如
"ps aux | grep java")在 Windows 下会失败,必须显式调用bash -c或cmd.exe /c - 更稳妥的做法:拆成独立命令,用 Java 自己处理输入输出,或引入
commons-exec库做跨平台封装
例如启动脚本:
Listcmd = new ArrayList<>(); if (isWindows) { cmd.addAll(Arrays.asList("cmd.exe", "/c", "myapp.bat")); } else { cmd.addAll(Arrays.asList("/bin/sh", "-c", "./myapp.sh")); } ProcessBuilder pb = new ProcessBuilder(cmd);
临时目录与用户主目录的陷阱
System.getProperty("java.io.tmpdir") 和 System.getProperty("user.home") 看似安全,但要注意:
- Docker 容器中
tmpdir可能是/tmp(内存挂载),写大文件会 OOM;建议用Files.createTempDirectory("myapp-")并设好清理钩子 - macOS 的
user.home是/Users/xxx,但某些企业部署下可能被策略重定向到网络路径,IO 延迟高 - Windows 用户目录若含中文或空格(如
C:\Users\张三\AppData\...),部分老代码用Runtime.exec(String)会因未加引号而解析失败
真正要落地的配置路径,优先用 System.getProperty("user.home") + "/.myapp/config.json",而不是硬编码 "./config.json"。
最麻烦的从来不是“能不能跑”,而是“跑得对不对”——比如时间格式化用 SimpleDateFormat 依赖默认时区,而 Docker 容器常没配时区,结果线上日志全是 UTC 时间,排查时才发现。










