两个数据流关联同一个文件,都没关闭,由于数据流默认采取 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();
}
}
}
案例三中为什么 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,总之,一定要找到写入数据的路径。
同样是两个不同的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,统统关闭。