Java IO - FileInputStream&FileOutputStream

原创 2016年06月01日 21:30:17

基本概念

  • FileInputStream 从文件系统中的某个文件中获得输入字节

  • FileOutputStream 文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。文件是否可用或能否可以被创建取决于基础平台。特别是某些平台一次只允许一个 FileOutputStream(或其他文件写入对象)打开文件进行写入。在这种情况下,如果所涉及的文件已经打开,则此类中的构造方法将失败。

  • 继承结构

这里写图片描述

这里写图片描述


实例探究

  • 写入文件的字节都会被底层按照系统默认编码转换成字符存到文件中

  • 当使用文件字节输出来的时候会被反转成字节

public class Test {

    private static final String TEMPFILE = "E:" + File.separator + "Test.txt";
    private static final String DESFILE = "E:" + File.separator + "Test2.txt";

    //英文字母 a ~ h
    private static final byte[] byteArray = { 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69 };

    public static void main(String[] args) throws IOException {
        write(TEMPFILE);
        read(TEMPFILE);
        copyFile(TEMPFILE, DESFILE);
    }

    public static void write(String path) {
        // 初始化,不然关闭流时编译不通过
        FileOutputStream fos = null;

        try {
            // 创建流,这里探究追加模式。假设文本内容现在为 1,2,3
            fos = new FileOutputStream(new File(path), true);

            // 写入 a
            fos.write(byteArray[0]);

            // 写入 b,c,d,e,f
            fos.write(byteArray, 1, 5);

            fos.close();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void read(String path) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(new File(path));

            char temp = (char) fis.read();

            // 输出 1
            System.out.print(temp);

            byte[] buffer = new byte[1024];
            int count = 0;
            while ((count = fis.read(buffer)) != -1) {
                // 输出 234abcdefabcdefabcdef
                System.out.println(new String(buffer, 0, count));
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // 文件复制
    public static void copyFile(String srcPath, String desPath) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(new File(srcPath));
            fos = new FileOutputStream(new File(desPath));

            // 为了效率,一般采用按字节数组读取
            byte[] buffer = new byte[1024];
            int count = 0;
            while ((count = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, count);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }

                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

源码分析

1.FileInputStream

类结构图如下:

这里写图片描述

首先来看类中的静态代码块,它的作用是设置类中(也就是FileInputStream)的属性的内存地址偏移量,便于在必要时操作内存给它赋值。

static {
    initIDs();
}

private static native void initIDs();

成员变量

// 文件描述符类,表示用来打开文件的句柄
private FileDescriptor fd;

// 文件通道,NIO部分
private FileChannel channel = null;

private Object closeLock = new Object(); 

private volatile boolean closed = false;

private static final ThreadLocal<Boolean> runningFinalize = new ThreadLocal<Boolean>();

该类总共定义了 3 个构造函数,通过代码可以发现可以 ①③ 都是通过获取实际的文件连接,在通过 open 方法来创建流。而 是直接通过文件描述符创建流。

// ①构造函数,通过文件路径创建
public FileInputStream(String name) throws FileNotFoundException {
    this(name != null ? new File(name) : null);
}

// ②构造函数,通过文件描述符创建
public FileInputStream(FileDescriptor fdObj) {
    SecurityManager security = System.getSecurityManager();
    if (fdObj == null) {
        throw new NullPointerException();
    }
    if (security != null) {
        security.checkRead(fdObj);
    }
    fd = fdObj;

    fd.incrementAndGetUseCount();
}

// ③构造函数,通过文件连接创建
public FileInputStream(File file) throws FileNotFoundException {

    String name = (file != null ? file.getPath() : null);

    // 操作文件之前,检查是否具有 read 权限
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkRead(name);
    }

    // 判断路径的合法性
    if (name == null) {
        throw new NullPointerException();
    }

    // 打开文件
    fd = new FileDescriptor();
    fd.incrementAndGetUseCount();
    open(name);
}

// 关键-->打开系统文件,native 方法
private native void open(String name) throws FileNotFoundException;

接下来看它 的 read 方法,观察下面的代码,发现在类中定义了常见的 3 种读取方式,如①②③。① 本身就是个native 方法,而 ②③则是通过 readBytes 这个 native 方法用来实现文件的读取。

//① 从此输入流中读取一个数据字节
public native int read() throws IOException;

//关键-->实际操作由它完成
private native int readBytes(byte b[], int off, int len) throws IOException;


//②从此输入流中将最多 len 个字节的数据读入一个 byte 数组中
public int read(byte b[]) throws IOException {
    return readBytes(b, 0, b.length);
}

//③从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中
public int read(byte b[], int off, int len) throws IOException {
    return readBytes(b, off, len);
}

接着来看其他流中没有的而 FileInputStream 中独有的几个方法

//返回文件描述符,表示该文件正在被 FileInputStream 使用
public final FileDescriptor getFD() throws IOException {
    if (fd != null) {
        return fd;
    }
    throw new IOException();
}

//返回文件文件通过,这里只允许单线程访问
public FileChannel getChannel() {
    synchronized (this) {
        if (channel == null) {
            channel = FileChannelImpl.open(fd, true, false, this);

            fd.incrementAndGetUseCount();

        }
        return channel;
    }
}

//重写了 Object 的方法,确保该文件输入流的close方法被调用的时候它不用有引用
protected void finalize() throws IOException {
    if ((fd != null) && (fd != FileDescriptor.in)) {
        // 当前其他流操作该对象时,调用该方法无法释放资源。但是可以调用 colse 方法强行释放。
        runningFinalize.set(Boolean.TRUE);
        try {
            close();
        } finally {
            runningFinalize.set(Boolean.FALSE);
        }
    }
}

最后再来看看剩下的几个方法

public native long skip(long n) throws IOException;

public native int available() throws IOException;

public void close() throws IOException {

    synchronized (closeLock) {
        if (closed) {
            return;
        }
        closed = true;
    }

    if (channel != null) {
        // 减少与该 FD 相关联的计算器(当每获得一个新的通道时,该计算器增加)
        fd.decrementAndGetUseCount();
        channel.close();
    }

    int useCount = fd.decrementAndGetUseCount();

    if ((useCount <= 0) || !isRunningFinalize()) {
        close0();
    }
}

private native void close0() throws IOException;

2.FileOutputStream

类结构图

Alt text

首先来看静态代码块,作用同上。

static {
    initIDs();
}

private static native void initIDs();

成员变量

private FileDescriptor fd;

private FileChannel channel = null;

private boolean append = false;

private Object closeLock = new Object();

private volatile boolean closed = false;

private static final ThreadLocal<Boolean> runningFinalize = new ThreadLocal<Boolean>();

在分析构造函数之前先来看两个 native 方法

//替换原文件的内容
private native void open(String name) throws FileNotFoundException;

//文件原有内容的末尾追加新内容
private native void openAppend(String name) throws FileNotFoundException;

该类定义了 5 个构造函数,其中 ① ~ ④ 都是通过 file 类来创建流,具体的实现都在 ④ 里面。默认未指定 append 时为false,表示替换原文件的内容,当 append 为 true 时,表示在文件原有内容的末尾追加新内容。观察 ④,发现该方法分别调用了 openopenAppend 这两个 native 方法来实现操作。而 ⑤ 则是通过文件描述符完成创建

//①构造函数,根据文件路径创建,默认不追加内容
public FileOutputStream(String name) throws FileNotFoundException {
    this(name != null ? new File(name) : null, false);
}

//②构造函数,根据文件路径创建,可以指定是否追加内容
public FileOutputStream(String name, boolean append) throws FileNotFoundException {
    this(name != null ? new File(name) : null, append);
}

//③构造函数,根据文件连接创建,默认不追加内容
public FileOutputStream(File file) throws FileNotFoundException {
    this(file, false);
}

//④构造函数,根据文件连接创建,可以指定是否追加内容
public FileOutputStream(File file, boolean append) throws FileNotFoundException {
    String name = (file != null ? file.getPath() : null);
    SecurityManager security = System.getSecurityManager();

    //检查是否具有 "写" 权限
    if (security != null) {
        security.checkWrite(name);
    }
    if (name == null) {
        throw new NullPointerException();
    }
    fd = new FileDescriptor();
    fd.incrementAndGetUseCount();
    this.append = append;

    //判断是否追加内容
    if (append) {
        openAppend(name);
    } else {
        open(name);
    }
}

//⑤构造函数,根据文件描述符创建
public FileOutputStream(FileDescriptor fdObj) {
    SecurityManager security = System.getSecurityManager();
    if (fdObj == null) {
        throw new NullPointerException();
    }

    if (security != null) {
        security.checkWrite(fdObj);
    }

    fd = fdObj;

    fd.incrementAndGetUseCount();
}

接着来看 writer 方法,与 文件输入流的 read 方法类型,不再分析。

public native void write(int b) throws IOException;

private native void writeBytes(byte b[], int off, int len) throws IOException;

public void write(byte b[]) throws IOException {
    writeBytes(b, 0, b.length);
}

public void write(byte b[], int off, int len) throws IOException {
    writeBytes(b, off, len);
}

再来看看普通输出流没有,而 FileOutputStream 独有的方法。

public final FileDescriptor getFD() throws IOException {
    if (fd != null) {
        return fd;
    }

    throw new IOException();
}

public FileChannel getChannel() {
    synchronized (this) {
        if (channel == null) {
            channel = FileChannelImpl.open(fd, false, true, this, append);

            fd.incrementAndGetUseCount();
        }
        return channel;
    }
}

protected void finalize() throws IOException {
    if (fd != null) {
        if (fd == FileDescriptor.out || fd == FileDescriptor.err) {
            flush();
        } else {

            runningFinalize.set(Boolean.TRUE);
            try {
                close();
            } finally {
                runningFinalize.set(Boolean.FALSE);
            }
        }
    }
}

最后再来看看 close 方法

public void close() throws IOException {
    synchronized (closeLock) {
        if (closed) {
            return;
        }
        closed = true;
    }

    if (channel != null) {
        fd.decrementAndGetUseCount();
        channel.close();
    }

    int useCount = fd.decrementAndGetUseCount();

    if ((useCount <= 0) || !isRunningFinalize()) {
        close0();
    }
}

private static boolean isRunningFinalize() {
    Boolean val;
    if ((val = runningFinalize.get()) != null)
        return val.booleanValue();
    return false;
}

private native void close0() throws IOException;

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

JAVA高级视频02_IO输入与输出 05 FileInputStream和FileOutPutStream

FileInputStream和FileOutputStream分别用来创建磁盘文件的输入流和输出流,构造函数来指定文件的路径和文件名。这里的In和out也是相对应用程序来说的,FileInputSt...

java基础之IO流中FileInputStream和FileOutputStream的认识和实现

FileInputStream和FileouputStream继承与基类字节流InputStream,OutputStream

Java IO流 FileOutputStream类 和 FileInputStream类 FileReader类和FileWriter类

FileOutputStream类和FileInputStream类,他们的父类分别是OutputStream类和InputStream类。FileOutputStream类FileInputStre...

java核心技术之IO流(二)FileInputStream和FileOutputStream

简单的说明从文件中读取和写入数据,我们要使用FileInputStream和FileOutputStream类来实现。只是要注意的是,这二个类处理的是字节(byte)型文件。FileInputStre...

Java IO流 FileOutputStream类 和 FileInputStream类 FileReader类和FileWriter类

FileOutputStream类 和 FileInputStream类 简单用法,在现实工作中一般这个类使用频率比较低。一般我会使用FileReader类和FileWriter类 ...
  • tianzhw
  • tianzhw
  • 2011年01月05日 13:20
  • 3511

Java_IO 文件输入流(FileInputStream)与文件输出流(FileOutputStream)

FileInputStream和FileOutputStream:用于磁盘、光盘或其他存储设备中文件的字节流的读写 1 从文件系统中读取文件到程序(内存中)example1:(FileInputStr...
  • hggliu
  • hggliu
  • 2016年08月21日 22:20
  • 1406

JAVA IO系列----FileInputStream和FileOutputStream类,Reader和Writer类

·FileInputStream和FileOutputStream类分别用来创建磁盘文件的输入流和输出流对象,通过它们的构造函数来指定文件路径和文件名。...

JAVA中常用IO流类:FileInputStream和FileOutputStream

FileInputStream 用于读取本地文件中的字节数据,继承自InputStream类 构造方法摘要 FileInputStream(File file)     ...

java_IO流之FileInputStream和FileOutputStream

看,API说的多简单啊: FileInputStream 用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用 FileReader。 FileOutputStream 用于写入诸如图...

Java基础 - IO流之字节流,FileInputStream,FileOutputStream,BufferedInputStream,BufferedOutputStream

Java基础 - IO流之字节流,FileInputStream,FileOutputStream,BufferedInputStream,BufferedOutputStream...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Java IO - FileInputStream&FileOutputStream
举报原因:
原因补充:

(最多只允许输入30个字)