使用Java NIO直接访问Windows磁盘

使用Java NIO直接访问Windows磁盘,java,nio,disk,drive,unc,Java,Nio,Disk,Drive,Unc,我正在使用一个使用JavaNIO的库来直接将文件映射到内存,但是直接读取磁盘时遇到了问题 我可以通过UNC使用FileInputStream直接读取磁盘,例如 File disk = new File("\\\\.\\PhysicalDrive0\\"); try (FileInputStream fis = new FileInputStream(disk); BufferedInputStream bis = new BufferedInputStream(fis)) { b

我正在使用一个使用JavaNIO的库来直接将文件映射到内存,但是直接读取磁盘时遇到了问题

我可以通过UNC使用
FileInputStream
直接读取磁盘,例如

File disk = new File("\\\\.\\PhysicalDrive0\\");
try (FileInputStream fis = new FileInputStream(disk);
    BufferedInputStream bis = new BufferedInputStream(fis)) {
    byte[] somebytes = new byte[10];
    bis.read(somebytes);
} catch (Exception ex) {
    System.out.println("Oh bother");
}
但是,我不能将此扩展到NIO:

File disk = new File("\\\\.\\PhysicalDrive0\\");
Path path = disk.toPath();
try (FileChannel fc = FileChannel.open(path, StandardOpenOption.READ)){
    System.out.println("No exceptions! Yay!");
} catch (Exception ex) {
    System.out.println("Oh bother");
}
stacktrace(直到原因)是:

我还没有找到一个解决办法,虽然我看到了一些接近。Daniel Alder建议使用GLOBALROOT的答案似乎是相关的,因为答案中使用了FileChannel,但我似乎找不到使用这种模式的驱动器。有没有办法将所有设备列在GLOBALROOT或类似的目录下

目前,我正在考虑用直接
InputStream
s替换NIO的使用,但如果可以的话,我想避免这种情况。首先,使用NIO是有原因的,其次,它需要运行大量的代码,需要做大量的工作。最后,我想知道如何实现类似Daniel的解决方案的东西,以便将来可以编写设备或使用NIO

总之:我如何使用Java NIO(而不是
InputStream
s)直接访问驱动器,和/或是否有办法列出通过GLOBALROOT访问的所有设备,以便使用Daniel Alser的解决方案

答案摘要: 我保留了过去的编辑(如下),以避免混淆。在EJP和Apangin的帮助下,我想我有了一个可行的解决方案。差不多

private void rafMethod(long posn) {
    ByteBuffer buffer = ByteBuffer.allocate(512);
    buffer.rewind();
    try (RandomAccessFile raf = new RandomAccessFile(disk.getPath(), "r");
            SeekableByteChannel sbc = raf.getChannel()) {
        sbc.read(buffer);
    } catch (Exception ex) {
        System.out.println("Oh bother: " + ex);
        ex.printStackTrace();
    }

    return buffer;
}
只要posn参数是扇区大小的倍数(在本例中设置为512),这将起作用。请注意,这也适用于Channels.newChannel(FileInputStream),在这种情况下,它似乎总是返回可查找的字节流,并且将其强制转换为一个字节流似乎是安全的

从快速和肮脏的测试看来,这些方法确实在寻找,而不仅仅是跳过。我在我的驱动器开始时搜索了一千个位置,它读取了它们。我也这么做了,但添加了磁盘大小一半的偏移量(用于搜索磁盘的背面)。我发现:

  • 两种方法花费的时间几乎相同
  • 搜索磁盘的开始或结束不影响时间
  • 缩小地址范围确实缩短了时间
  • 对地址进行排序确实减少了时间,但没有减少多少
这对我来说意味着,这是真正的寻找,而不仅仅是阅读和跳跃(就像一条小溪倾向于这样)。在这个阶段,速度仍然很糟糕,它让我的硬盘听起来像一台洗衣机,但代码是为快速测试而设计的,还没有变得漂亮。它可能仍然可以正常工作

感谢EJP和Apangin的帮助。阅读他们各自答案中的更多内容

编辑: 后来我在Windows7机器上运行了我的代码(我最初没有),我得到了一个稍微不同的异常(见下文)。这是以管理员权限运行的,第一段代码仍然在相同的条件下工作

java.nio.file.FileSystemException: \\.\PhysicalDrive0\: A device attached to the system is not functioning.

    at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:86)
    at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97)
    at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102)
    at sun.nio.fs.WindowsFileSystemProvider.newFileChannel(WindowsFileSystemProvider.java:115)
    at java.nio.channels.FileChannel.open(FileChannel.java:287)
    at java.nio.channels.FileChannel.open(FileChannel.java:335)
    at testapp.TestApp.doStuff(TestApp.java:30)
    at testapp.TestApp.main(TestApp.java:24)
编辑2: 为了响应EJP,我尝试了:

    byte[] bytes = new byte[20];
    ByteBuffer bb = ByteBuffer.wrap(bytes);
    bb.rewind();

    File disk = new File("\\\\.\\PhysicalDrive0\\");
    try (FileInputStream fis = new FileInputStream(disk);
            ReadableByteChannel rbc = Channels.newChannel(new FileInputStream(disk))) {

        System.out.println("Channel created");
        int read = rbc.read(bb);
        System.out.println("Read " + read + " bytes");
        System.out.println("No exceptions! Yay!");
    } catch (Exception ex) {
        System.out.println("Oh bother: " + ex);
    }
尝试此操作时,我得到以下输出:

已创建频道
噢,麻烦了:java.io.IOException:参数不正确


看来我可以创建FileChannel或ReadableByteChannel,但我不能使用它;也就是说,错误只是延迟了。

以管理员身份运行此操作。它确实有效,因为它只是
java.io:

    try (FileInputStream fis = new FileInputStream(disk);
        ReadableByteChannel fc = Channels.newChannel(fis))
    {
        System.out.println("No exceptions! Yay!");
        ByteBuffer  bb = ByteBuffer.allocate(4096);
        int count = fc.read(bb);
        System.out.println("read count="+count);
    }
    catch (Exception ex)
    {
        System.out.println("Oh bother: "+ex);
        ex.printStackTrace();
    }

编辑如果您需要随机访问,则必须使用
随机访问文件
。没有通过
频道
从中映射。但是上面的解决方案无论如何都不是NIO,只是
FileInput/OutputStream

上的Java NIO层,使用NIO您的原始代码只需要稍作更改

Path disk = Paths.get("d:\\.");
try (ByteChannel bc = Files.newByteChannel(disk, StandardOpenOption.READ)) {
    ByteBuffer buffer = ByteBuffer.allocate(10);
    bc.read(buffer);
} catch (Exception e){
    e.printStackTrace();
}

是很好的、可行的代码,但我在您的版本和我的版本中都遇到了拒绝访问错误。

当访问物理驱动器而不进行缓冲时,您只能读取完整的扇区。这意味着,如果扇区大小为512字节,则只能读取512字节的倍数。将缓冲区长度更改为512或4096(无论扇区大小如何),并且
FileChannel
可以正常工作:

ByteBuffer buf = ByteBuffer.allocate(512);

try (RandomAccessFile raf = new RandomAccessFile("\\\\.\\PhysicalDrive0", "r");
     FileChannel fc = raf.getChannel()) {
    fc.read(buf);
    System.out.println("It worked! Read bytes: " + buf.position());
} catch (Exception e) {
    e.printStackTrace();
}


您原来的
FileInputStream
代码显然可以正常工作,因为
BufferedInputStream
的默认缓冲区大小为8192。把它拿走——代码也会失败,出现同样的异常。

谢谢EJP,已经这么做了。当我第一次问我的问题时,我正在使用XP(没有Windows7)。当我在Windows7中运行它时,我得到的正是你提到的错误。然后我以管理员的身份运行,出现了一个新错误:“连接到系统的设备无法正常工作。”(请参阅我文章的底部)。当文件流工作时,设备显然正在运行,它也是我的操作系统磁盘。顺便说一句,同样的事情发生在磁盘1、2和3上。好的,我将尝试剪切和粘贴。啊。的确,您可以通过这种方式获得FileChannel,但它什么也做不了。它只是延迟异常,直到您尝试读取它为止。我添加了一个ByteBuffer并尝试读取它,得到了:java.io.IOException:该参数不正确有趣的是,这不同于“连接的设备…”@EJP“没有通过通道从该设备进行映射”-关于
RandomAccessFile.getChannel()
?@apangin,这不是通过
通道进行映射。请看我写的东西
getChannel()
将遇到与OP已经抱怨的问题完全相同的问题。感谢您的回答。我认为问题不在于引用(\\.\PhysicalDrive0),尽管我会尝试任何方法。我和你的代码有同样的错误。使用FileInputStream的第一段代码确实可以使用该引用,但不幸的是,FileInputStream不能满足我的需要。如果这是参考资料,我想这是行不通的。另外,当我使用无效引用时,我会遇到另一个错误。您没有尝试我的实际代码。尝试使用4096的缓冲区大小,就像我做的那样。你可能需要阅读前文本
ByteBuffer buf = ByteBuffer.allocate(512);

try (RandomAccessFile raf = new RandomAccessFile("\\\\.\\PhysicalDrive0", "r");
     FileChannel fc = raf.getChannel()) {
    fc.read(buf);
    System.out.println("It worked! Read bytes: " + buf.position());
} catch (Exception e) {
    e.printStackTrace();
}