Java I/O学习(一) - FileOutputStream

1、创作初衷

使用Java语言做了几年的开发,虽然IO这块不是经常用到,但是偶尔也会使用。在我每一次使用的时候,总是会被IO包下很多的方法给搞晕,总是遇到不知道该用谁、不知道谁该包裹谁、不知道谁的效率好、不知道谁对中文支持好等等这些情况…

说到头来,还是自己的基础不扎实。所以打算去把它们的源码都看看,然后进行总结并纪录在此。

2、FileOutputStream 使用流程

用FileOutputStream 将内容写入文件流程如下:

  1. 打开文件:用File对象打开文件
  2. 打开输出流:实例化FileOutputStream对象
  3. 写入数据:往流中写入数据
  4. 关闭流:关闭输出流

3、FileOutputStream 源码分析

3.1、FileOutputStream的构造方法

FileOutputStream提供了4个常用构造方法,用于实例化FileOutputStream对象,这4个不同的构造方法,对应着不同的使用场景。

3.1.1、构造函数1

 public FileOutputStream(String name) throws FileNotFoundException {
    	this(name != null ? new File(name) : null, false);
 }

该构造方法允许直接传入文件路径,参数 name 表示文件路径。
如果 name 参数指定的文件不存在,则会抛出 fileNotFoundException 异常。

查看该构造方法的代码,可以看见其内部使用了 name 参数创建了File对象打开文件。

后面这个 false参数表示的是:是否在原来内容的基础上追加写入内容,默认为false,不追加,也就是会覆盖原有的内容。
这一点需要注意,请小心使用。

3.1.1.1、使用示例
public static void main(String[] args) {
        try {
            
            FileOutputStream fos = new FileOutputStream("F:\\FilesExample\\A.txt");
            
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

3.1.2、构造函数2

public FileOutputStream(String name, boolean append) throws FileNotFoundException{
        this(name != null ? new File(name) : null, append);
    }

该构造函数和第一个构造函数极为相似,唯一的区别是多了一个参数表明是否追加内容,也就是说现在是否追加内容可以通过这个构造函数来手动控制。

append参数为true时,数据从文件尾部写入,append参数为false时,数据覆盖原文件。

3.1.2.1、使用示例
public static void main(String[] args) {
        try {

            FileOutputStream fos = new FileOutputStream("F:\\FilesExample\\A.txt", true);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

3.1.3、构造函数3

public FileOutputStream(File file) throws FileNotFoundException {
        this(file, false);
    }

该构造函数传入一个File对象参数,使用File对象打开本地文件,从文件读取数据。
与构造函数1、2相比,需要自己创建好File对象后,传入FileOutputStream构造器。构造方法实现中的第二个参数也是表示是否追加内容的意思。

3.1.3.1、使用示例
public static void main(String[] args) {
        try {
            
            File myFile = new File("F:\\FilesExample\\A.txt");
            FileOutputStream fos = new FileOutputStream(myFile);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

3.1.4、构造函数4

public FileOutputStream(File file, boolean append)
        throws FileNotFoundException
    {
	    // 先对 file 参数进行 null 处理
        String name = (file != null ? file.getPath() : null);
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkWrite(name);
        }
        // 判断是否为 null
        if (name == null) {
            throw new NullPointerException();
        }
        // 判断file对象是否合法
        if (file.isInvalid()) {
            throw new FileNotFoundException("Invalid file path");
        }
        this.fd = new FileDescriptor();
        this.append = append;
        this.path = name;
        fd.incrementAndGetUseCount();
        // 打开具有指定名称的文件进行覆盖或追加。
        open(name, append);
    }

其实上面三个构造器最终都会调用这个构造函数。

3.1.4.1、使用示例
public static void main(String[] args) {
        try {

            File myFile = new File("F:\\FilesExample\\A.txt");
            FileOutputStream fos = new FileOutputStream(myFile, true);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

4、FileOutputStream的写方法

FileOutputStream 类提供了三个文件写入方法(wirte),可以单独写一个字节到文件,也可以写一个byte数组到文件,也可以取byte数组的部分数据写入到文件。

wirte方法是被重载了三次。

4.1、wirte方法一

public void write(int b) throws IOException {
        Object traceContext = IoTrace.fileWriteBegin(path);
        int bytesWritten = 0;
        try {
            write(b, append);
            bytesWritten = 1;
        } finally {
            IoTrace.fileWriteEnd(traceContext, bytesWritten);
        }
    }

将指定的字节写入此文件输出流,int类型的参数 b 就是指定的字节,可以看到该方法其实还去调用了一个write(b, append)方法,这是一个 native 方法,append参数默认为false,也就是不进行追加内容,即覆盖。
一般简单的源码分析到native这一步就可以了,不必追根溯源。

4.1.1、使用示例

public static void main(String[] args) {
        FileOper fileOper = new FileOper();
        String filePath = "F:\\FilesExample\\A.txt";
        // 创建文件
        fileOper.createFile(filePath);

        // 创建File对象
        File myFile = new File(filePath);

        // 要输出的内容
        String str = "This is FileOutputStreamTest Content";

        // 使用 try-with-resource 打开资源
        try (FileOutputStream fos = new FileOutputStream(myFile)){
            // 遍历输出内容
            for (int i = 0; i < str.length(); i++) {
                int b = str.charAt(i);
                fos.write(b);
                fos.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

FileOper文件操作类,是我写的一个文件操作公共类,具体内容会在文章最后贴出来。在这里不用在意它。

我们需要在意的是,先是创建了一个File对象,然后利用这个File对象创建了一个 FileOutputStream 字节输出流。然后再使用write(b)这个方法输出。

4.2、write方法2

public void write(byte b[]) throws IOException {
        Object traceContext = IoTrace.fileWriteBegin(path);
        int bytesWritten = 0;
        try {
            writeBytes(b, 0, b.length, append);
            bytesWritten = b.length;
        } finally {
            IoTrace.fileWriteEnd(traceContext, bytesWritten);
        }
    }

将指定的字节数组写入到这个文件输出流,byte数组类型的参数 b 就是这个指定的数组。
这个方法它调用了一个 native 的writeBytes(b, 0, b.length, append)方法,这几个参数什么意思呢?

  • 参数 b:就是要写入到这个文件输出流的字节数组。
  • 参数 0:偏移量。到底什么是偏移量呢?下面解释。
  • 参数 b.length:要写入的字节长度,也就是要写入多少个字节。
  • 参数 append:和上面方法含义一样,是否追加内容,true为追加,false为不追加,默认为false。

4.2.1、偏移量

Java IO流中的偏移量是指接收数据的数组(或叫缓冲区)的偏移量,并不是数据流的偏移量。

详细解释:详解Java IO流中偏移量

这是它调用的 native 方法
private native void writeBytes(byte b[], int off, int len, boolean append)
throws IOException;

这三个参数联合起来用一句话来说就是:
将指定字节数组从偏移量off 开始的 len 个字节写入这个输出流。

4.2.2、使用示例

public static void main(String[] args) {
        FileOper fileOper = new FileOper();
        String filePath = "F:\\FilesExample\\A.txt";
        // 创建文件
        fileOper.createFile(filePath);

        // 创建File对象
        File myFile = new File(filePath);

        // 要输出的内容
        String str = "This is FileOutputStreamTest Content";

        // 使用 try-with-resource 打开资源
        try (FileOutputStream fos = new FileOutputStream(myFile)){
            // 输出内容
            byte[] bytes = str.getBytes();
            fos.write(bytes);
            fos.flush();
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

try块外面的内容和上面完全一致,唯一的区别就是在使用wirte方法输出的时候,这里传入的是一个byte数组。

4.3、wirte方法3

public void write(byte b[], int off, int len) throws IOException {
        Object traceContext = IoTrace.fileWriteBegin(path);
        int bytesWritten = 0;
        try {
            writeBytes(b, off, len, append);
            bytesWritten = len;
        } finally {
            IoTrace.fileWriteEnd(traceContext, bytesWritten);
        }
    }

这个方法和上面的两个方法都是大同小异。
可以说是方法1最简单,方法3最灵活,至于里面的参数呢,是和上面方法的参数一样的。已经详细解释过了,就不重复解释了。

4.3.1、使用示例

public static void main(String[] args) {
        FileOper fileOper = new FileOper();
        String filePath = "F:\\FilesExample\\A.txt";
        // 创建文件
        fileOper.createFile(filePath);

        // 创建File对象
        File myFile = new File(filePath);

        // 要输出的内容
        String str = "This is FileOutputStreamTest Content";

        // 使用 try-with-resource 打开资源
        try (FileOutputStream fos = new FileOutputStream(myFile)){
            // 输出内容
            byte[] bytes = str.getBytes();
            fos.write(bytes, 0, bytes.length);
            fos.flush();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

唯一的区别也就是 wirte方法处。

5、总结

使用FileOutputStream流可以输出字节数据到目标文件,FileOutputStream提供了单字节写入和byte数组写入两种方式。建议使用byte数组写入,将待写入的数据存储到一个byte数组中,然后再写入文件。当写入的文件已经存在时,需要指明写入方式是覆盖还是追加。

6、最后附上FileOper的内容

/**
 *  文件操作类
 *  Created by 6092002538 on 2019/7/15.
 */
public class FileOper {

    /**
     * 创建文件夹
     * @param dirPath 文件夹路径,也就是这个文件夹会创建在什么地方
     */
    public void createDir(String dirPath){
        File myDir = new File(dirPath);
        if (!myDir.exists()){
            myDir.mkdirs();
        }
    }

    /**
     * 在指定位置创建文件
     * @param filePath 指定的文件路径,也就是这个文件会被创建在什么地方
     */
    public void createFile(String filePath){

        // 创建File对象
        File myFile = new File(filePath);
        // 创建文件夹
        createDir(myFile.getParent());

        try {
            // 如果文件不存在则创建
            if (!myFile.exists()){
                myFile.createNewFile();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

技 术 无 他, 唯 有 熟 尔。
知 其 然, 也 知 其 所 以 然。
踏 实 一 些, 不 要 着 急, 你 想 要 的 岁 月 都 会 给 你。


  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值