由于标准 Java API 似乎确实缺少这一点,因此我看了一下自己使用JNA进行的操作。这是我第一次接触 JNA。它不像我想要的那么漂亮,而且 JNA 的代码似乎严重缺乏泛型,但它比使用 JNI 并试图为所需的不同平台设置糟糕的交叉编译器(最低 x86 和 x64即使您只针对一个操作系统)。最初是烦人的编译过程驱使我从 C++ 转向 Java,我希望永远不必再回到它。
无论如何,这似乎有效。希望它对其他人也有用。它提供了四种公共方法:
isAvailable()
-- 调用其他方法是否应该工作(即,我们在 Windows 上并且 JNA 本地库加载正常)
isCompressed(File)
setCompressed(File, boolean)
volumeSupportsFileCompression(File)
-- 询问 Windows 文件所在的分区是否支持 [individual] 文件压缩。例如,在 NTFS 上为真,在 FAT(U 盘等)上为假。
Windows API 中的压缩是通过专用的 I/O 控制操作完成的,而不仅仅是“SetAttributes”调用。如果它更简单(与其他文件属性同构),为了完整起见,我也会将加密属性放在那里,但无论如何。
import java.io.File;
import java.io.IOException;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.ShortByReference;
import com.sun.jna.win32.StdCallLibrary;
import com.sun.jna.win32.W32APIOptions;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.WinNT.HANDLE;
public class WindowsFileOps {
private WindowsFileOps() {}
private static interface Kernel32Extra extends StdCallLibrary {
int COMPRESSION_FORMAT_NONE = 0x00000000;
int COMPRESSION_FORMAT_DEFAULT = 0x00000001;
int FSCTL_SET_COMPRESSION = 0x0009C040;
Kernel32Extra INSTANCE = (Kernel32Extra)Native.loadLibrary("kernel32",
Kernel32Extra.class, W32APIOptions.UNICODE_OPTIONS);
boolean GetVolumeInformation(
String lpRootPathName,
Pointer lpVolumeNameBuffer,
int nVolumeNameSize,
IntByReference lpVolumeSerialNumber,
IntByReference lpMaximumComponentLength,
IntByReference lpFileSystemFlags,
Pointer lpFileSystemNameBuffer,
int nFileSystemNameSize
);
}
private static Boolean isAvailable;
public static boolean isAvailable() {
if (isAvailable == null) {
try {
isAvailable = Kernel32.INSTANCE != null && Kernel32Extra.INSTANCE != null;
} catch (Throwable t) {
isAvailable = false;
}
}
return isAvailable;
}
private static String pathString(File file) {
// "\\?\" is a Windows API thing that enables paths longer than 260 chars
return "\\\\?\\" + file.getAbsolutePath();
}
private static int getAttributes(File file) throws IOException {
int attrib = Kernel32.INSTANCE.GetFileAttributes(pathString(file));
if (attrib == Kernel32.INVALID_FILE_ATTRIBUTES) {
throw new IOException("Unable to read file attributes of " + file);
}
return attrib;
}
public static boolean isCompressed(File file) throws IOException {
return (getAttributes(file) & Kernel32.FILE_ATTRIBUTE_COMPRESSED) != 0;
}
public static void setCompressed(File file, boolean compressed) throws IOException {
HANDLE hFile = Kernel32.INSTANCE.CreateFile(
pathString(file),
Kernel32.GENERIC_READ | Kernel32.GENERIC_WRITE,
Kernel32.FILE_SHARE_READ,
null,
Kernel32.OPEN_EXISTING,
0,
null);
try {
if (!Kernel32.INSTANCE.DeviceIoControl(
hFile,
Kernel32Extra.FSCTL_SET_COMPRESSION,
new ShortByReference((short)(
compressed
? Kernel32Extra.COMPRESSION_FORMAT_DEFAULT
: Kernel32Extra.COMPRESSION_FORMAT_NONE
)).getPointer(),
2,
null, 0,
new IntByReference(),
null
)) throw new IOException("Unable to alter compression attribute of " + file);
} finally {
Kernel32.INSTANCE.CloseHandle(hFile);
}
}
public static boolean volumeSupportsFileCompression(File file) throws IOException {
IntByReference flags = new IntByReference();
if (!Kernel32Extra.INSTANCE.GetVolumeInformation(
pathString(file.getAbsoluteFile().toPath().getRoot().toFile()),
null, 0,
null,
null,
flags,
null, 0
)) throw new IOException("GetVolumeInformation failure");
return (flags.getValue() & Kernel32.FILE_FILE_COMPRESSION) != 0;
}
}