Windows:移动先前在内存中映射的文件失败

– :=编辑为简化=: –

我在将代码从Linux(Ubuntu LTS 12.4)环境移植到Windows Server 2008的过程中遇到了一个问题。

我需要使用内存映射文件,但我不能防止Windows下面的错误。

这个问题在下面的unit testing中重现。 2个testing在Linux上成功,但在Windows上testingtestWithRandowmAccessFile失败,底部的堆栈跟踪失败。

testWithRandowmAccessFiletesting失败的根本原因是什么?
我应该如何在Windows上实现?

import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import org.apache.commons.io.IOUtils; import org.junit.Test; public class TestIOOnWindows { @Test public void testWithRandowmAccessFile() throws IOException { final File sourceFile = new File("source.txt"); final File manipulatedFile = new File("manipulated.txt"); final File targetFile = new File("target.txt"); try ( FileInputStream sourceInputStream = new FileInputStream(sourceFile); RandomAccessFile manipulated = new RandomAccessFile(manipulatedFile, "rw"); FileChannel fcOut = manipulated.getChannel() ) { byte[] sourceBytes = new byte[Long.valueOf(sourceFile.length()).intValue()]; IOUtils.read(sourceInputStream, sourceBytes); final int length = sourceBytes.length; // ========= with this single line on Windows, the move fails ====== MappedByteBuffer byteBuffer = fcOut.map(FileChannel.MapMode.READ_WRITE, 0, length); // commenting this line would not prevent the error on Windows byteBuffer.put(sourceBytes, 0, length); } Files.move( Paths.get(manipulatedFile.getAbsolutePath()), Paths.get(targetFile.getAbsolutePath()), StandardCopyOption.REPLACE_EXISTING); } @Test public void testWithFileOutputStream() throws IOException { final File sourceFile = new File("source.txt"); final File manipulatedFile = new File("manipulated.txt"); final File targetFile = new File("target.txt"); try ( FileInputStream sourceInputStream = new FileInputStream(sourceFile); FileOutputStream manipulatedOutputStream = new FileOutputStream(manipulatedFile); FileChannel fcIn = sourceInputStream.getChannel(); FileChannel fcOut = manipulatedOutputStream.getChannel() ) { final long length = sourceFile.length(); // ========= with this single line on Windows, the move succeed ==== fcIn.transferTo(0, length, fcOut); } Files.move( Paths.get(manipulatedFile.getAbsolutePath()), Paths.get(targetFile.getAbsolutePath()), StandardCopyOption.REPLACE_EXISTING); } } 

添加从Windows上的命令提示符运行unit testing时得到的粘性轨迹。

 There was 1 failure: 1) testWithRandowmAccessFile(TestIOOnWindows) java.nio.file.FileSystemException: C:\Users\Administrator\AppData\Local\Temp\manipulated.txt -> C:\Users\Administrator\AppData\Local\Temp\target.txt: The process cannot access the file because it is being used by another process. at sun.nio.fs.WindowsException.translateToIOException(Unknown Source) at sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source) at sun.nio.fs.WindowsFileCopy.move(Unknown Source) at sun.nio.fs.WindowsFileSystemProvider.move(Unknown Source) at java.nio.file.Files.move(Unknown Source) ===> at TestIOOnWindows.testWithRandowmAccessFile(TestIOOnWindows.java:40) <=== at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222) at org.junit.runners.ParentRunner.run(ParentRunner.java:300) at org.junit.runners.Suite.runChild(Suite.java:128) at org.junit.runners.Suite.runChild(Suite.java:24) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222) at org.junit.runners.ParentRunner.run(ParentRunner.java:300) at org.junit.runner.JUnitCore.run(JUnitCore.java:157) at org.junit.runner.JUnitCore.run(JUnitCore.java:136) at org.junit.runner.JUnitCore.run(JUnitCore.java:117) at org.junit.runner.JUnitCore.runMain(JUnitCore.java:98) at org.junit.runner.JUnitCore.runMainAndExit(JUnitCore.java:53) at org.junit.runner.JUnitCore.main(JUnitCore.java:45) FAILURES!!! Tests run: 2, Failures: 1 

Solutions Collecting From Web of "Windows:移动先前在内存中映射的文件失败"

在Java中,文件映射是垃圾收集的,并没有支持强制销毁映射的方法。

FileChannel.map()文档:

缓冲区和它表示的映射将保持有效,直到缓冲区本身被垃圾回收。

映射一旦建立,就不依赖于用来创建它的文件通道。 关闭频道,特别是对映射的有效性没有影响。

在Sun的JDK中,可以通过在执行文件移动之前强制销毁映射来测试这确实是罪魁祸首:

 import sun.nio.ch.DirectBuffer; import sun.misc.Cleaner; [...] if (byteBuffer.isDirect()) { Cleaner cleaner = ((DirectBuffer) byteBuffer).cleaner(); cleaner.clean(); } // move file 

错误消息说明了这一切。 Windows不能象任何Unix一样删除或重命名一个打开的文件,并且打开workFile ,然后立即尝试重新命名它。 您必须先关闭流。