关于 new FileOutputStream(FileDescriptor)的疑问

案例一:

两个数据流关联同一个文件,都没关闭,由于数据流默认采取 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,统统关闭。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值