zoukankan      html  css  js  c++  java
  • IO流(1)--文件流及其原理

    文件流的基本类有四种:

    • FileInputStream/FileOutputStream
    • FileReader/FileWriter

    一、File对象

    文件流是一种节点流,它沟通程序与文件之间的数据传输。在Java中,文件被抽象为File。

    我们通过File的构造器创建File对象,最常用的是通过文件路径字符串进行创建。

    public class Main{
        public static void main(String[] args){
            // 将一个已经存在的,或者不存在的文件或者目录封装成file对象
            File f = new File("/home/ubuntu/test/a.txt");
            File dir = new File("/home/ubuntu/test");
        }
    }        

    File类提供了很多对于文件或目录的操作。

    1. 获取文件的信息。文件名称,路径,文件大小,修改时间等等。
    2. 文件的创建和删除,目录的创建
    3. 文件设置权限(读,写,执行)
    4. ...

    二、FileInputStream/FileOutputStream

    FileInputStream和FileOutputStream是作用于文件的字节流。其实例连接了程序内存与文件对象,在构造流对象的时候需要指定文件对象。

    // FileInputStream.java
    public class FileInputStream extends InputStream{
        // 传入文件名作为参数
        public FileInputStream(String name) throws FileNotFoundException {
            this(name != null ? new File(name) : null);
        }
    
        // 传入文件作为参数
        public FileInputStream(File file) throws FileNotFoundException {
            String name = (file != null ? file.getPath() : null);
            SecurityManager security = System.getSecurityManager();
            if (security != null) {
                security.checkRead(name);
            }
            if (name == null) {
                // 文件对象为空指针
                throw new NullPointerException();
            }
            // 文件路径无效
            if (file.isInvalid()) {
                throw new FileNotFoundException("Invalid file path");
            }
            // 文件描述符
            fd = new FileDescriptor();
            fd.attach(this);
            // 设置path
            path = name;
            // 打开文件
            open(name);
        }
        // 传入文件描述符
        public FileInputStream(FileDescriptor fdObj) {
            SecurityManager security = System.getSecurityManager();
            if (fdObj == null) {
                throw new NullPointerException();
            }
            if (security != null) {
                security.checkRead(fdObj);
            }
            fd = fdObj;
            path = null;
    
            /*
             * FileDescriptor is being shared by streams.
             * Register this stream with FileDescriptor tracker.
             */
            fd.attach(this);
        }
    
    }

    从源码中我们可以看到,FileInputStream在构造的时候需要传入一个文件对象,同时你可能还注意到,在构造器中我们还实例化了一个FileDescriptor,甚至在重载的构造器里有直接传入一个FileDescriptor对象,这个对象有什么作用暂且不说,我们接着看一下FileInputStream的读数据操作。

    //FileInputStream.java
    // 打开文件
    private void open(String name) throws FileNotFoundException {
        open0(name);
    }
    private native void open0(String name) throws FileNotFoundException;
    
    // 读字节
    public int read() throws IOException {
        return read0();
    }
    private native int read0() throws IOException;
    
    // 读字节数组
    public int read(byte b[]) throws IOException {
        return readBytes(b, 0, b.length);
    }
    public int read(byte b[], int off, int len) throws IOException {
        return readBytes(b, off, len);
    }
    private native int readBytes(byte b[], int off, int len) throws IOException;
    
    // 忽略部分字节
    public long skip(long n) throws IOException {
        return skip0(n);
    }
    private native long skip0(long n) throws IOException;

    可以看到,读数据等操作都由本地方法实现。我们可以分析一下,FileInputStream在文件与程序之间建立了连接,实现了文件的读字节操作。在构造FileInputStream的实例对象时候,我们传入了文件,那么具体的操作怎么去执行呢,FileDescriptor就派上了用场。

    根据定义,FileDescriptor也称作文件描述符,内核通过文件描述符来访问文件,文件描述符通常为非负整数的索引,它指向内核为每个进程所维护的该进程打开文件的记录表。通俗来说,文件描述符就是文件的索引,有了这个索引,内核才能找到文件,也就才能把“流”连接起来,对于文件的操作也是基于这个索引展开的。

    还有一点比较有意思,POSIX定义了3个符号常量:

    • 标准输入的文件描述符 0
    • 标准输出的文件描述符 1
    • 标准错误的文件描述符 2

    而在FileDescriptor类中,也定义了三个常量in、out、err。根据注释,这三个变量就是System.out、System.in、System.err所对应的三个文件描述符。

    public static final FileDescriptor in = new FileDescriptor(0);
    public static final FileDescriptor out = new FileDescriptor(1);
    public static final FileDescriptor err = new FileDescriptor(2);

    总的来说,为了构建基本流,我们需要:

    • 程序内存端。
    • 节点端,如文件对象。
    • 文件描述符对象,用于开放节点(如开放文件、开放套接字、或者某个字节的源/目的地)
    • 节点流对象,用于连接程序内存与文件对象,连接内存自不用说,连接文件对象则是通过文件描述符来完成。

    FIleOutputStream与FileInputStream也是类似的,只是将读操作变为写操作。

    三、FileReader/FileWriter

    FileReader/FileWriter是作用于文件的字符流。它们分别继承自转换流InputStreamReader/OutputStreamWriter。在构造流对象时同样需要传入文件对象。此处就以FileWriter为例。

    FileWriter继承自OutputStreamWriter,只是定义了几个构造器。在构造器中,FileWriter调用了父类构造器,并传入FileOutputStream对象作为参数。由此可见FileWriter的写文件操作底层还是通过FileOutputStream完成。

    public class FileWriter extends OutputStreamWriter {
    
        // 传入文件名
        public FileWriter(String fileName) throws IOException {
            super(new FileOutputStream(fileName));
        }
    
        // 传入文件名,并指定追加模式
        public FileWriter(String fileName, boolean append) throws IOException {
            super(new FileOutputStream(fileName, append));
        }
    
        // 传入文件对象
        public FileWriter(File file) throws IOException {
            super(new FileOutputStream(file));
        }
    
        // 传入文件对象,指定是否追加
        public FileWriter(File file, boolean append) throws IOException {
            super(new FileOutputStream(file, append));
        }
    
        // 传入文件描述符
        public FileWriter(FileDescriptor fd) {
            super(new FileOutputStream(fd));
        }
    
    }

    那么写操作的功能都在OutputStreamWriter中实现

    // OutputStreamWriter.java
    // 在构造器中会初始化一个StreamEncoder的实例对象se,对文件的操作就是通过se的方法来完成。
    // 构造se对象时将FileWriter传入的FileOutputStream对象作为参数,因此我猜想写文件的操作过程在se中,首先对字符进行编码,然后调用FileOutputStream进行写入操作。
    // 写字符 public void write(int c) throws IOException { se.write(c); } // 写字符数组 public void write(char cbuf[], int off, int len) throws IOException { se.write(cbuf, off, len); } // 写字符串 public void write(String str, int off, int len) throws IOException { se.write(str, off, len); } // 刷写 public void flush() throws IOException { se.flush(); } // 关闭流 public void close() throws IOException { se.close(); }

    FileReader与FileWriter的原理也是类似的。这里就不一一赘述了。

    四、总结

    文件的操作流主要就是这四个,我们可以通过源码窥见出,FileInputStream/FileOutputStream是对文件进行字节的读写。FileReader/FileWriter是字符流,它们通过中间的编码解码器操作,将字符转换成字节或者将字节转换成字符,最终对文件的操作还是落在FileInputStream/FileOutputStream这两个字节流上。

  • 相关阅读:
    Android实战开发租赁管理软件(适配UI,数据的存储,多线程下载)课程分享
    随 机 数 算 法
    java 状态模式 解说演示样例代码
    数据挖掘 决策树算法 ID3 通俗演绎
    经常使用表单数据的验证方法
    编程基本功训练:流程图画法及练�
    log4net使用具体解释
    妄想性仮想人格障害 新手教程 +改动器
    使用VS插件在VS2012/2013上编辑和调试Quick-Cocos2d-x的Lua代码
    经典回忆Effective C++ 1
  • 原文地址:https://www.cnblogs.com/zhengshuangxi/p/11061513.html
Copyright © 2011-2022 走看看