我一直在寻找一种方法来确定文件是否是连接,但没有找到任何令人满意的答案。
我尝试的第一件事是:
Files.isSymbolicLink(aPath)
它仅检测符号链接,而不检测 Windows 中称为连接的文件。
还尝试了此处提出的解决方案(使用 JNA 库): Stackoverflow 问题 (3249117) ,但在我知道是连接的任何文件上它从未返回 true。
我发现确定哪些文件是连接的唯一方法是在 Windows 命令提示符中运行以下命令:
DIR /S /A:L
在我的计算机上它返回 66 个文件夹,而 Files.isSymbolicLink(aPath) 仅返回 2 个。 所以我想我可以找到一种方法来利用它,但我认为在遍历文件树时它不会非常有效。
有没有办法使用标准 java 库或 JNA 来做到这一点?
如果您可以在 JNA 中编写本机代码,则可以直接调用 Win32 API
GetFileAttributes()
函数并检查 FILE_ATTRIBUTE_REPARSE_POINT
标志(连接点被实现为重解析点)。
更新:为了区分不同类型的重解析点,您必须检索实际重解析点的
ReparseTag
。 对于连接点,它将设置为 IO_REPARSE_TAG_MOUNT_POINT
(0xA0000003)。
有两种方法可以检索
ReparseTag
:
DeviceIoControl()
和 FSCTL_GET_REPARSE_POINT
控制代码来获取 REPARSE_DATA_BUFFER
结构体,作为 ReparseTag
字段。 您可以在以下文章中查看使用此技术的 IsDirectoryJunction()
实现示例:FindFirstFile()
获取 WIN32_FIND_DATA
结构体。如果路径具有 FILE_ATTRIBUTE_REPARSE_POINT
属性,则 dwReserved0
字段将包含 ReparseTag
。如果你有正确的java,例如Oracle jdk 8,可以有一种方法可以在没有JNA的情况下做到这一点。它很狡猾,它可能会停止工作,但是....
可以获取BasicFileAttributes接口相关链接:
BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
这个接口实现可能是一个类
sun.nio.fs.WindowsFileAttributes
。这个类有一个方法 isReparsePoint
,它对于连接点和符号链接都返回 true。所以你可以尝试使用反射并调用该方法:
boolean isReparsePoint = false;
if (DosFileAttributes.class.isInstance(attr))
try {
Method m = attr.getClass().getDeclaredMethod("isReparsePoint");
m.setAccessible(true);
isReparsePoint = (boolean) m.invoke(attr);
} catch (Exception e) {
// just gave it a try
}
现在只能判断是否真的是符号链接:
Files.isSymbolicLink(path)
如果不是,但它是重解析点,那么那就是连接点。
在 Windows 上,连接点的属性有
isSymbolicLink()
== false
,它们有 isOther()
== true
。所以你可以这样做:
boolean isWindows = System.getProperty("os.name").toLowerCase().contains("windows")
BasicFileAttributes attrs = Files.readAttributes(aPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
boolean isJunction = isWindows && attrs.isDirectory() && attrs.isOther();
在 J2SE 1.7 中使用 Java NIO
/**
* returns true if the Path is a Windows Junction
*/
private static boolean isJunction(Path p) {
boolean isJunction = false;
try {
isJunction = (p.compareTo(p.toRealPath()) != 0);
} catch (IOException e) {
e.printStackTrace(); // TODO: handleMeProperly
}
return isJunction;
}
黑盒解决方案:
aPath.toRealPath()
解析连接点和符号链接,因此结果会偏离 aPath
。
此外,由于未记录的原因,
BasicFileAttributes.isSymbolicLink()
还为路口提供 false
:
例如
Path.toRealPath(LinkOption.NOFOLLOW_LINKS)
很好地将连接点视为链接,但无法解析它!!
因此,通过
toRealPath()
和 BasicFileAttributes.isSymbolicLink()
的非同一性,您可以识别出一个交汇点。
您可以使用 PowerShell 命令发现链接类型
(获取项目-路径文件名-Force).LinkType
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
class WindowsFileLinkUtils {
public enum WindowsLinkType {
JUNCTION("Junction"),
HARD_LINK("HardLink"),
SYMBOLIC_LINK("SymbolicLink");
private final String key;
WindowsLinkType(String key) {
this.key = key;
}
public String getKey() {
return key;
}
}
private static final String CREATE_JUNCTION_COMMAND = "(Get-Item -Path %s -Force).LinkType";
public static Optional<WindowsLinkType> getLinkType(Path path) throws IOException, InterruptedException {
ProcessBuilder processBuilder = createIsJunctionProcessBuilder(path);
Process process = processBuilder.start();
process.waitFor();
try (BufferedReader inStreamReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String output = inStreamReader.readLine();
return Arrays.stream(WindowsLinkType.values()).filter(windowsLinkType -> windowsLinkType.getKey().equals(output)).findFirst();
}
}
private static ProcessBuilder createIsJunctionProcessBuilder(Path target) {
ProcessBuilder processBuilder = new ProcessBuilder();
List<String> arguments = processBuilder.command();
arguments.add("powershell.exe");
arguments.add(String.format(CREATE_JUNCTION_COMMAND, target.toString()));
return processBuilder;
}
private WindowsFileLinkUtils() {
}
}