您的位置:首页 > 编程语言 > PHP开发

关于 new FileOutputStream(FileDescriptor)的疑问

2018-01-01 22:12 966 查看

案例一:

两个数据流关联同一个文件,都没关闭,由于数据流默认采取 append = false 的方法写入文件,故后一个数据流总是会覆盖前一个数据流写入的数据。

public class Test {
public static void main(String[] args) {
try {
FileOutputStream fos = new FileOutputStream(new File("data.txt"));
FileOutputStream fos2 = new FileOutputStream(new File("data.txt"));

fos.write("lehua".getBytes());
fos2.write("wendan".getBytes());
fos.close();
fos2.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}


运行结果:



案例二:

前一个数据流关闭后,后一个数据流依然能后正常写入数据

public class Test {
public static void main(String[] args) {
try {
FileOutputStream fos = new FileOutputStream(new File("data.txt"));
FileOutputStream fos2 = new FileOutputStream(new File("data.txt"));
fos.write("lehua".getBytes());
fos.close();
fos2.write("wendan".getBytes());
fos2.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}


运行结果:



案例三:

一个数据流按照正常方式关联着文件,另一个数据流由前一个数据流的FileDescriptor产生。

public class Test {
public static void main(String[] args) {
try {
FileOutputStream fos = new FileOutputStream(new File("data.txt"));
FileDescriptor fd = fos.getFD();

FileOutputStream fos2 = new FileOutputStream(fd);

fos.write("lehua".getBytes());
fos2.write("wendan".getBytes());

fos.close();
fos2.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}


运行结果如下:



案例三和案例一很相似,都是两个输出流没有关闭就写入了数据,但是为什么这里写入的是”lehuawendan”,而案例一写入的只是“wendan”呢?因为fos2引用了fos1的fd,个人猜测,只有new File()才真正和一个文件建立了关联,fos2是通过fos1建立的,fos2写入数据的时候,可能使用的也是fos1建立起来的关联,故虽然同样是写入,案例三没有发生数据重写,而案例一发生了。

案例四:

前一个数据流关闭后,后一个数据流无法写入数据

public class Test {
public static void main(String[] args) {
try {
FileOutputStream fos = new FileOutputStream(new File("data.txt"));
FileDescriptor fd = fos.getFD();

FileOutputStream fos2 = new FileOutputStream(fd);

fos.write("lehua".getBytes());
fos.close();

fos2.write("wendan".getBytes());
fos2.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}


运行结果如下:



疑问1

案例三中为什么 fos2 能够写入数据?



查看 FileOutputStream 的源码



关联源文件的唯一属性就是path,也就是说,要想写入数据到文件,必须知道path,那fos2知道path是什么吗?

fos2 是通过new FileOutputStream(FileDescriptor)产生的,源码如下:

public FileOutputStream(FileDescriptor fdObj) {
SecurityManager security = System.getSecurityManager();
if (fdObj == null) {
throw new NullPointerException();
}
if (security != null) {
security.checkWrite(fdObj);
}
this.fd = fdObj;
this.append = false;
this.path = null;

fd.attach(this);
}


根据源码我们知道,FileOutputStream fos2 并不直接知道要写入数据的路径(因为其this.path = null),fos2只是从fos1得到了一个 FileDescriptor对象,难道fos1的fd中有path信息?

查看FileDescriptor源码如下:



FileDescriptor 的成员属性中并不包含path信息,而且fos1在赋值 fd时,传入的是一个新值,肯定没有path信息,那唯一可以传入path信息的就是fd.attach()



查看 FileDescriptor的attach()源码如下:



而FileOutputStream 本身 implements Closeable,也就是说,fos1将自己赋值给了fd的属性parent,那fd当然能够掌握fos1的全部信息,也就包括path信息,fos2引用了fd,故也就找到了写入数据的路径。

测试如下:


public class Test {
public static void main(String[] args) {
try {
FileOutputStream fos = new FileOutputStream(new File("data.txt"));
System.out.println("源FileOutputStream 地址:" + fos);

FileDescriptor fd = fos.getFD();

FileOutputStream fos2 = new FileOutputStream(fd);

fos.write("lehua".getBytes());
fos2.write("wendan".getBytes());

fos.close();
fos2.close();

Class clazz = fd.getClass();

Field field = clazz.getDeclaredField("parent");
field.setAccessible(true);

FileOutputStream source = (FileOutputStream) field.get(fd);
System.out.println("fd 的parent 地址 :                  "+ source);

Class c = source.getClass();
Field field2 = c.getDeclaredField("path");
field2.setAccessible(true);
String path = (String) field2.get(source);
System.out.println("fos2 可以访问路径: "+ path);
} catch (Exception e) {
e.printStackTrace();
}
}
}


运行结果如下:



大胆猜测:

FileOutputStream 的write()方法是native方法,源码如下:

private native void write(int b, boolean append) throws IOException;


此方法在写入数据的时候,首先直接判断path是否为null,如果为null,则通过其属性FileDescriptor,通过上面的反射方法获取path,总之,一定要找到写入数据的路径。

疑问2:

同样是两个不同的FileOutputStream对象,为什么案例2,前者fos关闭,后者正常写入,而案例四,前者关闭了,而后者就崩溃了呢?


fos.write("lehua".getBytes());
fos.close();

fos2.write("wendan".getBytes());
fos2.close();


查看FileOutputStream的close()源码如下:



关键之处就是 fd.closeAll(),查看源码如下:



此方法关闭了此FileDescriptor对象的所有parent,fos1对象建立时调用一次fd.attach(),则fd.parent = fos1,源码如下:

public FileOutputStream(File file, boolean append) throws FileNotFoundException {
……
this.fd = new FileDescriptor();
fd.attach(this);
……
}


fos2对象建立时,再调用一次fd.attach(),则fos1,fos2都成为了 fd对象的otherParent,

源码如下:

public FileOutputStream(FileDescriptor fdObj) {
……
this.fd = fdObj;
this.append = false;
this.path = null;

fd.attach(this);
}


所以 fos1调用close(),关闭了其fd的所有otherParent,也就把fos2关闭了,所以fos2再写入时,就会报错。

注意:

fos2是通过fos1产生的,fos2调用close(),fos1也会照样关闭,因为他们作用的,是连接它们的FileDescriptor对象,其执行closeAll(),六亲不认,所有parent,统统关闭。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  FileDescri FileOutput