Java基础 IO

IO流

IO流

  1. 什么是IO流?
    存储和读取数据的解决方案

    I:input

    O:output

    流:像水流一样传输数据

  2. IO流的作用?

    用于读写数据(本地文件,网络)

  3. IO流按照流向可以分类哪两种流?

    输出流:程序 -> 文件

    输入流:文件 -> 程序

  4. IO流按照操作文件的类型可以分类哪两种流?

    字节流:可以操作所有类型的文件

    字符流:只能操作纯文本文件

  5. 什么是纯文本文件?

    用windows系统自带的记事本打开并且能读懂的文件

    txt文件,md文件,xml文件,lrc文件等

    (word, excel不是纯文本文件)

在这里插入图片描述在这里插入图片描述

字节流

FileOutputStream

  • 字节输出流FileOutputStream:操作本地文件的字节输出流,可以把程序中的数据写到本地文件中
    书写步骤:

    • 创建字节输出流对象
    • 写数据
    • 创建资源

    例子:写出一段文字到本地文件中。(暂时不写中文)

    public static void main(String[] args) throws IOException {
          
          
            /*
            * 演示:字节输出流FileOutputStream
            * 实现需求:写出一段文字到本地文件中。(暂时不写中文)
            *
            * 实现步骤:
            *       创建对象
            *       写出数据
            *       释放资源
            * */
    
            //1.创建对象
            //写出 输出流 OutputStream
            //本地文件    File
            // 注:FileOutputStream 要抛出异常IOException 
            FileOutputStream fos = new FileOutputStream("myio\\a.txt");
            //2.写出数据
            fos.write(97); //a
            //3.释放资源
            fos.close();
        }
    
  • 字节输出流的细节:

    1. 创建字节输出流对象
      细节1:参数是字符串表示的路径或者是File对象都是可以的
      细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的。
      细节3:如果文件已经存在,则会清空文件
    2. 写数据
      细节:write方法的参数是整数,但是实际上写到本地文件中的是整数在ASCII上对应的字符
      如果真的要写数字,就要把每个数字位看成字符,写数字的ASCII码
      ‘9’:57
      ‘7’:55
    3. 释放资源
      每次使用完流之后都要释放资源,否则建立连接的通道不会关闭,占用内存
  • FileOutputStream写数据的3种方式:
    在这里插入图片描述(注意第三种方式的参数可能和你想的不一样,第二个参数是起始索引,第三个参数是长度)

    public static void main(String[] args) throws IOException {
          
          
            /*
               void write(int b)                       一次写一个字节数据
               void write(byte[] b)                    一次写一个字节数组数据
               void write(byte[] b, int off, int len)  一次写一个字节数组的部分数据
               参数一:
                    数组
               参数二:
                    起始索引  0
               参数三:
                    个数      3
            */
    
    
            //1.创建对象
            FileOutputStream fos = new FileOutputStream("myio\\a.txt");
            //2.写出数据
            //fos.write(97); // a
            //fos.write(98); // b
            byte[] bytes = {
          
          97, 98, 99, 100, 101};
            /* fos.write(bytes);*/
    
            fos.write(bytes,1,2);// b c
            //3.释放资源
            fos.close();
        }
    
  • 两个问题:如何换行?如何续写?

    • 换行写:
      再次写出一个换行符就可以了

            windows: \r\n
            Linux:    \n
            Mac:      \r
      
      • 细节
        在windows操作系统当中,java对回车换行进行了优化。
        虽然完整的是\r\n,但是我们写其中一个\r或者\n,
        java也可以实现换行,因为java在底层会补全。
      • 建议:
        不要省略,还是写全了。
    • 续写:
      如果想要续写,打开续写开关即可
      开关位置:创建对象的第二个参数
      默认false:表示关闭续写,此时创建对象会清空文件
      手动传递true:表示打开续写,此时创建对象不会清空文件

    public static void main(String[] args) throws IOException {
          
          
            /*
                换行写:
                    再次写出一个换行符就可以了
                    windows: \r\n
                    Linux:    \n
                    Mac:      \r
                细节:
                    在windows操作系统当中,java对回车换行进行了优化。
                    虽然完整的是\r\n,但是我们写其中一个\r或者\n,
                    java也可以实现换行,因为java在底层会补全。
                建议:
                    不要省略,还是写全了。
    
    
                续写:
                    如果想要续写,打开续写开关即可
                    开关位置:创建对象的第二个参数
                    默认false:表示关闭续写,此时创建对象会清空文件
                    手动传递true:表示打开续写,此时创建对象不会清空文件
    
            */
    
            //1.创建对象
            FileOutputStream fos = new FileOutputStream("myio\\a.txt",true);
            //2.写出数据
            String str = "kankelaoyezuishuai";
            byte[] bytes1 = str.getBytes();
            fos.write(bytes1);
    
            //再次写出一个换行符就可以了
            String wrap = "\r\n";
            byte[] bytes2 = wrap.getBytes();
            fos.write(bytes2);
    
            String str2 = "666";
            byte[] bytes3 = str2.getBytes();
            fos.write(bytes3);
    
            //3.释放资源
            fos.close();
        }
    

FileInputStream

  • 字节输出流FileInputStream:操作本地文件的字节输入流,可以把本地文件中的数据读到程序中
    书写步骤:
    • 创建字节输出流对象
      • 细节:如果文件不存在,直接报错
    • 读数据
      • 细节1:一次读一个字节,读出来的数据是在ASCII上对应的数字,如果想显示原内容,可以用(char)强转
      • 细节2:读到文件末尾了,read方法返回-1.
    • 创建资源
      • 每次使用完流必须释放资源

例子:读文本中的数据

public static void main(String[] args) throws IOException {
    
    
        /*
         * 演示:字节输入流FileInputStream
         * 实现需求:读取文件中的数据。(暂时不写中文)
         *
         * 实现步骤:
         *       创建对象
         *       读取数据
         *       释放资源
         * */

        //1.创建对象
        FileInputStream fis = new FileInputStream("myio\\a.txt");
        //2.读取数据
        int b1 = fis.read();
        System.out.println((char)b1);
        int b2 = fis.read();
        System.out.println((char)b2);
        int b3 = fis.read();
        System.out.println((char)b3);
        int b4 = fis.read();
        System.out.println((char)b4);
        int b5 = fis.read();
        System.out.println((char)b5);
        int b6 = fis.read();
        System.out.println(b6);//-1
        //3.释放资源
        fis.close();
    }
  • FileInputStream循环读取

    public static void main(String[] args) throws IOException {
          
          
             /*
                 字节输入流循环读取
             */
    
           //1.创建对象
            FileInputStream fis = new FileInputStream("myio\\a.txt");
            //2.循环读取
            int b;
            while ((b = fis.read()) != -1) {
          
          
                System.out.println((char) b);
            }
            //3.释放资源
            fis.close();
       }
    

    问题:不定义变量b可不可以?

    		/*
            * read :表示读取数据,而且是读取一个数据就移动一次指针
            *
            * */
            FileInputStream fis = new FileInputStream("myio\\a.txt");
            //2.循环读取
            while ((fis.read()) != -1) {
          
          
                System.out.println(fis.read());//98  100  -1
            }
            //3.释放资源
            fis.close();
    

    答:不可以。因为 read读取数据,读取一个数据就移动一次指针,因此调用两次fis.read()后指针移动了两次,不能完整输出数据

  • 文件拷贝
    细节:先打开的后关闭
    (以下代码边读边写,一个字节一个字节读写,效率太低,只能拷贝小文件)

    public static void main(String[] args) throws IOException {
          
          
            /*
            *   练习:
            *       文件拷贝
            *       把D:\itheima\movie.mp4拷贝到当前模块下。
            *
            *   注意:
            *       选择一个比较小的文件,不要太大。大文件拷贝我们下一个视频会说。
            *
            *
            *
            *   课堂练习:
            *       要求统计一下拷贝时间,单位毫秒
            * */
    
            long start = System.currentTimeMillis();
    
            //1.创建对象
            FileInputStream fis = new FileInputStream("D:\\itheima\\movie.mp4");
            FileOutputStream fos = new FileOutputStream("myio\\copy.mp4");
            //2.拷贝
            //核心思想:边读边写
            int b;
            while((b = fis.read()) != -1){
          
          
                fos.write(b);
            }
            //3.释放资源
            //规则:先开的最后关闭
            fos.close();
            fis.close();
    
            long end = System.currentTimeMillis();
    
            System.out.println(end - start);
    
        }
    

    解决方式: public int read(byte[] buffer) 一次读一个字节数组数据,返回值是读取的字节长度
    如果读到最后不够字节数组的长度了,那么只会读取剩下的字节,返回的是剩下的字节长度。此时数组里面还存在上次读取的字节,这样最后一次就会多拷贝出来一些东西。因此用String str1 = new String(bytes,0,len1)、fos.write(bytes, 0, len)确保最后一次读取的是正确的字节数。

    public static void main(String[] args) throws IOException {
          
          
          /*
              public int read(byte[] buffer)      一次读一个字节数组数据
          */
    
            //1.创建对象
            FileInputStream fis = new FileInputStream("myio\\a.txt");
            //2.读取数据
            byte[] bytes = new byte[2];
            //一次读取多个字节数据,具体读多少,跟数组的长度有关
            //返回值:本次读取到了多少个字节数据
            int len1 = fis.read(bytes);
            System.out.println(len1);//2
            String str1 = new String(bytes,0,len1);
            System.out.println(str1);
    
            int len2 = fis.read(bytes);
            System.out.println(len2);//2
            String str2 = new String(bytes,0,len2);
            System.out.println(str2);
    
            int len3 = fis.read(bytes);
            System.out.println(len3);// 1
            String str3 = new String(bytes,0,len3);
            System.out.println(str3);// ed
    
            //3.释放资源
            fis.close();
        }
    

    拷贝大文件:

    public static void main(String[] args) throws IOException {
          
          
            /*
             *   练习:
             *       文件拷贝
             *       把D:\itheima\movie.mp4 (16.8 MB) 拷贝到当前模块下。
             *
             * */
    
            long start = System.currentTimeMillis();
            //1.创建对象
            FileInputStream fis = new FileInputStream("D:\\itheima\\movie.mp4");
            FileOutputStream fos = new FileOutputStream("myio\\copy.mp4");
            //2.拷贝
            int len;
            byte[] bytes = new byte[1024 * 1024 * 5];
            while((len = fis.read(bytes)) != -1){
          
          
                fos.write(bytes,0,len);
            }
            //3.释放资源
            fos.close();
            fis.close();
    
            long end = System.currentTimeMillis();
            System.out.println(end - start);
        }
    
  • IO流try…catch异常处理注意事项(了解):释放资源语句要放在finally里确保一定会被执行。
    无论try里有没有异常,finally里的代码一定会被执行,除非虚拟机停止

public static void main(String[] args) {
    
    
       /*
       *    利用try...catch...finally捕获拷贝文件中代码出现的异常
       */
        //1.创建对象
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
    
    
            fis = new FileInputStream("D:\\itheima\\movie.mp4");
            fos = new FileOutputStream("myio\\copy.mp4");
            //2.拷贝
            int len;
            byte[] bytes = new byte[1024 * 1024 * 5];
            while((len = fis.read(bytes)) != -1){
    
    
                fos.write(bytes,0,len);
            }
        } catch (IOException e) {
    
    
            //e.printStackTrace();
        } finally {
    
    
            //3.释放资源
            if(fos != null){
    
    
                try {
    
    
                    fos.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }
            //如果创建fis/fos的路径不存在时就不会创建字节流,这样fis和fos就还是null
            //如果不加以判断就会出现空指针异常的错误,所以要加个非空判断
            if(fis != null){
    
    
                try {
    
    
                //fis.close()也会有异常出现,所以在finally里又嵌套了一个try catch捕获异常
                    fis.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
    }

上述代码的简化:AutoCloseable,不需要写finally,自动释放
JDK7:实现了AutoCloseable的类才能在try()中创建对象
JDK9:JDK7不好阅读,所以把创建流对象放外面了
在这里插入图片描述

public static void main(String[] args) {
    
    
        /*
         *
         *    JDK7:IO流中捕获异常的写法
         *
         *      try后面的小括号中写创建对象的代码,
         *          注意:只有实现了AutoCloseable接口的类,才能在小括号中创建对象。
         *     try(){
         *
         *     }catch(){
         *
         *     }
         *
         * */


        try (FileInputStream fis = new FileInputStream("D:\\itheima\\movie.mp4");
             FileOutputStream fos = new FileOutputStream("myio\\copy.mp4")) {
    
    
            //2.拷贝
            int len;
            byte[] bytes = new byte[1024 * 1024 * 5];
            while ((len = fis.read(bytes)) != -1) {
    
    
                fos.write(bytes, 0, len);
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }


    }

字符集

计算机存储规则:任意数据都是以二进制形式存储的
字节:计算机中最小的存储单位,1字节(byte)=8比特(bit)
存储英文一个字节就够了(ASCII码只有128个)

ASCII码存储规则

在这里插入图片描述在这里插入图片描述

咱汉字怎么存?

1、GB2312字符集,1980年发布,1981年5月1日实施的简体中文汉字编码国家标准。收录7445个图形字符,其中包括6763个简体汉字

2、BIG5字符集:台湾地区繁体中文标准字符集,共收录13053个中文字,1984年实施。

3、GBK字符集,2000年3月17日发布,收录21003个汉字。

包含国家标准GB13000-1中的全部中日韩汉字,和BIG5编码中的所有汉字。

windows系统默认使用的就是GBK。系统显示:ANSI

4、Unicode字符集:国际标准字符集,它将世界各种语言的每个字符定义一个唯一的编码,以满足跨语言、跨平台的文本信息转换。

简体中文版Windows用GBK字符集。GBK字符集完全兼容ASCII字符集。

GBK存储规则

在这里插入图片描述
在这里插入图片描述制定上述规则的原因:
规则1:2个字节是2^16=65535,能容纳所有汉字。一个字节不够用,三个字节浪费,两个字节刚刚好。
规则2:最高位是1还是0用于区分中文还是英文。
如:下面三个字节很容易看出来是一个一个汉字和一个英文
10111010 10111010 01100001

在这里插入图片描述

总结:
1.在计算机中,任意数据都是以二进制的形式来存储的
2.计算机中最小的存储单元是一个字节
3.ASCII字符集中,一个英文占一个字节
4.简体中文版Windows,默认使用GBK字符集
5. GBK字符集完全兼容ASCII字符集
一个英文占一个字节,二进制第一位是0
一个中文占两个字节,二进制高位字节的第一位是1

1990年,国际组织研发了Unicode编码来同一各国编码。

  • Unicode编码
    UTF-16:16个bit(2字节)存储(浪费空间)
    UTF-32:32个bit(4字节)存储(浪费空间)
    UTF-8:1~4个字节保存。不同的语言用不同字节数保存。在UTF-8编码中,英文占1个字节,中文占3个字节。中文第一个字节的首位是1,英文是0。
    在这里插入图片描述
    在这里插入图片描述
    问:1、UTF-8是一个字符集吗?
    不是,UTF-8是Unicode字符集的一种编码方式。
    2、以下Unicode字符集UTF-8编码规则,有几个中文几个英文?
    01001010 01100001 01110110 01100001 4个英文
    01001010 01001010 11100110 11001000 11100001 2个英文1个中文

乱码

- 乱码出现的原因:
读取数据时未读完整个汉字
编码和解码时的方式不统一
- 如何避免乱码:
不要用字节流读取文本文件
编码解码时使用同一个码表,同一个编码方式
拷贝为什么不出现乱码:拷贝时目的地的编码方式和数据源的编码方式是一样的,同时读取字节也是完整的,所以不会乱码。

在这里插入图片描述

编解码

编码方法:

String类中的方法 说明
public byte[] getBytes() 使用默认方式进行编码
public byte[] getBytes(String charseName) 使用指定方式进行编码

解码方法:

String类中的方法 说明
String (byte[] bytes) 使用默认方式进行解码
String (byte[] bytes, String charseName) 使用指定方式进行解码
public static void main(String[] args) throws IOException {
    
    
        //编码
        String str1 = "ai你呀";
        byte[] bytes1 = str1.getBytes(); //IDEA默认编码方式是UTF-8
        System.out.println(Arrays.toString(bytes1));//问:byte1的长度是多少?

        byte[] bytes2 = str1.getBytes("GBK");
        System.out.println(Arrays.toString(bytes2)); //问:byte2的长度是多少?

        //解码
        String str2 = new String(bytes1);
        System.out.println(str2);

        String str3 = new String(bytes2, "GBK"); //这里如果编解码方式不一样的话就会产生乱码
        System.out.println(str3);
    }

问题:上述代码中的bytes1 和bytes2的数组长度分别是多少?

字符流

字符流底层就是字节流:字符流=字节流+字符集

特点:
输入流:一次读一个字节,遇到中文时,一读多个字节
输出流:底层会把数据按照指定的编码方式进行编码,变成字节再写到文件中

使用场景:对于纯文本进行读写操作。
在这里插入图片描述

FileReader

  • 操作步骤
    1、创建字符输入流对象
    2、读取数据
    在这里插入图片描述
    细节1:按字节进行读取,遇到中文,一次读多个字节,读取后解码,返回一个整数
    细节2:读到文件末尾了,read方法返回-1

    3、释放资源(也叫关流)

空参read:
字符流的底层也是字节流,默认也是一个字节一个字节的读取的。
如果遇到中文就会一次读取多个,GBK一次读两个字节,UTF-8一次读三个字节

    //read()细节:
    //1.read():默认也是一个字节一个字节的读取的,如果遇到中文就会一次读取多个
    //2.在读取之后,方法的底层还会进行解码并转成十进制。
    //  最终把这个十进制作为返回值
    //  这个十进制的数据也表示在字符集上的数字
    //  	英文:文件里面二进制数据 0110 0001
    //          read方法进行读取,解码并转成十进制97
    //  	中文:文件里面的二进制数据 11100110 10110001 10001001
    //          read方法进行读取,解码并转成十进制27721

    // 我想看到中文汉字,就是把这些十进制数据,再进行强转就可以了
public static void main(String[] args) throws IOException {
    
    
        /*
            第一步:创建对象
            public FileReader(File file)        创建字符输入流关联本地文件
            public FileReader(String pathname)  创建字符输入流关联本地文件

            第二步:读取数据
            public int read()                   读取数据,读到末尾返回-1
            public int read(char[] buffer)      读取多个数据,读到末尾返回-1

            第三步:释放资源
            public void close()                 释放资源/关流
        */

        //1.创建对象并关联本地文件
        FileReader fr = new FileReader("myio\\a.txt");
        //2.读取数据 read()
        //字符流的底层也是字节流,默认也是一个字节一个字节的读取的。
        //如果遇到中文就会一次读取多个,GBK一次读两个字节,UTF-8一次读三个字节

        //read()细节:
        //1.read():默认也是一个字节一个字节的读取的,如果遇到中文就会一次读取多个
        //2.在读取之后,方法的底层还会进行解码并转成十进制。
        //  最终把这个十进制作为返回值
        //  这个十进制的数据也表示在字符集上的数字
        //  英文:文件里面二进制数据 0110 0001
        //          read方法进行读取,解码并转成十进制97
        //  中文:文件里面的二进制数据 11100110 10110001 10001001
        //          read方法进行读取,解码并转成十进制27721

        // 我想看到中文汉字,就是把这些十进制数据,再进行强转就可以了

        int ch;
        while((ch = fr.read()) != -1){
    
    
            System.out.print((char)ch);
        }

        //3.释放资源
        fr.close();
    }

带参read()
与空参read不同,带参read(chars)读取数据,解码,强转三步合并了,把强转之后的字符放到数组当中

public static void main(String[] args) throws IOException {
    
    
        /*
            第一步:创建对象
            public FileReader(File file)        创建字符输入流关联本地文件
            public FileReader(String pathname)  创建字符输入流关联本地文件

            第二步:读取数据
            public int read()                   读取数据,读到末尾返回-1
            public int read(char[] buffer)      读取多个数据,读到末尾返回-1

            第三步:释放资源
            public void close()                 释放资源/关流
        */


        //1.创建对象
        FileReader fr = new FileReader("myio\\a.txt");
        //2.读取数据
        char[] chars = new char[2];
        int len;
        //read(chars):读取数据,解码,强转三步合并了,把强转之后的字符放到数组当中
        //空参的read + 强转类型转换
        while((len = fr.read(chars)) != -1){
    
    
            //把数组中的数据变成字符串再进行打印
            System.out.print(new String(chars,0,len));
        }
        //3.释放资源
        fr.close();
    }

FileWriter

构造方法
在这里插入图片描述
成员方法
在这里插入图片描述

  • 步骤
    1.创建字符输出流对象

    细节1:参数是字符表示的路径或者File对象都是可以的

    细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的

    细节3:如果文件已经存在,则会清空文件,如果不想清空可以打开续写开关

    2.写数据

    细节:如果write方法的参数是整数,但是实际上写到本地文件中的是整数在字符集上对应的字符

    3.释放资源

    细节:每次使用完流之后都要释放资源

public static void main(String[] args) throws IOException {
    
    
          /*
            第一步:创建对象
                public FileWriter(File file)                            创建字符输出流关联本地文件
                public FileWriter(String pathname)                      创建字符输出流关联本地文件
                public FileWriter(File file,  boolean append)           创建字符输出流关联本地文件,续写
                public FileWriter(String pathname,  boolean append)     创建字符输出流关联本地文件,续写

            第二步:读取数据
                void write(int c)                           写出一个字符
                void write(String str)                      写出一个字符串
                void write(String str, int off, int len)    写出一个字符串的一部分
                void write(char[] cbuf)                     写出一个字符数组
                void write(char[] cbuf, int off, int len)   写出字符数组的一部分

            第三步:释放资源
                public void close()                 释放资源/关流


                '我'    25105
        */

        FileWriter fw = new FileWriter("myio\\a.txt",true);

        //fw.write(25105);
        //fw.write("你好威啊???");
        char[] chars = {
    
    'a','b','c','我'};
        fw.write(chars);

        fw.close();
    }

字符流底层原理

FileReader原理
在这里插入图片描述

输入流:

  1. 创建字符输入流对象

    底层:关联文件,并创建缓冲区(长度为8192的字节数组)

  2. 读取数据

    底层:

    • 1 判断缓冲区中是否有数据可以读取

    • 2 缓冲区没有数据:就从文件中获取数据,装到缓冲区中,每次尽可能装满缓冲区,如果文件中也没有数据了,返回 -1

    • 3 缓冲区有数据:就从缓冲区中读取。

      空参的read方法:一次读取一个字节,遇到中文一次读多个字节,把字节解码并转成十进制返回

      有参的read方法:把读取字节,解码,强转三步合并了,强转之后的字符放到数组中

    缓冲区:没读取数据时缓冲区内全为0
    在这里插入图片描述
    读取第一个数据后缓冲区装满数据
    在这里插入图片描述

问题1:如果文件大于8192个字节怎么读取?(文件中有8192个a和1个bcd)
读数据先装满8192字节
在这里插入图片描述
8192字节都读完后,再读第8193个数据时,再把剩余数据都装到缓冲区
在这里插入图片描述

问题2:先读一个之后再写,之后再继续读会怎么样?(见下列代码)
由于写会把文件清空,所以无法读取文件中的内容,但是由于读取一个之后会把8192字节的数据放入缓冲区中,所以再读时并不是从文件中读,而是从缓冲区中读,所以会读到缓冲区中的8192个字节的内容。

public static void main(String[] args) throws IOException {
    
    

        FileReader fr = new FileReader("myio\\b.txt");
        fr.read();//会把文件中的数据放到缓冲区当中

        //清空文件
        FileWriter fw = new FileWriter("myio\\b.txt");

        //请问,如果我再次使用fr进行读取
        //会读取到数据吗?

        //会把缓冲区中的数据全部读取完毕

        //正确答案:
        //但是只能读取缓冲区中的数据,文件中剩余的数据无法再次读取
        int ch;
        while((ch = fr.read()) != -1){
    
    
            System.out.println((char)ch);
        }


        fw.close();
        fr.close();
    }

FileWriter原理
内存中存在缓冲区,以下3种情况会将缓冲区中的数据送入目的地:
1、装满了(写到第8193个字节才会把前8192个字节保存到本地)
2、手动刷新flush:无论缓冲区有多少数据,用flush方法可以直接保存到本地
3、释放资源 close()

flush()和close()
flush():将缓冲区中的数据刷新到本地文件中。刷新之后,还可以继续往文件中写出数据
close():释放资源/关流。断开通道,无法再往文件中写出数据

FileWriter的缓冲区:
在这里插入图片描述
如果你在写FileWriter时忘写了close,就会发现文件里什么也没有或者写的数据不全,因为都写在缓冲区里了,缓冲区里的数据不到8192时只有写关流close()才会把缓冲区里的数据保存到本地中。

代码示例:

public static void main(String[] args) throws IOException {
    
    

        FileWriter fw = new FileWriter("myio\\a.txt");


       fw.write("我的同学各个都很厉害");
       fw.write("说话声音很好听");

       fw.flush();

       fw.write("都是人才");
       fw.write("超爱这里哟");

      fw.close();

       fw.write("B站");
    }
  • 字节流和字符流的使用场景

    • 字节流:拷贝任意型的文件

    • 字符流:

      读纯文本文件中的数据

      往纯文本文件中写出数据

在这里插入图片描述

练习

1、拷贝文件夹

public static void main(String[] args) throws IOException {
    
    
        //拷贝一个文件夹,考虑子文件夹

        //1.创建对象表示数据源
        File src = new File("D:\\aaa\\src");
        //2.创建对象表示目的地
        File dest = new File("D:\\aaa\\dest");

        //3.调用方法开始拷贝
        copydir(src,dest);
    }

    /*
    * 作用:拷贝文件夹
    * 参数一:数据源
    * 参数二:目的地
    *
    * */
    private static void copydir(File src, File dest) throws IOException {
    
    
        dest.mkdirs();
        //递归
        //1.进入数据源
        File[] files = src.listFiles();
        //2.遍历数组
        for (File file : files) {
    
    
            if(file.isFile()){
    
    
                //3.判断文件,拷贝
                FileInputStream fis = new FileInputStream(file);
                FileOutputStream fos = new FileOutputStream(new File(dest,file.getName()));
                byte[] bytes = new byte[1024];
                int len;
                while((len = fis.read(bytes)) != -1){
    
    
                    fos.write(bytes,0,len);
                }
                fos.close();
                fis.close();
            }else {
    
    
                //4.判断文件夹,递归
                copydir(file, new File(dest,file.getName()));
            }
        }
    }

2、文件加密解密
为了保证文件的安全性,就需要对原始文件进行加密存储,再使用的时候再对其进行解密处理。

加密原理:对原始文件中的每一个字节数据进行更改,然后将更改以后的数据存储到新的文件中。

解密原理:读取加密之后的文件,按照加密的规则反向操作,变成原始文件。

public static void main(String[] args) throws IOException {
    
    
        File f1 = new File("D:\\AppData\\Local\\Java\\JAVA-Code\\IdeaProjects\\untitled2\\a.txt");
        File f2 = new File("D:\\AppData\\Local\\Java\\JAVA-Code\\IdeaProjects\\untitled2\\a_en.txt");
        File f3 = new File("D:\\AppData\\Local\\Java\\JAVA-Code\\IdeaProjects\\untitled2\\a_de.txt");
        Encoder(f1, f2);
        Decoder(f2, f3);
    }
    //加密
    public static void Encoder(File src, File dest) throws IOException {
    
    
        dest.createNewFile();
        FileInputStream fis = new FileInputStream(src);
        FileOutputStream fos = new FileOutputStream(dest);
        int len;
        while((len = fis.read()) != -1){
    
    
            fos.write(len + 1);
        }
        fos.close();
        fis.close();
    }
    //解密
    public static void Decoder(File src, File dest) throws IOException {
    
    
        dest.createNewFile();
        FileInputStream fis = new FileInputStream(src);
        FileOutputStream fos = new FileOutputStream(dest);
        int len;
        while((len = fis.read()) != -1){
    
    
            fos.write(len - 1);
        }
        fos.close();
        fis.close();
    }

3、文本文件中有以下的数据:2-1-9-4-7-8
将文件中的数据进行排序,变成以下的数据:1-2-4-7-8-9
方法一:常规方法

public static void main(String[] args) throws IOException {
    
    
        //1.读取数据
        FileReader fr = new FileReader("myio\\a.txt");
        StringBuilder sb = new StringBuilder();
        int ch;
        while((ch = fr.read()) != -1){
    
    
            sb.append((char)ch);
        }
        fr.close();
        System.out.println(sb);
        //2.排序
        String str = sb.toString();
        String[] arrStr = str.split("-");//2-1-9-4-7-8

        ArrayList<Integer> list = new ArrayList<>();
        for (String s : arrStr) {
    
    
            int i = Integer.parseInt(s);
            list.add(i);
        }
        Collections.sort(list);
        System.out.println(list);
        //3.写出
        FileWriter fw = new FileWriter("myio\\a.txt");
        for (int i = 0; i < list.size(); i++) {
    
    
            if(i == list.size() - 1){
    
    
                fw.write(list.get(i) + "");
            }else{
    
    
                fw.write(list.get(i) + "-");
            }
        }
        fw.close();
    }

常规方法不止一种,比如我写的:

public static void mysort(File f) throws IOException {
    
    
        FileInputStream fis = new FileInputStream(f);
        List<Character> b = new ArrayList<>();
        int len;
        while((len = fis.read()) != -1){
    
    
            if(len >= 48 && len <= 58) {
    
    
                System.out.println((char)len);
                b.add((char)len);
            }
        }
        fis.close();
        Collections.sort(b);

        StringJoiner sj = new StringJoiner("-", "", "");
        for (Character c : b) {
    
    
            sj.add(c.toString());
        }
        String s = sj.toString();

        FileOutputStream fos = new FileOutputStream(f);
        fos.write(s.getBytes());
        fos.close();
    }

方法二:用Stream流+方法引用(注意复习!下列代码看不懂赶紧去复习)

public static void main(String[] args) throws IOException {
    
    
        //1.读取数据
        FileReader fr = new FileReader("myio\\a.txt");
        StringBuilder sb = new StringBuilder();
        int ch;
        while((ch = fr.read()) != -1){
    
    
            sb.append((char)ch);
        }
        fr.close();
        System.out.println(sb);
        //2.排序
        Integer[] arr = Arrays.stream(sb.toString()
                .split("-"))
                .map(Integer::parseInt)
                .sorted()
                .toArray(Integer[]::new);
        //3.写出
        FileWriter fw = new FileWriter("myio\\a.txt");
        String s = Arrays.toString(arr).replace(", ","-");
        String result = s.substring(1, s.length() - 1);
        fw.write(result);
        fw.close();
    }

注意事项:
这种类型的题文本里不要轻易加换行,因为换行其实是\r\n,会被读到,需要注意。

关联的文件不是IDEA里的文件时文件可能会有隐含的bom头,记录一些文件信息(比如文件的字符编码),记录在文本文档的最前面。此时可以将其另存为UTF-8编码方式,就不再会有bom头。
如果是在IDEA中的文件默认是没有bom头的,如下图
在这里插入图片描述

缓冲流

缓冲流是把1基本流做了一个封装,额外又添加了一些功能的高级流
在这里插入图片描述

字节缓冲流

原理:底层自带了长度为8192的缓冲区提高性能
在这里插入图片描述

用字节缓冲流拷贝文件(一次读写一个字节数组)

	//创建缓冲流对象
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\AppData\\Local\\Java\\JAVA-Code\\IdeaProjects\\untitled2\\a.txt"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\AppData\\Local\\Java\\JAVA-Code\\IdeaProjects\\untitled2\\copy.txt"));
        //循环读取并写到目的地
        int len;
        while((len = bis.read()) != -1) {
    
    
            bos.write(len);
        }
        //关流
        bos.close();
        bis.close();

问题1:缓冲区在哪?
BufferedOutputStream 的构造方法,红框中的变量就是缓冲区。
在这里插入图片描述

问题2:为什么关流时只需要关缓冲流,不需要关基本流?
因为在关闭缓冲流的源码中已经包含关闭基本流了。

用字节缓冲流拷贝文件(一次读写多个字节数组)

public static void main(String[] args) throws IOException {
    
    
        //创建缓冲流对象
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\AppData\\Local\\Java\\JAVA-Code\\IdeaProjects\\untitled2\\a.txt"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\AppData\\Local\\Java\\JAVA-Code\\IdeaProjects\\untitled2\\copy.txt"));
        //循环读取并写到目的地
        int len;
        byte[] bytes = new byte[1024];
        while((len = bis.read(bytes)) != -1) {
    
    
            bos.write(bytes, 0, len);
        }
        //关流
        bos.close();
        bis.close();
    }

字节缓冲流读写原理

基本输入流一次性读取8192字节的数据放到缓冲区中,中间变量b在缓冲区之间不断一个一个把左缓冲区的数据移动到右缓冲区中(内存里的倒手非常快,几乎不消耗时间),当右边缓冲区填满了,就会用基本输出流自动写到目的地。当变量b在缓冲区中读不到数据了,继续重新循环,直到文件末尾为止。
注:左边的缓冲区和右边的缓冲区不是一个缓冲区,一个是输入流缓冲区,一个是输出流缓冲区。
在这里插入图片描述
如果定义的是数组bytes,移动速度更快。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/yeeanna/article/details/128764110