【Java IO】2、文件流与转换流

目录

说明:

一、文件字节流

1、字节输入流 FileInputStream

2、字节输出流 FileOutputStream

二、文件字符流

三、转换流

1、字符输入流 InputStreamReader

2、字符输出流 OutputStreamWriter


说明:

上一篇总结了Java I/O流的分类,其中 InputStream和 OutputStream 是所有字节流的基类、Reader 和 Writer 是所有字符流的基类。当然了,不同用途的流则对应有不同的子类,这篇从访问文件的角度,整理下文件流和转换流。文件流有:字节文件输入流 FileInputStream、字节文件输出流 FileOutputStream、字符文件输入流 FileReader、字符文件输出流 FileWriter转换流则有:InputStreamReader 和 OutputStreamWriter,它们在字节转字符操作中起到桥梁的作用。

一、文件字节流

1、字节输入流 FileInputStream

用于应用程序读取外部设备(这里指的是文件)的字节数据,源码分析如下:

/**
 * 源码注释:JDK8
 *   FileInputStream是从文件系统中的文件中获取输入字节,用于读取原始字节流,比如图像数据。
 **/
public class FileInputStream extends InputStream{
    private final FileDescriptor fd; //文件描述符
    private final String path; //引用文件路径
    private FileChannel channel = null; //文件通道
    private final Object closeLock = new Object(); //对象监视器
    private volatile boolean closed = false; //流关闭的标识
    
    -------------------------------构造方法---------------------------------
    // 传入文件路径
    public FileInputStream(String name) throws FileNotFoundException {
        // this指的是参数为File的FileInputStream构造器
        this(name != null ? new File(name) : null); 
    }  
    // 传入文件对象File
    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 = name;
        open(name); // 打开文件,本质上调用一个本地方法open0(),获取文件中的输入字节
    }
    private void open(String name) throws FileNotFoundException {open0(name);}
    private native void open0(String name) throws FileNotFoundException; 
    // 传入文件描述符
    public FileInputStream(FileDescriptor fdObj) {
        SecurityManager security = System.getSecurityManager();
        if (fdObj == null) {
            throw new NullPointerException();
        }
        if (security != null) {
            security.checkRead(fdObj);
        }
        fd = fdObj;
        path = null;
        fd.attach(this);
    }
    
    -------------------------------从输入流中读取字节数据---------------------------------
    // 直接读取一个字节的数据
    public int read() throws IOException {return read0();} 
    // 从输入流中读取字节数据到整个字节数组中(默认偏移量和大小)
    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);}
    // 读取的时候,跳过指定数量的字节
    public long skip(long n) throws IOException {return skip0(n);}
    // 预估输入流中可用字节的数量
    public int available() throws IOException {return available0();}
    // 私有本地方法
    private native int read0() throws IOException;
    private native int readBytes(byte b[], int off, int len) throws IOException;
    private native long skip0(long n) throws IOException;
    private native int available0() throws IOException;

    -------------------------------关闭输入流---------------------------------
    // finalize():输入流的终结方法。
    // 如果FileDescriptor对象的引用是共享的,要确保在安全操作下调用终结器,
    //      当引用全都不可达时,才能调用close()关闭。
    protected void finalize() throws IOException {
        if ((fd != null) &&  (fd != FileDescriptor.in)) {
            /* if fd is shared, the references in FileDescriptor
             * will ensure that finalizer is only called when
             * safe to do so. All references using the fd have
             * become unreachable. We can call close()
             */
            close();
        }
    }
    // 关闭流,释放资源
    public void close() throws IOException {
        synchronized (closeLock) { //线程同步操作
            if (closed) {
                return;
            }
            closed = true;
        }
        if (channel != null) {
           channel.close(); //关闭文件通道
        }
        fd.closeAll(new Closeable() { //关闭所有文件
            public void close() throws IOException {
               close0(); 
           }
        });
    }
    private native void close0() throws IOException; // 私有本地方法
}

小结一下:

  • 通过构造方法创建一个可以从输入流中读取字节数据的对象,参数可以是文件路径、文件对象File,文件描述符对象FileDescriptor。
  • FileInputStream提供了多个读取字节数据的read()方法,及跳过指定数量字节的skip()方法,还有关闭流并释放资源的close()方法。
  • finalize()是一个终结方法,当fd对象的引用不再有其他对象关联或使用时,也就是JVM中的不可达分析,才能调用close()方法关闭。

实战操作:(从目标文件读取字节数据)

// FileInputStream方式没有使用缓冲区,效率较低,实际工作中不建议使用
public class Test {
    public static void main(String[] args) throws IOException {
        File file = new File("D:/targetTest.txt");
        System.out.println(read(file));
    }

    public static String read(File file) throws IOException {
        InputStream in = new FileInputStream(file);

        // 一次性取多少个字节
        byte[] bytes = new byte[1024];
        // 用来接收读取的字节数组
        StringBuilder sb = new StringBuilder();
        // 读取到的字节数组长度,为-1时表示没有数据
        int length = 0;
        // 循环取数据
        while ((length = in.read(bytes)) != -1) {
            // 将读取的内容转换成字符串
            sb.append(new String(bytes, 0, length));
        }
        
        in.close();

        return sb.toString();
    }
}

2、字节输出流 FileOutputStream

用于应用程序向外部设备(这里指的是文件)写入字节数据,源码分析如下:

/**
 * 源码注释:JDK8
 *   FileOutputStream向文件或文件描述符写入字节数据,用于编写原始字节流,比如图像数据。
 **/
public class FileOutputStream extends OutputStream {
    private final FileDescriptor fd;
    private final boolean append; //追加文件内容的标识
    private FileChannel channel;
    private final String path;

    private final Object closeLock = new Object();
    private volatile boolean closed = false;
    
    -------------------------------构造方法---------------------------------
    // 传入文件路径,默认写入时不追加内容
    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);}
    // 传入文件对象File,默认写入时不追加内容
    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();
        }
        if (file.isInvalid()) {
            throw new FileNotFoundException("Invalid file path");
        }
        this.fd = new FileDescriptor();
        fd.attach(this);
        this.append = append;
        this.path = name;
        open(name, append); //会调用本地的open0()
    }
    // 传入FileDescriptor,默认写入时不追加内容
    public FileOutputStream(FileDescriptor fdObj) {...}
    
    -------------------------------向输出流中写入字节数据---------------------------------
    // 写入指定数量的字节
    public void write(int b) throws IOException {write(b, append);}
    // 写入整个字符数组的字节(默认偏移量和大小)
    public void write(byte b[]) throws IOException {writeBytes(b, 0, b.length, append);}
    // 写入字符数组的一部分字节(带有偏移量和指定大小)
    public void write(byte b[], int off, int len) throws IOException {writeBytes(b, off, len, append);}
    
    -------------------------------关闭输出流---------------------------------
    //finalize():终结方法,参考InputStream说明
    protected void finalize() throws IOException {
        if (fd != null) {
            if (fd == FileDescriptor.out || fd == FileDescriptor.err) {
                flush();
            } else {
                /* if fd is shared, the references in FileDescriptor
                 * will ensure that finalizer is only called when
                 * safe to do so. All references using the fd have
                 * become unreachable. We can call close()
                 */
                close();
            }
        }
    }
    // 关闭流,释放资源
    public void close() throws IOException {
        synchronized (closeLock) {...} //线程同步操作,关闭标识
        if (channel != null) {... } //关闭文件通道    
        fd.closeAll... //关闭所有文件
    }
}

小结一下:

  • 通过构造方法创建一个向输出流中写入字节数据的对象,参数可以是文件路径+是否追加文件内容、文件对象File+是否追加文件内容,文件描述符对象FileDescriptor。
  • FileOutputStream 提供了多个写入字节数据的write()方法,还有关闭流并释放资源的close()方法。

实战操作:(向目标文件写入字节数据)

// FileOutputStream方式没有使用缓冲区,效率较低,实际工作中不建议使用
public class Test {
    public static void main(String[] args) throws IOException {
        File file = new File("D:/targetTest.txt");
        write(file);
    }

    public static void write(File file) throws IOException {
        OutputStream os = new FileOutputStream(file, true);
        // 要写入的字符串
        String string = "五一快乐啊!!!";
        // 写入文件
        os.write(string.getBytes());
        // 关闭流
        os.close();
    }
}

二、文件字符流

FileReader和FileWriter源码也很简单,就是通过super()调用父类的构造器,源码分析如下:

public class FileReader extends InputStreamReader {
    // 通过super()调用父类InputStreamReader构造方法
    public FileReader(String fileName) throws FileNotFoundException {super(new FileInputStream(fileName));}
    public FileReader(File file) throws FileNotFoundException {super(new FileInputStream(file));}
    public FileReader(FileDescriptor fd) {super(new FileInputStream(fd));}
}
public class FileWriter extends OutputStreamWriter {
    // 通过super()调用父类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));}
}

小结一下:

  • FileReader 和 FileWriter 是面向字符数据操作的流,源码中通过继承各自对应的转换流,让面向字符数据的操作可以适配成面向字节数据的操作,InputStreamReader/OutputStreamWriter 则是将字节操作转换成字符操作的桥梁 ,这是设计模式中的适配器设计模式。 
  • FileReader 只是通过父类 InputStreamReader 中参数为 InputStream 的一种构造方法实现的,而 InputStreamReader 还提供了读取字符read()方法,关闭流close()方法,也可以使用转换流对字符数据进行操作。FileWriter同理。
  • 字符输出流相比于字节输出流,可以通过append()方法进行追加文本内容。

实战操作:(实现文件复制)

// 把D盘中的Stream.txt复制到C盘的根目录下
public static void main(String[] args) {
    File filesrc = new File("D:\\iotest2\\Stream.txt");
    File filedest = new File("C:\\"+"copy.txt");        
    try {
        Reader in = new FileReader(filesrc);        
        Writer out = new FileWriter(filedest);            
        char[]cs = new char[1024];            
        int len = -1;//实际的位置
        while((len = in.read(cs,0,cs.length)) != -1){
            out.write(cs,0,len);
        }
        out.close();
        in.close();
        }  catch (IOException e) {
            e.printStackTrace();
    }
}

三、转换流

转换流指的是InputStreamReader和OutputStreamWriter,在字节转字符操作起到了桥梁的作用。InputStreamReader父类是Reader ,OutputStreamWriter父类是Writer。

1、字符输入流 InputStreamReader

InputStreamReader 源码分析如下:

public class InputStreamReader extends Reader {
    private final StreamDecoder sd; //流的解码器,实现字节转字符操作
    
    // 实现类FileReader调用的构造器,默认字符集名称为null
    public InputStreamReader(InputStream in) {
        super(in); // 调用的是父类Reader的Reader(Object lock)构造方法
        try {
            sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
        } catch (UnsupportedEncodingException e) {
            // The default encoding should always be available
            throw new Error(e);
        }
    }
    // 含有字符集的构造方法
    public InputStreamReader(InputStream in, String charsetName) throws UnsupportedEncodingException{...}
    public InputStreamReader(InputStream in, Charset cs) {...}
    public InputStreamReader(InputStream in, CharsetDecoder dec) {...}
    
    // 读取单个字符
    public int read() throws IOException {return sd.read();}
    // 从输入流中读取指定偏移量和大小的字节数据到字节数组中
    public int read(char cbuf[], int offset, int length) throws IOException {return sd.read(cbuf, offset, length);}
    // 测试输入流是否可读
    public boolean ready() throws IOException {return sd.ready();}
    
    // 关闭流,释放资源
    public void close() throws IOException {sd.close();}
}

2、字符输出流 OutputStreamWriter

OutputStreamWriter源码分析如下:

public class OutputStreamWriter extends Writer {
    private final StreamEncoder se; //流的编码,实现字节转字符操作
    
    // 实现类FileWriter调用的构造器,默认字符集名称为null
    public OutputStreamWriter(OutputStream out) {
        super(out);
        try {
            se = StreamEncoder.forOutputStreamWriter(out, this, (String)null);
        } catch (UnsupportedEncodingException e) {
            throw new Error(e);
        }
    }
    // 含有字符集的构造方法
    public OutputStreamWriter(OutputStream out, String charsetName) throws UnsupportedEncodingException{...}
    public OutputStreamWriter(OutputStream out, Charset cs) throws UnsupportedEncodingException{...}
    public OutputStreamWriter(OutputStream out, CharsetEncoder enc) throws UnsupportedEncodingException{...}
    
    // 写入指定大小的字符
    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();}
}

实战操作:(IDEA控制台输入一行文字,保存到文件中,这里使用BufferedReader、BuffereWrite加强读写功能)

public static void main(String[] args) throws IOException {
    // 标准输入--->内存
    InputStream is = System.in;
    //System.in已经是描述好的标准输入流对象,转换
    InputStreamReader ir = new InputStreamReader(is, "utf-8");

    // 加入缓冲流
    BufferedReader bfr = new BufferedReader(ir);
    System.out.println("请输入一行文字:");
    String msg = bfr.readLine();

    // 保存到文件中,创建原始流
    FileOutputStream fos = new FileOutputStream("E:\\save.txt");

    // 转换成字符输出流
    OutputStreamWriter ops = new OutputStreamWriter(fos, "utf-8");

    // 加入缓冲流
    BufferedWriter bfw = new BufferedWriter(ops);
    bfw.write(msg);

    bfr.close();
    bfw.close();
}

总结:

这里从源码的角度,分析了面向文件操作的字节流和字符流,以及将字节转换成字符操作的转换流,并进行了相关的实战操作。从使用角度看,面对数据量较大的读写操作时,不推荐使用文件字节流,而是使用文件字符流或转换流,同时为了加强读写功能,一般还会通过缓冲流进行包装,下一篇将介绍缓冲流。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值