【问题标题】:Accessing Windows disks directly with Java NIO使用 Java NIO 直接访问 Windows 磁盘
【发布时间】:2014-04-05 09:34:00
【问题描述】:

我正在使用一个使用 Java NIO 的库来直接将文件映射到内存,但是我无法直接读取磁盘。

可以直接使用FileInputStream和UNC读取磁盘,比如

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");
}

堆栈跟踪(直到原因)是:

java.nio.file.FileSystemException: \\.\PhysicalDrive0\: The parameter is incorrect.

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:334)
at hdreader.HDReader.testcode(HDReader.java:147)

虽然我在How to access specific raw data on disk from java 上看到了一些接近的东西,但我一直无法找到解决方案。 Daniel Alder 建议使用 GLOBALROOT 的答案似乎是相关的,因为答案在答案中使用了 FileChannel,但我似乎无法找到使用这种模式的驱动器。有没有办法列出 GLOBALROOT 下的所有设备或类似的东西?

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

总而言之:如何使用 Java NIO(而不是 InputStreams)直接访问驱动器,和/或有没有办法列出通过 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),在这种情况下,它似乎总是返回一个 SeekableByteStream,并且它似乎将其转换为一个是安全的。

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

  • 这两种方法花费的时间几乎相同。
  • 搜索磁盘的开头或结尾影响时间。
  • 缩小地址范围确实缩短了时间。
  • 对地址进行排序确实减少了时间,但幅度不大。

这向我表明,这是真正的寻找,而不仅仅是阅读和跳过(就像流所趋向的那样)。在这个阶段速度仍然很糟糕,它让我的硬盘听起来像一台洗衣机,但代码是为快速测试而设计的,还没有变得漂亮。它可能仍然可以正常工作。

感谢 EJP 和 Apangin 的帮助。阅读他们各自的答案。

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

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,但我不能使用它;也就是说,错误只是被延迟了。

【问题讨论】:

  • 你没有尝试我的实际代码。像我一样尝试使用 4096 的缓冲区大小。您可能必须读取确切的磁盘块倍数。
  • 我现在不在电脑附近,所以我会在星期一尝试。我正在使用 NO,以便我可以快速随机访问磁盘。也许我应该在问题中这么说。所以我需要能够寻找、抓取零件(一个块应该可以工作)并且最好使用 transfer from 和 transfer to。
  • 我准备打赌原始磁盘驱动程序不支持部分块读取。如果这是您的要求,您最好使用FileInput/OutputStreamsBufferedInput/OutputStreams
  • 不幸的是,FileInput 流无法完成这项工作。从头到尾阅读都可以,但是随机访问存在问题。它要么不允许,要么需要永远。如果招待任何其他跨平台 Java 解决方案。我和蔚来没有关系。
  • 确实如此,但是(尽管我承认我可能弄错了)我认为所有 FileChannel 对象都支持映射以及 transferTo 和 transferFrom。我使用的库使用 NIO 来提高通过映射处理大文件的效率。我对 NIO 的经验很少,因为在我有一个大文件(整个磁盘或磁盘映像)之前,FileInputStream 的效率差不多(它在 java 6 或 7 上效率更高;只是重新编译加快了我的代码)。但是,寻找比跳过快很多。

标签: java nio disk drive unc


【解决方案1】:

当访问没有缓冲的物理驱动器时,您只能读取完整的扇区。这意味着,如果扇区大小为 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();
}

Alignment and File Access Requirements

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

【讨论】:

  • 有没有直接的方法来访问磁盘扇区大小?还是需要原生?
  • @LppEdd 见this question。或者只是通过尝试读取和捕获异常来探测常见大小。
【解决方案2】:

使用 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();
}

代码很好,可行,但我的版本和你的版本都出现拒绝访问错误。

【讨论】:

  • 感谢史蒂夫的回答。我认为问题不在于参考 (\\.\PhysicalDrive0),尽管我会尝试任何方法。我的代码与您遇到的错误相同。使用 FileInputStream 的第一段代码确实适用于该引用,但不幸的是,FileInputStream 不能满足我的需要。如果它是参考,我希望它不起作用。此外,当我使用无效的引用时,我会得到一个不同的错误。
【解决方案3】:

以管理员身份运行。它确实有效,因为它只是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();
    }

编辑如果您需要随机访问,则只能使用RandomAccessFile。通过Channels 没有映射。但是上面的解决方案无论如何都不是 NIO,只是FileInput/OutputStream 之上的一个 Java NIO 层。

【讨论】:

  • 感谢 EJP,已经完成了。当我第一次问我的问题时,我使用的是 XP(没有 Windows 7)。当我在 Windows 7 中运行它时,我得到了你提到的错误。然后我以管理员身份运行,并收到一个新错误:“连接到系统的设备无法正常工作。” (见我帖子的底部)。该设备显然正在运行,因为 FileStream 工作,它也是我的操作系统磁盘。顺便说一句,磁盘 1、2 和 3 上也会发生同样的事情。
  • 好的,将尝试剪切和粘贴。
  • 啊。确实,您可以通过这种方式获得 FileChannel,但它无能为力。它只是推迟异常,直到您尝试从中读取。我添加了一个 ByteBuffer 并尝试读取它并得到:java.io.IOException:参数不正确有趣的是,这与“连接的设备......”不同。
  • @EJP “没有通过 Channels 映射” - RandomAccessFile.getChannel() 呢?
  • @apangin 这不是映射通过Channels请阅读我实际写的内容。 getChannel() 将遇到与 OP 已经抱怨的完全相同的问题。
猜你喜欢
  • 2011-12-07
  • 2010-11-26
  • 2016-12-10
  • 2012-05-21
  • 2011-02-20
  • 1970-01-01
  • 2011-02-11
  • 1970-01-01
  • 2011-02-06
相关资源
最近更新 更多