字节流

经过昨天的IO流概念的了解,我们知道他可以字符流和字节流,所以今天开始我们先来学习字节流。

而字节流呢?又可以分为字节输出流和字节输入流。今天我们准备对字节输出流进行一个详细的了解吧!

文件世界里,一切皆为字节

我们要知道,一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。

字节输出流的父类(OutputStream)

OutputStream ,是所有字节输出流基类,是表示字节输出流的所有类的超类。主要作用是将指定的字节信息写出到目的地。它对整个体系的字节输出流制定了一些规范和方法。

1. 先来看看他的体系结构

  • FileOutputStream:字节文件输出流是用于将数据写入到File,从程序中写入到其他位置。
  • FilterOutputStream :装饰者模式中处于装饰者,具体的装饰者都要继承它,所以在该类的子类下都是用来装饰别的流的,也就是处理类。
  • ObjectOutputStream :也是装饰类,用于序列化。主要是将Java对象的原始数据类型写出到文件,实现对象的持久存储。
  • BufferedInputStream:缓冲流,是FilterOutputStream 装饰流的加强,提高读写的效率。
  • PrintStream:是FilterOutputStream 装饰流的加强。能够方便地打印各种数据类型的值,是一种便捷的输出方式。
  • DataOutputStream:数据输出流,它是用来装饰其它输出流。

2. OutputStream中的方法

后续继承的子类都有这些方法,基本字节输出流都需要write()方法进行写入文件中,使用close()方法关闭资源。

3. FileOutputStream

1. 构造方法

  • public FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件。
  • public FileOutputStream(File file, boolean append):跟上面类似,多了个属性,下面再讲
  • public FileOutputStream(String name): 创建文件输出流以指定的名称写入文件。
  • public FileOutputStream(String name, boolean append):跟上面类似,多了个属性,下面再讲

这是还未创建构造方法前的文件夹信息

public class IOTest {
    public static void main(String[] args) throws FileNotFoundException {
        //public FileOutputStream(File file)
        //使用File对象创建流对象
        File file = new File("e:\\demo\\a.txt");
        FileOutputStream fos = new FileOutputStream(file);

        //public FileOutputStream(String name)
        // 使用文件名称创建流对象
        FileOutputStream fos2 = new FileOutputStream("E:\\demo\\b.txt");
    }
}

我们中的a.txt本来是没有的,而且b.txt里面是有内容的,让我们执行下代码后,会发生什么吧:

可以看到,控制台没有报错,也没有什么信息。那我们在来看看文件夹信息吧:

注意:当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。

那为什么呢,有文件还会被清空数据呢,那我不想每次创建输出流对象,都要被清空目标文件中的数据,怎么做呢?

  • 我们先看下我们调用的构造方法具体过程吧:

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

    可以发现,最后都会调用了包含了append属性的构造方法,并且append为false。

    而这个append属性表示如果为true表示追加追加数据,false表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了。我先在b.txt加了hello。然后我们在试下文件是否又被清空掉

    public class AppendDemo {
        public static void main(String[] args) throws FileNotFoundException {
            FileOutputStream fos2 = new FileOutputStream("E:\\demo\\b.txt", true);
        }
    }
    
    执行后,可以看到,文件没有被清空数据了。

想一下我们在创建字节输出流对象了做了几件事情,是不是就认为不是直接创建的吗?还是底层有步骤的啦:

  1. 调用系统功能去创建文件。
  2. 创建fos对象。
  3. fos对象指向这个文件。

2. 写入数据

既然我们要创建出字节输出流,不就是为了写入数据吗,不然创建出来有什么用呢?那我们看下怎么用吧:

  1. public void write(int b):每次写一个字节。

    public class WriteTest {
        public static void main(String[] args) throws IOException {
            // 使用文件名称创建输出流对象
            FileOutputStream fos = new FileOutputStream("E:\\demo\\a.txt");
    		
            //public void write(int b)
            fos.write(97);
    
            fos.close();
            //如果在流关闭后再写入数据会怎样。
            //fos.write(98);//Exception in thread "main" java.io.IOException: Stream Closed
        }
    }
    
    执行结果后,查看文件a.txt得到的是:
    a

    有几点所需要注意:

    • 一定要关闭资源:

      为了让流对象变成垃圾,这样就可以被垃圾回收器回收了。

      通知系统去释放跟该文件相关的资源。

    • 为什么写的97,然后写到文件后变成字符a了:

      因为97代表了底层二进制数据,通过这个然后在字符表中找到对应的字符a。最后显示的也是a,而不是97,但是底层数据是97。我们看下完整的ASCII码表:

    • 如果关闭了流资源后,则无法继续对这个流进行操作,否则会报IOException异常。

    • 现在我们进行IO流操作,除了可能报错会打印在控制台上,其他都是直接在文件中进行操作,结果是直接显示在文件里,如果要看结果,打开文件就看到。

  2. public void write(byte[] b, int off, int len):每次写出从off索引开始,len个字节。相当于写入一个字节数组的一部分。

    public class WriteTest2 {
        public static void main(String[] args) throws IOException {
            // 使用文件名称创建输出流对象
            FileOutputStream fos = new FileOutputStream("E:\\demo\\a.txt");
    
            //public void write(byte[] b)
            byte[] b = {97, 98, 99, 100, 101};
            fos.write(b);
    
            fos.close();
        }
    }
    
    执行结果后,查看文件a.txt得到的是:
    abcde
  3. public void write(byte[] b):每次写一个字节数组。

    public class WriteTest3 {
        public static void main(String[] args) throws IOException {
            // 使用文件名称创建输出流对象
            FileOutputStream fos = new FileOutputStream("E:\\demo\\a.txt");
    
    
            //public void write(byte[] b, int off, int len)
            byte[] b = {97, 98, 99, 100, 101};
            fos.write(b,0,3);
    
            fos.close();
        }
    }
    
    执行结果后,查看文件a.txt得到的是:
    abc

3. 实现换行

我们以上操作好像都是写在一行上,那我们可不可以实现换行呢?

那我先看看怎样换行符是什么吧?

  • 回车符\r和换行符\n
    • 回车符:回到一行的开头(return)。
    • 换行符:下一行(newline)。
  • 系统中的换行:
    • Windows系统里,每行结尾是 回车+换行 ,即\r\n
    • Unix系统里,每行结尾只有 换行 ,即\n
    • Mac系统里,每行结尾是 回车 ,即\r。从 Mac OS X开始与Linux统一。

而我们现在默认使用的应该都是Windows系统,那我们需要加入的是\r\n。那我们代码演示下看看吧:

执行结果后,查看文件a.txt得到的是:
epublic class WriteTest4 {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建输出流对象
        FileOutputStream fos = new FileOutputStream("E:\\demo\\a.txt");

        //实现写入每一个字符后都换行
        byte[] b = {97, 98, 99, 100, 101};
        for (byte bs : b) {
            fos.write(bs);
            // 写出一个换行, 换行符号转成数组写出
            fos.write("\r\n".getBytes());
        }

        fos.close();
    }
}

执行结果后,查看文件a.txt得到的是:
a
b
c
d
e

是不是觉得每次都要查看文件,然后确认是否添加成功,很麻烦呢。接下来我们就学习字节流中的输入流,学习如何读取文件。

总结

相信各位看官都对IO流中字节输出流有了一定了解,期待等待下一章的字节输入流教学吧!

当然还有很多流等着下次一起看吧!欢迎期待下一章的到来!

学到这里,今天的世界打烊了,晚安!虽然这篇文章完结了,但是我还在,永不完结。我会努力保持写文章。来日方长,何惧车遥马慢!

感谢各位看到这里!愿你韶华不负,青春无悔!

注: 如果文章有任何错误和建议,请各位大佬尽情留言!如果这篇文章对你也有所帮助,希望可爱亲切的您给个三连关注下,非常感谢啦!


作者:***爷哪吒
链接:https://juejin.cn/post/6993310593729953829
来源:掘金