在将代码从Linux(Ubuntu LTS 12.4)环境移植到Windows Server 2008的过程中,我遇到一个问题。
我需要使用内存映射文件,但无法在Windows上防止以下错误。
在下面的单元测试中重现了此问题。这两个测试在Linux上均成功完成,但是在Windows上,测试 testWithRandowmAccessFile 失败,并且堆栈跟踪位于底部。 testWithRandowmAccessFile 测试失败
的根本原因是什么? 我应该如何在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上从命令提示符运行单元测试时得到的跟踪跟踪。
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
在Java中,文件映射是垃圾收集的,并且没有支持的方法来强制销毁映射。
从FileChannel.map()文档中:
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