下面看段代码:
@Test
public void append(){
DateTimeFormatter df = DateTimeFormatter.ofPattern("HH:mm");
try {
//追加模式创建FileOutputStream
FileOutputStream fos = new FileOutputStream("d:/tmp/1.txt",true);
FileChannel fc = fos.getChannel();
ByteBuffer buffer = ByteBuffer.wrap(df.format(LocalTime.now()).getBytes("utf-8"));
println("A fileChannel.position ()= " + fc.position());
println("channel size="+fc.size());
//此处无论position设置成什么值,下面的write方法都是在文件末尾追加内容
fc.position(0);
println("A fileChannel.position ()= " + fc.position());
println( "write() 1 返回值 :" + fc.write(buffer)) ;
println ("B fileChannel .position ()= " + fc.position());
fc.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
其实position方法调用成功了,感兴趣的可以跟踪下FileChannelImpl类的position方法:
public long position() throws IOException {
this.ensureOpen();
synchronized(this.positionLock) {
long var2 = -1L;
int var4 = -1;
long var5;
try {
this.begin();
var4 = this.threads.add();
if (this.isOpen()) {
do {
//通过position(long newPos)方法后看下this.nd.seek(this.fd, -1L)返回的值就知道其实设置position是成功的
var2 = this.append ? this.nd.size(this.fd) : this.nd.seek(this.fd, -1L);
} while(var2 == -3L && this.isOpen());
var5 = IOStatus.normalize(var2);
return var5;
}
var5 = 0L;
} finally {
this.threads.remove(var4);
this.end(var2 > -1L);
assert IOStatus.check(var2);
}
return var5;
}
}
上面这一诡异现象印证了一句话“你说的很对,但是我就是不听”
省略中间环节,直接看下FileDispatcherImpl类的write方法
int write(FileDescriptor var1, long var2, int var4) throws IOException {
return write0(var1, var2, var4, this.append);
}
通过名字我们可以知道write0是个native方法,继续往下看jdk源码是怎么实现write0这个方法的:
JNIEXPORT jint JNICALL
Java_sun_nio_ch_FileDispatcherImpl_write0(JNIEnv *env, jclass clazz, jobject fdo,
jlong address, jint len, jboolean append)
{
BOOL result = 0;
DWORD written = 0;
HANDLE h = (HANDLE)(handleval(env, fdo));
if (h != INVALID_HANDLE_VALUE) {
OVERLAPPED ov;
LPOVERLAPPED lpOv;
//针对追加模式,特殊处理,所以不管之前position设置的位置如何,如果以追加模式打开文件,
//在windows系统都会把数据追加到文件末尾,而不是position设置的位置
if (append == JNI_TRUE) {
ov.Offset = (DWORD)0xFFFFFFFF;
ov.OffsetHigh = (DWORD)0xFFFFFFFF;
ov.hEvent = NULL;
lpOv = &ov;
} else {
lpOv = NULL;
}
result = WriteFile(h, /* File handle to write */
(LPCVOID)address, /* pointers to the buffers */
len, /* number of bytes to write */
&written, /* receives number of bytes written */
lpOv); /* overlapped struct */
}
if ((h == INVALID_HANDLE_VALUE) || (result == 0)) {
JNU_ThrowIOExceptionWithLastError(env, "Write failed");
}
return convertReturnVal(env, (jint)written, JNI_FALSE);
}
总结:
1、这个问题产生的原因不是position无效或者失败,而是write方法另有蹊跷!
2、如果想自由控制position,可以使用RandomAccessFile或者通过FileChannel的int write(ByteBuffer src, long position)方法来显示传入写入位置,关于FileChannelImpl类的int write(ByteBuffer src, long position)方法以及int write(ByteBuffer var1)方法
参考文章:
1、https://docs.microsoft.com/zh-cn/windows/win32/api/fileapi/nf-fileapi-writefile