java IO流

IO流的概述

IO流:储存和读取数据的解决方案

游戏的进度为什么和消失了:因为我们的数据是保存在内存中的,程序停止,数据将会丢失

要解决这个问题:需要添加一个存档功能,把游戏的数据保存在硬盘的文件当中





是程序在读取内存中的数据,是程序在往文件中写入数据,这里说内存也可以因为程序就是运行在内存上面的

IO流的分类

纯文本文件:指的是用Windows自带的记事本打开能读懂的文件

经过验证:txt 和md是纯文本文件 word 和excel不是纯文本文件

IO流的体系和字节输出流基本用法



package com;

import java.io.*;

public class ByteStreamDemo1 {
    public static void main(String[] args) throws IOException {
        /*
        演示:字节输出流FileOutputStream :由程序中写出文字到文件中
        需求:写出一段文字到本地文件中(暂时不写中文)
        在相对路径下的aa.txt文件中写入数据
        实现步骤:
        1.创建对象
        2.写出数据
        3.释放资源
         */
        //1.创建流的对象

        //创建该流需要抛出异常 为了防止路径不存在
        FileOutputStream fos = new FileOutputStream("small\\www.txt");//FileOutputStream用于操作本地文件
        fos.write(97);//写入数据
        fos.close();//关闭资源
    }
}

FileOutStream的原理

创建对象实际上是在程序和文件之间建立了一个通道,write就是将数据放在通道上传输,close则是关闭通道

字节输出流写出数据的细节

如果要书写数字的化,可以写每个数字对应的ASCII

package com;

import java.io.*;

public class ByteStreamDemo1 {
    public static void main(String[] args) throws IOException {
        /*
        演示:字节输出流FileOutputStream :由程序中写出文字到文件中
        需求:写出一段文字到本地文件中(暂时不写中文)
        在相对路径下的aa.txt文件中写入数据
        实现步骤:
        1.创建对象
        细节1:参数是字符串表示的路径或者是File对象都是可以的(如果写的是字符串内部也会new对象)
        细节2:如果该路径下的文件不存在会常见一个新的文件,但是要保证父级路径是存在的
        细节3:如果文件已经存在,则会清空文件

        2.写出数据
        细节:write方法的参数是整数,但实际上是写到本地文件中的是整数在ASCII上对应的字符
        3.释放资源
        每次使用完流之后都要释放资源
        如果没有释放资源,则该文件一直被java占用,在后台中则无法删除该文件
         */
        //1.创建流的对象

        //创建该流需要抛出异常 为了防止路径不存在
        FileOutputStream fos = new FileOutputStream("small\\www.txt");//FileOutputStream用于操作本地文件
        fos.write(97);//写入数据
        //fos.close();//关闭资源
        while (true) ;
    }
}

FileOutStream写数据的3种方式


ctrl+alt+L格式化代码

package com;

import java.io.*;

public class ByteStreamDemo1 {
    public static void main(String[] args) throws IOException {
        //FileOutputStream的三种写出方式
        /*
        void write(int b)  一次写一个字节数据
        void write(byte[]b)  一次写一个字节数组数据
        void write(byte[]b,int off,int len)  一次写一个字节数组的部分数据
            参数1:数组名
            参数2:起始索引
            参数3:个数

         */
        //1.创建对象
        FileOutputStream fos = new FileOutputStream("small\\www.txt");//创建代码和文件之间的流通道
        //2.写入数据
     /*   fos.write(97);
        fos.write(99);*/
        byte[]bytes = {97,98,99,100};
        //fos.write(bytes);//全部读入字节数组
        fos.write(bytes,1,3);
        fos.close();//释放资源

    }
}

注意只要文件存在,没有写入前将会清空文件中的原始数据

换行和续写


解决换行书写
在windows的操作系统中,换行包含回车\r和换行\n两部

package com;

import java.io.*;

public class ByteStreamDemo1 {
    public static void main(String[] args) throws IOException {
        /*
        换行写:
        在两行之间写出换行符即可
        windows  \r\n
        Linux    \n
        Mac     \r
        细节:
        在java中,对Windows中的换行符进行了优化
        完整的换行符是\r\n,但我们只需要写\r或者\n
        java会在其底层进行补全
        建议:完整书写
         */
        //1.创建流的对象
        FileOutputStream fos = new FileOutputStream("small\\www.txt");
        //2.书写数据(现在已经可以书写字母和数字)
        //2.1先用字符串表示要数据的数据
        String str = "hello world";
        //将数据转化为字节数组
        final byte[] bytes = str.getBytes();//将数据转化为字节数组
        fos.write(bytes);//将字节数组里面的内容写入到文件中
        //2.2 进行换行
        //将换行符用字符串表示
        String str1 = "\r\n";
        final byte[] bytes1 = str1.getBytes();
        fos.write(bytes1);//对人换行符
        //2.3 写入第二行的数据
        String str3 = "swtYYDS";
        final byte[] bytes2 = str3.getBytes();
        fos.write(bytes2);
        //3.关闭资源
        fos.close();


    }
}

解决续写操作
在每次进行写入操作时都会默认对该文件中的内容进行清空操作,其实在创建流时就确定了对文件的内容是否清空,构造方法有一个参数用来确定是否对文件的内容进行清空,当我们没有进行设计时,该开关默认是false

package com;

import java.io.*;

public class ByteStreamDemo1 {
    public static void main(String[] args) throws IOException {
        /*
        换行写:
        在两行之间写出换行符即可
        windows  \r\n
        Linux    \n
        Mac     \r
        细节:
        在java中,对Windows中的换行符进行了优化
        完整的换行符是\r\n,但我们只需要写\r或者\n
        java会在其底层进行补全
        建议:完整书写
         */
        /*
        续写:该构造方法传递的开关理解为是否开启续写
        如果想要续写,打开开关即可
        开关为止,构造方法的第二个参数
        开关默认为false:表示关闭续写,此时创建对象将会清空文件
        手动传递true,表示打开续写,此时创建对象不会清空文件

         */
        //1.创建流的对象
        FileOutputStream fos = new FileOutputStream("small\\www.txt",true);
        //2.书写数据(现在已经可以书写字母和数字)
        //2.1先用字符串表示要数据的数据
        String str = "hello world";
        //将数据转化为字节数组
        final byte[] bytes = str.getBytes();//将数据转化为字节数组
        fos.write(bytes);//将字节数组里面的内容写入到文件中
        //2.2 进行换行
        //将换行符用字符串表示
        String str1 = "\r\n";
        final byte[] bytes1 = str1.getBytes();
        fos.write(bytes1);//对人换行符
        //2.3 写入第二行的数据
        String str3 = "swtYYDS";
        final byte[] bytes2 = str3.getBytes();
        fos.write(bytes2);
        //3.关闭资源
        fos.close();


    }
}

FileInputStream字节输入流

字节输入流的基本用法


暂时结论:在IO流中 是ASCII码和其表示的字符进行交互的
我们以ASCII码的形式写入数据到文件中,然后文件又以ASCII的形式将文件中的数据输出到我们的代码中

package com;

import java.io.*;

public class ByteStreamDemo1 {
    public static void main(String[] args) throws IOException {
      /*
      演示FileInputStream
      需求:读取文件中的数据(暂时不写中文)
      实现步骤:
        创建对象
        读取数据
        释放资源
       */
        //1.创建对象
        FileInputStream fis = new FileInputStream("small\\www.txt");
        //2.读取数据
        int read = fis.read();
        System.out.println((char)read);//将int类型强制转成char

        int read1 = fis.read();
        System.out.println((char)read1);

        int read2 = fis.read();
        System.out.println((char)read2);

        int read3 = fis.read();
        System.out.println((char)read3);

        int read4 = fis.read();
        System.out.println((char)read4);
       //注意该题的前提下只有abcde 5个数据
        int read5 = fis.read();
        System.out.println(read5);//-1

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

结论:当read方法读取不到数据时,它将会返回-1

字节输入流读取数据的细节

注意:当读取-1时,程序会先读取-(负号)然后再读取1

重点在创建对象时的细节

创建对象
细节:当文件不存在会直接报错
java为什么会这样设计?
输出流:当文件不存在会创建文件
而输入流:不存在,会报错
理解:重要的是数据,当输出流往文件中写数据时,数据已经存在 而没有文件创建出一个空的文件就可以了
当输入流时往程序中输出文件中的数据,这时候文件都没有,不知道数据,如果创建出一个空文件也没有意义

字节输入流循环读取

以上演示的都是少量数据,但是在现实中的的数据一定会比较大,所以才有了字节输入流的循环读取

package com;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ByteStreamDemo3 {
    public static void main(String[]args) throws IOException {
        /*
        字符串输入流的循环读取
         */
        //1.定义流的对象并把文件放在流上面
        FileInputStream fis = new FileInputStream("small\\www.txt");
        //2.进行循环读取
        int b;//定义第三方变量接受read的结果
        while ((b = fis.read()) != -1){
            System.out.println( (char)b);
        }
    }
}

关于在循环读取时必须要设置第三方变量b的原因

如果我们不设置第三方变量b,在输出的时候直接用fis.read()进行输出,其实是又调用了一次read函数,相当于我们一次循环读取了2个数据,但是我们只输出了一个数据,这样必然会导致我们有些数据没有数据

在上述的过程中我们已经学会了将程序中的数据写入到本地文件中,即FileOutStream,和将本地文件中的数据输出到程序当中,即FileIntSteam,即到现在我们已经就掌握了对文件的拷贝

文件拷贝的代码

package com;

import java.io.*;

public class ByteStreamDemo1 {
    public static void main(String[] args) throws IOException {
      //文件拷贝的基本代码
        //注意:选择一个比较小的文件,大文件拷贝下一个视频再说
        //由于没有mp4文件,我们以txt为例
        //1.创建对象
        FileOutputStream fos = new FileOutputStream("small\\sss.txt");
        FileInputStream fis = new FileInputStream("C:\\Data\\a1.txt");
        //2.拷贝
        //核心思想:边读边写
        //先将文件里面的内容读出来 然后写入到新的文件中
        int b;//
        while ((b = fis.read())!=-1){
            fos.write(b);
        }
            //3.释放资源
        //规则:先开的最后释放
        fos.close();
        fis.close();
    }
}

文件拷贝的弊端和解决方案


速度慢的根本原因:;一次只读取一个字节


read()方法当读取到数据时,返回所读取到的数据的int表示,当没有读取到数据时返回-1 read(byte[])方法当读取到了数据时,返回本次所读取到的数据的字节个数


在我们读取数据的过程中,我们不断的用读取到的数据来跟新数组中所记录的数据,这样就会带来一个问题,当最后一次读取到的数据无法将数组装满时,我们的数组中就会有上一次读取的残留数据

我们来进行改进的时候,将重点放在Arrays的toString方法上,toString的一个重载方法,可以将字节数组的一部分变成字符串

package com;

import java.io.*;
import java.util.Arrays;

public class ByteStreamDemo1 {
    public static void main(String[] args) throws IOException {
      //文件拷贝的改进:一次读取多个数据
        FileInputStream fis = new FileInputStream("C:\\Data\\a1.txt");
        //定义字节数组
        byte[]bytes = new byte[2];//一次读取2个字节的数据
        final int len = fis.read(bytes);//返回一次读取了几个数据
        System.out.println(len);
       String str = new String(bytes,0,len);
        System.out.println(str);

        final int len1 = fis.read(bytes);//返回一次读取了几个数据
        System.out.println(len);
        String str1 = new String(bytes,0,len1);
        System.out.println(str1);

        final int len2 = fis.read(bytes);//返回一次读取了几个数据
        System.out.println(len2);
        String str2 = new String(bytes,0,len2);
        System.out.println(str2);

        final int len3 = fis.read(bytes);//返回一次读取了几个数据
        System.out.println(len3);

        String str3 = new String(bytes,0,len3);
        System.out.println(str3);



    }
}

文件拷贝的改写(用一次读取多个数据提速版)

package com;

import java.io.*;
import java.util.Arrays;

public class ByteStreamDemo3 {
    public static void main(String[]args) throws IOException {
        //文件的拷贝(提速版)
        //将C:\Data\a1.txt下的内容拷贝到small\sss.txt中
        //1.定义流的对象
        FileInputStream fis = new FileInputStream("C:\\Data\\a1.txt");
        FileOutputStream fos = new FileOutputStream("small\\sss.txt");
        //2.进行拷贝:边读边写
        //定义字节数组
        int len;//第三方变量
        byte[] bytes = new byte[2];//规定其最多一次读取2个字节
        while ((len = fis.read(bytes))!=-1){
            fos.write(bytes,0,len);//读取多少即写入多少
        }
        //3.关闭资源
        fos.close();
        fis.close();

    }
}

IO流中不同JDK版本捕获异常的方法


以上对于异常处理的存在一个问题,当try所包含的语句中出现了异常,此时代码将会跳转到catch中,此时就会导致释放资源的语句没有被执行,这样将会导致很严重的后果

这时候可以引出我们完整的异常处理语句try...catch...finally

将释放资源的代码写在finally中

package com;

import java.io.*;
import java.util.Arrays;

public class ByteStreamDemo3 {
    public static void main(String[]args)  {
        //文件的拷贝(提速版):用try...catch..finally进行异常处理

        FileInputStream fis = null;
        FileOutputStream fos = null;
        //如果当创建fis和fos对象时出现异常,将会进行跳转,fis和fos有没有被初始化的风险
        try {
            //1.定义流的对象
          fis =   new FileInputStream("C:\\Data\\a1.txt");
          fos = new FileOutputStream("small\\sss.txt");

            //2.进行拷贝:边读边写
            //定义字节数组
            int len;//第三方变量
            byte[] bytes = new byte[2];//规定其最多一次读取2个字节
            while ((len = fis.read(bytes))!=-1){
                fos.write(bytes,0,len);//读取多少即写入多少
            }
            //3.关闭资源

        } catch (IOException e) {
           e.printStackTrace();
        } finally {//对于资源的关闭语句也可能会出现异常
            //当fos和fis为空也可能出现空指针异常
            if(fos!= null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
           if(fis!=null){
               try {
                   fis.close();
               } catch (IOException e) {
                   e.printStackTrace();
               }
           }

        }

    }
}

但是这个代码我们可以发现,对于异常的处理还是有点复杂,java给我们提供了一个接口可以简化对资源的释放

注意:一定要实现了AutoCloseable接口才能使用自动关闭资源

jdk7

package com;

import java.io.*;
import java.util.Arrays;

public class ByteStreamDemo3 {
    public static void main(String[]args)  {
        //文件的拷贝(提速版):用try...catch..finally进行异常处理
        //JDK7:io中捕获异常的写法
        /*
        try后面的括号中写创建对象的代码
            注意:只有实现了AutoCloseable接口的类,才能在小括号中创建对象
            try(){

            }catch(){
            }
         */
        try(FileOutputStream fos = new FileOutputStream("small\\sss.txt");
        FileInputStream fis = new FileInputStream("C:\\Data\\a1.txt")){
            //2.进行拷贝:边读边写
            //定义字节数组
            int len;//第三方变量
            byte[] bytes = new byte[2];//规定其最多一次读取2个字节
            while ((len = fis.read(bytes))!=-1){
                fos.write(bytes,0,len);//读取多少即写入多少
            }
        }catch (IOException e){
            e.printStackTrace();
        }

    }
}

jdk9中发现将创建对象的代码写在()中的代码阅读性差

package com;

import java.io.*;
import java.util.Arrays;

public class ByteStreamDemo3 {
    public static void main(String[]args) throws FileNotFoundException {
        //文件的拷贝(提速版):用try...catch..finally进行异常处理
       //jdk9中的处理
        //外面的异常依然抛出
        FileOutputStream fos = new FileOutputStream("small\\sss.txt");
        FileInputStream fis = new FileInputStream("C:\\Data\\a1.txt");
        try(fis;fos){
            //2.进行拷贝:边读边写
            //定义字节数组
            int len;//第三方变量
            byte[] bytes = new byte[2];//规定其最多一次读取2个字节
            while ((len = fis.read(bytes))!=-1){
                fos.write(bytes,0,len);//读取多少即写入多少
            }
        }catch (IOException e){
            e.printStackTrace();
        }

    }
}

字符集详解


当有中文会有怎样的结果?

package com;

import java.io.*;
import java.util.Arrays;

public class ByteStreamDemo3 {
    public static void main(String[]args) throws IOException {
      //测试用字节流读取中文
        FileInputStream fis = new FileInputStream("small\\sss.txt");
       int b;
       while ((b = fis.read())!=-1){
           System.out.println((char)b);
       }
       fis.close();
    }
}

如代码运行结果所示,当我们的文件中存在中文时,我们读取的代码将会出现乱码

计算机的存储规则


ASCII字符集


ASCII储存英文的储存规则

那如果我们想储存汉字该怎么办呢

ANSI就表示GBK

GBK英文字母的储存

和ASCII的储存方式一样

GBK储存汉字

为社么规则高位的二进制一定是以1开头

这主要是为了区分中文和英文,因为GBK可以同时为英文和中文进行编码,而英文的编码规则是在高位补零,一定是以0开头,而在中文中进行高位补1可以和英文进行区分



UniCode字符集详解


编码规则

英文编码规则

和前面的一样,最高位补零,其他用查询到的二进制进行补充

中文编码规则



为什么会有乱码



当我们用字节流读取汉字时,我们的流每次只能读取一个字节,如上图所示,我们先读取字母,UTF-8编码因为使用的是一个字节,每一个字节表示的是一个英文字母。而当我们读取到汉字是,读取第一个字节时,在进行解码的过程时,其所对应的数字找不到对应的汉字,此时就会出现乱码的情况

原因2编码和解码的方式不一样

  • 正确的解码过程
  • 错误解码

如何不产生乱码

字节流读取中文会乱码,但为什么拷贝不会乱码呢


一个字节一个字节进行拷贝,数据没有丢失

java中编码和解码的代码实现

在java中可以通过方法看到编码和解码的结果


对于编码过程的理解

通过我们要编码的数字在对于的字符集中找到其对于的码值,然后根据码值找到其对应的二进制

package com;

import java.io.*;
import java.util.Arrays;

public class ByteStreamDemo3 {
    public static void main(String[]args) throws UnsupportedEncodingException {
        /*
            java中的编码方法
            public byte[] getBytes() 使用默认的方式进行编码
            public byte[] getBytes(String charsetName) 使用指定的方式进行编码
            java中的解码方法
            String (byte[]bytes)
            String (byte[]bytes,String charsetName)
         */
        //编码
        String str = "ai你哟";
        byte[] bytes = str.getBytes();//空参使用idea默认的编码方法(UTF-8)
        System.out.println(Arrays.toString(bytes));

         byte[] bytes1 = str.getBytes("GBK");//此处有可能你所写的编码方式不存在
        //所以需要抛出异常
        System.out.println(Arrays.toString(bytes1));
        //解码
        String str3 = new String(bytes);//默认的解码方式
        System.out.println(str3);

        String str4 = new String(bytes,"GBK");
        System.out.println(str4);//ai浣犲摕 编码和解码不一样将会出现乱码
    }
}

有没有一种流,遇到字符每次读一个字节,当遇到中文每次读多个字节?---------字符流

字符输入流-空参read方法详解


字符流继承框架

FileReader



package com;

import java.io.*;
import java.util.Arrays;

public class ByteStreamDemo3 {
    public static void main(String[]args) throws IOException {
       /*
       FileReader的方法演示
       第一步:创建对象
       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("small\\www.txt");
        //2.读取数据read()
        /*
        1.read()默认也是一个一个字节进行读取的,如果碰到中文和一次读取多个字节
        GBK一次读取2个字节 UTF-8一次读3个字节

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

            如果想看到中文,把这些十进制数据进行强制转化就行

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

字符输入流--有参read方法详解

package com;

import java.io.*;
import java.util.Arrays;

public class ByteStreamDemo3 {
    public static void main(String[]args) throws IOException {
       /*
       FileReader的方法演示
       第一步:创建对象
       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("small\\www.txt");
        //2.读取数据(一次读取多个数据并将数据放在数组中)
        char [] chars = new char[2];//一次读取2个数据
        int len;
        /*
        有参read方法的细节理解:
        read(chars) :读取数据,解码,强转三部合并了,把强转和的字符放到数组当中
        等于空参的read+强转
         */
        while(( len = fr.read(chars))!= -1){
            //把数组里面的数据变成字符串再打印
            System.out.println(new String (chars,0,len));//注意最后数组装不满的情况
        }
    }
}

细节:
我们的文件内容:

很明显我们的文件中有一个回车的内容,而再我们的文件中回车是由\r\n进行表示的,当我们每次读取2个文字时,会以此读到 流\r和
\n举此时的输出结果会很明显


当我们的输出为print时,可以得到和文本一样的内容

字符输出流写数据



字节流,每次只能对一个字节进行操作,当我们的数值超过了一个字节,将会出现乱码
在idea中找到该文件 然后右键 打开于 然后Exploer 打开该文件在计算机中的储存位置

package com;

import java.io.*;
import java.util.Arrays;

public class ByteStreamDemo3 {
    public static void main(String[]args) throws IOException {
      /*
      第一步:创建对象
      public FileWriter(File file) 创建输出流关联本地文件(后面的几种构造方法不再写出)
      第二步:读取数据
      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() 释放资源/关流
      '我' 251045

       */
        //创建流关联文件
        FileWriter fw = new FileWriter("small\\sss.txt");
        //1.如果此时是字节流将会出现乱码,因为字节流只能操作一个字节
        //2.将25105解码成对应的二进制储存
        //fw.write(25105);引出一个字符
        String str = "千山鸟飞绝,万径人踪灭";
       // fw.write(str);//写入一个字符串
        char[] chars = {'a','b','c','天','才'};
        fw.write(chars);
        fw.close();
    }
}

字符流原理

字符输入流底层原理

缓冲区的运行过程

在内存中建立缓冲区,可以避免频繁在文件中读取数据,可以节约大量的时间

当我们的文件比较大,超过了8192将会怎么办?

它会将文件中剩下的数据放在缓冲区中,然后读取缓冲区

下面这个举例有点意义

字符输出流底层原理


也会先在内存中建立长度为8192的字节数组(缓冲区)

会先把数据写入到缓冲区中,当1.缓冲区装满了2.手动刷新缓冲区(flush方法)3.释放资源(close)进行以下三种操作可以将缓冲区中的数据保存到本地文件中

close和flush方法的区别

和字符输入流的原理相似,但是不完全一样

综合练习1(拷贝文件夹)


package com;

import java.io.*;
import java.util.Arrays;

public class ByteStreamDemo3 {
    public static void main(String[]args) throws IOException {
        //拷贝一个文件夹并且考虑子文件夹
        //1.创建2个路径表示源文件夹和目标文件夹的路径
        File src = new File("C:\\src");
        File dest = new File("C:\\dest");
        //2.调用方法进行拷贝
        copydir(src,dest);

    }
/*
作用:拷贝文件夹
参数一:数据源
参数二:目的地
 */
  public static void copydir(File src, File dest) throws IOException {
      dest.mkdirs();//创建目标文件夹(如果我们的目标文件夹不存在将会保存并不会被自动创建)
      //思想:递归
      //1.进入数据源
      final File[] files = src.listFiles();//将文件夹中的内容装到File数组中
      //2.遍历数组
      for (File file : files) {//对File数组中每一个内容进行判断
          if(file.isFile()){
              //3.判断文件,拷贝
              //3.1创建输入输出流用于拷贝
              FileInputStream fis = new FileInputStream(file);
              //注意:这里的输出流的路径不能写文件夹的路径,我们应该是拷贝到文件夹中具体的文件中
              FileOutputStream fos = new FileOutputStream(new File(dest,file.getName()));
              //为了提高效率我们使用字节数组
              byte[]bytes = new byte[1024];//每次拷贝1kb
              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次可以得到原始数值

package com;

import java.io.*;
import java.util.Arrays;

public class ByteStreamDemo1 {
    public static void main(String[] args) throws IOException {
        /*
        加密原理:
                对原始文件中每一个字节数据进行更改,然后将更改以后的数据储存到新的文件中
        解密原理:
                读取加密的文件,按照加密的规则反向操作,从而解密
         */
        //本次加密原理:我们发现同一个数值对同一个数值^2次可以得到原始数值
        //1.创建流关联文件
        FileOutputStream fos = new FileOutputStream("small\\zzz.txt");
        FileInputStream fis = new FileInputStream("small\\secrt.txt");
        //2.进行读取和写入操作(拷贝)
        int a;
        while ((a = fis.read())!=-1){//每次读取一个字节更方便对字节进行操作
            fos.write(a^10);
        }
       fis.close();
        fos.close();
    }
}

综合练习3(修改文件中的数据)(重点)

方法一:常规写法
方法二:使用流来优化

细节一:文件中的数据不要换行,因为我们在换行时会有\r\n,在读取的时候会被读取到,在排序时会导致混乱
细节二:bom头(会将文件的信息,如文件的编码,会将文件的这些信息以一个隐藏的方式放在数据信息的前面)

当我们打开文件,另存为,将下面的编码选为带有Bom头的UTI-8编码,即为文件的数据包含了Bom头信息,此时的文件大小会增加

避免Bom头的方式

1.本地文件:打开文件,--》另存为---》查看编码方式

2.idea创建的文件:文件-->打开设置-->编译器--->文件编码--->将默认创建文件的方式改为不包含Bom
这个练习涉及到很多之前学习的内容,在此设立标记,应该重点关注

字符缓冲流拷贝文件(一次读一个字节)

我们之前学习的流都是基本流,我们将基本流进行封装都添加缓冲区以提高效率,并添加一些新的方法,以此变成高级流



高级流是由基本流进行包装的,其实在底层还是用基本流读写数据

package com;

import java.io.*;
import java.util.Arrays;

public class ByteStreamDemo1 {
    public static void main(String[] args) throws IOException {
       /*
       利用字节缓冲流拷贝文件
       字节缓冲输入流构造方法:
            public BufferedInputSteam(InputStream is)
        字节缓冲输出流构造方法
            pulic BufferedOutputStream(OutStream os)
        */
        //1.建立缓冲流并关联文件
        //文件的路径写在基本流中,也证实了实际由基本流进行操作
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("small\\www.txt"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("small\\cc.txt"));
        //2.进行拷贝操作
        int b;
        while ((b = bis.read()) != -1){
            bos.write(b);
        }
        //只需要关闭缓冲流,不需要关闭基本流
        bos.close();
        bis.close();
    }
}

为什么只用关闭缓冲流,而不用关闭基本流?

我们查看缓冲流的关闭源码,可以知道在关闭缓冲流的时候,会在代码的中先将基本流进行关闭了

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

package com;

import java.io.*;
import java.util.Arrays;

public class ByteStreamDemo1 {
    public static void main(String[] args) throws IOException {
       /*
       利用字节缓冲流拷贝文件
       字节缓冲输入流构造方法:
            public BufferedInputSteam(InputStream is)
        字节缓冲输出流构造方法
            pulic BufferedOutputStream(OutStream os)
        */
      //1.创建缓冲流对象
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("small\\www.txt"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("small\\cc.txt",));
        //2.进行拷贝
        byte[] bytes = new byte[1024];//一次拷贝1024个字节
        int len;
        while ((len = bis.read(bytes)) != -1){//read方法返回拷贝了多少给字节,并将拷贝的数据放到字节数组中
            bos.write(bytes,0,len);
        }
        //3.关闭资源
        bos.close();
        bis.close();
    }
}

字节缓冲流读写原理


变量b在缓冲区中读取数据并写入缓冲区中,变量b的作用为倒手,当右边的缓冲区填满了,会自动的写到目的地。当左边的缓冲区中读不到数据了,会再次到文件中读取8192个字节到缓冲区中

因为我们的倒手的过程都是在内存中进行的,而内存的操作是非常快的,所以这段过程非常快

当我们一次读取一个字节数组,无非倒手的是一个数组

字符缓冲流


由于字符基本流里面已经自带了缓冲区,所以字符缓冲流提高效率不是很明显



newLine方法的底层会先判断是哪个操作系统,然后写出对应的换行符

字符缓冲输入流

package com;

import java.io.*;
import java.util.Arrays;

public class ByteStreamDemo1 {
    public static void main(String[] args) throws IOException {
      /*
      字符缓冲输入流:
        构造方法:
             public BufferedReader(Reader r)
        特有方法:
            public String readLine() 读一整行
       */
        //1.创建字符缓冲输入流的对象并关联文件
        BufferedReader br = new BufferedReader(new FileReader("small\\cc.txt"));
        //2.读取数据(读取单行数据)
       /* final String line1 = br.readLine();
        System.out.println(line1);*/
        /*
        readLne方法细节:
        1.readLine方法一次读取一行,遇到回车换行结束(不会把回车和和换行读到内存当中)
        2.readLine方法返回一个字符串,将读取到的内容放到该字符串中
        3.当没有读取到内容,将返回null
         */
        //3.循环读取所有数据
        String line;
        while ((line = br.readLine()) != null){
            System.out.println(line);
        }



    }
}

字符缓冲输出流

package com;


import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.FilterWriter;
import java.io.IOException;

public class BufferStreamDemo1 {
    public static void main(String[] args) throws IOException {
        /*
        字符缓冲输出流
        构造方法:
            public BufferedWriter(Writer r)
         特有方法:
            public viod newLine() 跨平台换行
         */
        //1.创建字符缓冲输出流并关联文件
       BufferedWriter bw = new BufferedWriter(new FileWriter("cd.txt"));//如果没有指定父级路径
        //即默认在当前模块下 即scr文件夹下
       //2.写入数据
        bw.write("你好啊");
        //bw.write("\r\n");此处的换行符不能跨平台
        bw.newLine();
        bw.write("新的一天");
        bw.close();

    }
}

注意:字符缓冲流和字节缓冲流的长度有区别,字节缓冲流的缓冲区是8192的字节数组,而字符缓冲流的缓冲区是8192的字符数组

综合练习1(拷贝文件并比较4种拷贝方式的效率)


为什么字节缓冲流一次读取一个字节所用的时间比较多?

字节缓冲流的输入流和输出流都存在缓冲区,中间的变量反复倒手,倒手操作在内存中进行,案例来说所用的时间不多,甚至可以忽略不计,但是在我们的文件非常大的时候,这个变量的倒手操作其实也是需要花一点时间的


第一种拷贝方式,耗时最多。第三种方式也耗费了大量的时间

以后进行文件拷贝的操作,建议使用字节基本流一次读取一个数组和字节缓冲流一次读取一个数组

综合训练2(修改文本顺序)

因为是纯文本文件,所有使用字符流比较好,如果是使用字符流的基本流,需要一个子一个子的读,而使用字符流的缓冲流,可以一次读一行,效率更高

写法一

package com;


import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class BufferStreamDemo1 {
    public static void main(String[] args) throws IOException {
        //需求:把文件的顺序恢复到一个新的文件中
        //思路:读出数据-->排序-->写入到新的文件
        //1.读取数据
        //使用字符缓冲输入流读取数据
        BufferedReader  bf = new BufferedReader(new FileReader("C:\\dest\\a1.txt"));
        String line;
        ArrayList<String> list = new ArrayList<>();
        while ((line = bf.readLine()) != null){
           //将读取的数据用集合储存以备排序

            list.add(line);

        }
        bf.close();

        //2.排序
        //Collections默认按照升序排序,不符合我们现在的排序需求
        //使用该方法的自定义重载
       Collections.sort(list, new Comparator<String>() {
           @Override
           public int compare(String o1, String o2) {
               final int i1 = Integer.parseInt(o1.split("\\.")[0]);
               final int i2 = Integer.parseInt(o2.split("\\.")[0]);
               return i1-i2;
           }
       });
        //3.写入数据
        BufferedWriter bw = new BufferedWriter(new FileWriter("small\\ff.txt"));
        for (String str : list) {
            bw.write(str);//写入一个字符串
            bw.newLine();//换行
        }
        bw.close();
    }
}

排序前文件

排序后文件


该题经历:当我们的源文件使用了Idea中的文件,在进行pareInt转化成int类型时,一直报转化格式异常,当换成本地文件时则正常,不知道为什么

写法二(用带有排序功能的集合进行储存)

package com;

import java.io.*;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

public class BufferStreamDemo2 {
    public static void main(String[] args) throws IOException {
        //1.读取数据
        BufferedReader br = new BufferedReader(new FileReader("C:\\dest\\a1.txt"));
        String line;
        TreeMap<Integer,String> tm = new TreeMap<>();//储存序号和内容
        while ((line = br.readLine()) != null){
            final String[] arr = line.split("\\.");//按照.进行切割
            tm.put(Integer.parseInt(arr[0]),line);//将序号和包含序号的内容放进集合中
        }
        br.close();
        //2.写出数据
        BufferedWriter bw = new BufferedWriter(new FileWriter("small\\ff.txt"));
        //遍历集合将内容写到新的文件中
        final Set<Map.Entry<Integer, String>> entries = tm.entrySet();//将Map集合放到Set集合中
        for (Map.Entry<Integer, String> entry : entries) {//遍历set集合
            final String value = entry.getValue();//得到内容
            bw.write(value);//将排序后的内容写入
            bw.newLine();//写入换行

        }
      bw.close();//关闭资源
    }
}

Tree集合本身有排序功能,不需要再排序,该程序的运行结果和上面的程序一样

综合训练3(控制软件运行次数)


利用计数器的思想记录程序运行的次数,因为程序中的变量是在内存中,随着程序的结束该变量将会消失,所有我们可以将变量定义在本地文件中实现对该计数器变量的永久储存

package com;

import java.io.*;

public class BufferStreamDemo3 {
    public static void main(String[] args) throws IOException {
        //实现一个验证程序运行次数的小程序
        /*
        分析思路:
        计数:计数器
        count=0  计数器定义在程序(内存)中 程序运行结束count就消失了
        解决方案:将count保存在本地文件中实现永久储存
         */
        //1.把计数器文件读取到程序中
        //使用缓冲字符流更好 次数为1000可以一次读取
        BufferedReader br = new BufferedReader(new FileReader("small\\count.txt"));
        int count = Integer.parseInt(br.readLine());//读取一行
        br.close();
        count++;//表示程序又运行了一次
        //2.判断
        if(count<=3){
            System.out.println("欢迎使用本软件,第"+count+"次使用免费");
        }else {
            System.out.println("本软件只能免费使用3次,欢迎你注册账号后继续使用");
        }
      //3.把自增后的count写入到文件中
        BufferedWriter bw = new BufferedWriter(new FileWriter("small\\count.txt"));
        bw.write(count+"");//如果不转换成字符串,写入的就是count对应的字符
        bw.close();
    }
}

小细节:当我们将输入流和输出流写在一起时
我们运行代码发现出现格式转化异常,我们将要转化的字符串输出,发现 s是null

原因解释

输入流BufferWreter流在创建的时候会刷新文件,原本文件中的0被刷新掉了,然后读取的时候读取到的就是null,所有导致了后面的异常的发生

转换流的基本用法


属于字符流,是高级流


InputStreamReader内部包装一个字节输入流,这个这个字节流就有了字符流的特性,可以根据字符集一次读取多个字节,读取数据不会乱码了


OutputStreamWriter将字符流转换为字节流

转换流的作用


作用1:利用转换流指定字符集读取

package com;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

//验证转换流的作用
public class ConvertStreamDemo1 {
    public static void main(String[] args) throws IOException {
        //利用转换流指定字符集读取
      //1.创建转换输入流并指定字符集
        //源文件是GBK编码,如果我们不指定GBK编码,则使用我们IDEA默认的UTF-8编码,将会出现乱码
        InputStreamReader isr = new InputStreamReader(new FileInputStream("C:\\dest\\a1.txt"),"GBK");
        //2.读取数据
        int ch;
        while ((ch = isr.read())!=-1){
            System.out.print((char) ch);
        }
        isr.close();
    }
}

但是这种方式在JDK11时已经被淘汰

新的方式


        FileReader fr = new FileReader("C:\\dest\\a1.txt", Charset.forName("GBK"));
       //读取数据
        int ch;
        while ((ch = fr.read())!=-1){
            System.out.print((char) ch);
        }
        fr.close();

利用FileReader类在jdk11时新增的构造方法,可以在创建对象的时候指定读取的编码集

作用二:利用转换流指定字符编码写出

package com;

import java.io.*;

public class ConvertStreamDemo2 {
    public static void main(String[] args) throws IOException {
        //利用转换流按照指定字符编码写出
        //1.创建转换流对象
        //指定写入的编码方式为GBk
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("small\\dd.txt"),"GBK");
        osw.write("你吃饭了吗");
        osw.write("hello");
        osw.close();
    }
}

此时的写出编码指定为GBK但是IDEA文件的默认编码为UTF-8,此时将会出现乱码的现象


当我们将指定的编码修改成和IDea中文件的编码方式UTF-8,乱码的现象就消失了

但是在jdk11时,这种方式也被淘汰了

新的解决方案

  //替代方案
        FileWriter fw = new FileWriter("small\\dd.txt", Charset.forName("GBK"));
        fw.write("你吃饭了吗");
        fw.write("hello");
        fw.close();

感悟:要想包装数据不乱码需要保证读写的编码方式和文件的编码方式(IDEA中创建文件的默认编码和本地文件的编码都包含)一样
把GBK文件转换成UTF-8文件

package com;

import java.io.*;

public class ConvertStreamDemo3 {
    public static void main(String[] args) throws IOException {
        //将本地文件的GBK文件转成UTF-8文件
        //1.设置GBK的编码方式读出文件
        //2.设置UTF-8的方式写入文件
        //jdk11之前的解决方案

        //读出(以GBK的方式进行读取)
        InputStreamReader isr = new InputStreamReader(new FileInputStream("C:\\dest\\a3.txt"),"GBK");
        int ch;
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("small\\result.txt"),"UTF-8");
        while ((ch = isr.read()) !=-1){
            osw.write(ch);
        }
        osw.close();
        isr.close();
    }
}

替代方案

//替代方案
        FileReader fr = new FileReader("C:\\dest\\a3.txt",Charset.forName("GBK"));
        FileWriter fw = new FileWriter("small\\result.txt", Charset.forName("UTF-8"));
        int b;
        while ((b = fr.read()) !=-1){
            fw.write(b);
        }
        fw.close();
        fr.close();

转换流练习

package com;

import java.io.*;

public class ConvertStreamDemo4 {
    public static void main(String[] args) throws IOException {
        //利用字节流读取文件中的数据,每次读一整行,而且不能出现乱码
        //1.字节流读中文的时候,会出现乱码,但字符流可以搞定
        //2.字节流里面没有读一整行的方法,但是字符缓冲流可以搞定

        /*
        1.我们的转换流可以把字节流进行封装转换成字符流,可以实现不乱码
        2.由于转换流没有读取一整行的方法,我们可以将转换流封装成缓冲流(缓冲流有读取一整行的方法)
         */
      /*  //字节流
        FileInputStream fis = new FileInputStream("C:\\dest\\a1.txt");
        //将字节流封装成字符转换流
        InputStreamReader isr = new InputStreamReader(fis);
        //字符流封装成缓冲流
        BufferedReader br = new BufferedReader(isr);
        //进行读取*/
        //将上面封装的过程合并成一步
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("C:\\dest\\a1.txt")));
        String line;
        while ((line = br.readLine()) !=null){
            System.out.println(line);
        }
      br.close();
    }
}

序列化流

在IO流钟的体系结构

序列化流属于字节流,为一种高级流,用来包装基本流,用于输出数据

反序列化流,为输入流


利用序列化流可以把对象写到文件中,但是会参数乱码,但是只用可以用反序列化流正确的读出来就可以了

另一种解决方案

我们可以直接将对象的属性写到文件中,这样在文件中就可以看懂并且也可以修改了


这两种方案都有应用场景

举例

我们将游戏中角色的对象写道文件中,可以用系列化流进行写入,在文件中表示时将会是乱码形式,无法修改。
我们想象一下如果是直接将属性写到文件中,这样玩家不就可以随便修改游戏中角色的属性了吗

系列化流也称为对象操作输出流,可以把java中的对象写到本地文件中

package com;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class ObjectStreamDemo1 {
    public static void main(String[] args) throws IOException {
        /*
        需求:利用序列化流/对象操作输出流,把一个对象写到本地文件中
        构造方法:
            public ObjectOutputStream(OutputStream out) 把基本流变成高级流
         成员方法:
            public final void writeObject(Object obj)  把对象序列化(写入)到文件中
         */
        //1.先创建对象
        Student stu = new Student("张三",23);
        //2.创建对象输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("small\\aw.txt"));
        //3.写入对象
        oos.writeObject(stu);
        //4.释放资源
        oos.close();
    }
}

但是以上代码我们在运行的时候,发现会出现以下异常



我们将Student类实现`Serializable接口

反序列流


将文件中的对象读取到程序中

package com;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;

public class ObjectStreamDemo2 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        /*
        需求:利用反序列化流/对象输入流把文件中的对象读取到程序中
        构造方法:
            public ObjectInputStream(InputStream out) 把基本流变成高级流
         成员方法
         public Object readObject() 把序列化到本地文件读取到程序中

         */
        //1.创建反序列化流的对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("small\\aw.txt"));
        //2.读取数据
        final Student o = (Student) ois.readObject();
        //3.打印对象
        System.out.println(o);
        //4.释放资源
        ois.close();


    }
}

序列化流和反序列化流的使用细节

细节1

当我们先用序列化流将对象写入到了文件中,然后我们在Student类中临时添加一个属性adderss,然后用反序列化流将文件中的对象读取到程序中

发现出现了异常(进行反序列流时)


我们仔细查看报错信息发现好像是serialVersionUID不一样

序列号


如果我们的类实现了Serializable接口,java会根据这个类的成员变量 ,静态变量 构造方法 成员方法计算出一个long类型的序列号也可以理解为版本号

当我们用序列化流把对象写到文件中时,也会把序列化一起写到文件中,而当我们修改Student类,java会重新计算Stduent类的序列号,当我们进行读取对象时,会造成序列化不一致,从而出现异常

以上问题的解决方案

1.不让别人修改JavaBean类,但这是不实际的,但业务需要时,肯定会修改javabean类

解决方案---固定序列号

序列号变量的写法只能是serialVersionUID
可以自己手动在类中定义,但是这样太麻烦了,可以修改idea的设置实现由idea提示定义

上面是自己定义,这个变量太难记了,我们修改idea的设置实现,提示定义
**我们设置好idea如下图,如果实现了Serializable接口的类没有固定序列号,该类将会出现黄色,alt+enter可以自动生成**

方案书(不推荐)
也可以在实现了`Serializable接口的类中复制,然后修改版本号

序列号不变,如果改变了属性,在文件中也会做出相应的改变

细节2:transient瞬态关键字

这个关键字可以让该属性的属性值不被序列化到文件中

我们在adderss这个属性添加transieent关键字,保证该属性的值不被序列化到文件中,执行结果如下


对总结的第二点的解释

是指把对象序列化到文件中变成乱码后,如果我们修改乱码的内容,就读不出来了,将会报错

序列化流综合练习(读写多个对象)


序列化操作

package com;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

//序列化流和反序列化流的综合练习
public class Practice1 {
    public static void main(String[] args) throws IOException {
        //将自定义的对象序列化到文件中,但是对象的个数不确定,该如何操作呢?
      //序列化多个对象
        Student s1 = new Student("zhangsan",23,"湖北");
        Student s2 = new Student("lisan",24,"江西");
        Student s3 = new Student("wangwu",25,"山东");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("small\\person.txt"));
        oos.writeObject(s1);
        oos.writeObject(s2);
        oos.writeObject(s3);
        oos.close();
    }
}

反序列化操作

package com;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Practice2 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //将序列化的对象反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("small\\person.txt"));
        final Student s1 = (Student) ois.readObject();
        final Student s2 = (Student) ois.readObject();
        final Student s3 = (Student) ois.readObject();
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
    }
}


以上程序可以运行,但是当我们的对象的个数不确定时,以上的程序将寸步难行,不具有通用性

当我们的文件中没有对象了,但是我们还继读取,此时不会返回null,而是会抛出EOFException

解决方案,我们可以知道ArrayList集合也实现了Serializable接口,并且规定了序列号,我们可以把对象添加到集合中,然后序列号一个集合,然后反序列化一个集合

序列化

package com;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;

//序列化流和反序列化流的综合练习
public class Practice1 {
    public static void main(String[] args) throws IOException {
        //将自定义的对象序列化到文件中,但是对象的个数不确定,该如何操作呢?
      //序列化多个对象
        Student s1 = new Student("zhangsan",23,"湖北");
        Student s2 = new Student("lisan",24,"江西");
        Student s3 = new Student("wangwu",25,"山东");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("small\\person.txt"));
        ArrayList<Student> list = new ArrayList<>();
        list.add(s1);
        list.add(s2);
        list.add(s3);
        oos.writeObject(list);

        oos.close();
    }
}

反序列化

package com;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;

public class Practice2 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //将序列化的对象反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("small\\person.txt"));
        final ArrayList<Student> list  =(ArrayList<Student>) ois.readObject();//反序列化一个集合
        for (Student student : list) {
            System.out.println(student);
        }
    }
}

打印流

打印流io体系框架

打印流不能读只能写,所有打印流是输出流
打印流的特点

字节打印流


在参数中String encoding 和Charset charset都表示字符编码,只是形式不一样

package com;

import java.io.*;
import java.nio.charset.Charset;

public class PrintStreamDemo1 {
    public static void main(String[] args) throws IOException {
        /*
        构造方法:
        public PrintStream(OutputStream/File/String)关联字节输出流/文件/字符串创建对象
        public PrintStream(String fileName,Charset charset)指定字符编码
        public PrintStream(OutputStream out,boolean autoFulsh)自动刷新
        public PrintStream(OutputStream out,boolean autoFulsh ,String encoding)指定字符编码且自动刷新
        成员方法:
        public void write(int b)常规方法:和之前一样按字节写出
        public void println(XXX xx)特有方法:打印任意数据,自动刷新 自动换行
        public void print(XXX xx)特有方法:打印任意数据,不换行
        public void printf(String format,Object...args)特有方法:带有占位符的打印语句,不换行
         */
        //1.创建字节打印流对象
        PrintStream ps = new PrintStream(new FileOutputStream("small\\ff.txt"),true,"UTF-8");
        //2.写入数据到文件中
        ps.println("hello");
        ps.println("the");
        ps.println("world");
        ps.print("天才");
        ps.close();
    }
}

数据被原样写入到文件中

字符打印流

字符打印流和字节打印流相比,其底层有缓冲区,想要自动刷新需要开启


和字节打印流基本一样

关闭资源,缓冲区里面的数据会刷新显示到页面中,不关流是为了测试是否开启了自动刷新

package com;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

public class PrintWriter1 {
    public static void main(String[] args) throws IOException {
        /*
        构造方法:
            public PrintWriter(Writer/File/String) 关联字符输出流/文件/文件路径
            public PrintWriter(String fileName,Charset charset) 指定字符编码
            public PrintWriter(Writer,boolean autoFlush)自动刷新
            public PrintWriter(Writer out,boolean autoFlush,String encoding) 指定编码且自动刷新
            成员方法:
            public void write(int b)常规方法,规则和之前一样,按照字节写出
            public void println(XXX xx)特有方法:打印任意数据,自动刷新,自动换行
            public void print(XXX xx)特有方法:打印任意数据,不换行
            public void printf(String format,Object...args)特有方法:带有占位符打印语句,不换行
         */
        //1.创建字符打印流对象
        PrintWriter pw = new PrintWriter(new FileWriter("small\\ff.txt"),true);
        //2.写入数据
        pw.println("天才在左");
        pw.println("疯子在右");
        pw.print(34);
        pw.printf("%s杀了%s","你","他");
        pw.close();

    }
}

打印流的应用场景

package com;

import java.io.PrintStream;

public class PrintWrinter2 {
    public static void main(String[] args) {
        //打印流的应用场景
      //获取打印流的对象,此打印流在虚拟机启动的时候,由虚拟机创建,默认指向控制台
        //这是特殊的打印流,系统中的标准输出流,是不能关闭的,在系统中是唯一的
       PrintStream ps =  System.out;
       //调用打印流中的println方法
       ps.println(123);
       
       //ps.close();不能关闭,关闭后,后面的将不能打印
        //out是System类中的一个静态变量,由类名直接调用,而out属于PrintStream打印流类型,可以调用里面的打印方法
        //该打印流不是指向文件,而是默认指向控制台
    }
}

压缩流和解压缩流

应用场景:当我们的传输文件比较大,可以先压缩再传输

当得到一个压缩包,需要先解压


压缩流和解压缩流在io中的框架体系

解压缩流

要求:压缩包的类型必须是zip


先进行解压,然后读取,最后写入

解压的具体代码

package com;

import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

//解压缩流的本质:把压缩包里面的每一个文件或文件夹读取出来,按照层级拷贝到目的地当中
public class ZipStream1 {
    public static void main(String[] args) throws IOException {


        //准备一个压缩包,进行解压缩到新的文件中
        //1.创建文件表示压缩包的路径
        File file = new File("D:\\aa.txt");
        //2.创建一个文件表示解压后的目的地
        
        
        File dest = new File("D:\\");//解压后的文件夹的路径可以只写父级路径,java会创建和压缩包相同名称的文件夹
        //也可以写完整

        unzip(file, dest);

    }

    //定义一个方法表示解压操作
    public static void unzip(File src, File dest) throws IOException {
        //1.用解压缩流读取压缩包的数据 2.使用输出流将数据写入到新的文件中

        //1.创建解压缩流的对象并关联压缩包
        ZipInputStream zis = new ZipInputStream(new FileInputStream("D:\\aa.zip"));
        //2.进行解压
        //每一个文件或者文件夹都是一个ZipEntry对象,
        //getNextEntry获取当前文件或文件夹的对象
        //在getNextEntry方法的底层已经包含递归,可以把压缩包的所有文件和文件夹都获取出来
        //getNextEntry方法当获取不到的时候,会返回null
        ZipEntry entry = null;
        while ((entry = zis.getNextEntry()) != null) {//获取压缩包里面每一个文件或文件夹
            System.out.println(entry);

            if (entry.isDirectory()) {//只能判断是不是文件夹
                //如果是文件夹 需要在目的地创建一个同样的文件夹
                File file = new File(dest, entry.toString());
                file.mkdirs();//创建该文件夹
            } else {
                //如果是文件,需要读取到压缩包中的文件,并把他存放到目的地dest文件夹中(按照层级目录进行存放)

                //用来写入数据
                FileOutputStream fos = new FileOutputStream(new File(dest, entry.toString()));

                int c;
                while ((c = zis.read()) != -1) {
                    fos.write(c);
                }
                fos.close();
                //表示在压缩包中的一个文件处理完毕了
                zis.closeEntry();
            }
        }
       zis.close();
    }
}

对于父级路径和子级路径的新理解:

对于一个文件或者文件夹的父级路径,不仅仅是去除文件名或文件夹名的路径,从中间截断也可以,如下图

package com;

import java.io.File;
import java.io.IOException;

//关于父级路径和子级路径的判断
public class Testc {
    public static void main(String[] args) throws IOException {
        //以下的也属于父级路径也子级路径
        File file = new File("D:\\aa\\aa\\dd","gg\\name.txt");
        file.createNewFile();
    }
}

压缩包里面的每一个文件或者文件夹其实是一个Zipentry对象

压缩流(压缩单个文件)


压缩本质:把每一个文件/文件夹看成Zipentry对象放到压缩包中

package com;

import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;


//利用压缩流压缩单个文件
public class ZipStream2 {
    public static void main(String[] args) throws IOException {
        //把D:\her.txt打包成一个压缩包
        //1.创建对象表示源文件
        File file = new File("D:\\her.txt");
        //2.创建对象表示压缩包的位置
        File dest = new File("D:\\" );
        //3.调用压缩方法进行压缩
        toZip(file,dest);
    }

    public static void toZip(File file,File dest) throws IOException {//参数依次表示源文件和压缩包的位置
        //1.创建压缩流的对象并关联压缩包
       ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest,"her.zip")));
       //2.把原文件打包成Zipentry对象


        ZipEntry entry = new ZipEntry("her.txt");//注意:这个路径不用写盘符D:\\her.txt(可以参见Zipentry的字符串表示)
        //3.把Zipentry对象放到压缩包中
        zos.putNextEntry(entry);
      //以上程序只是把文件打包成了压缩包,但是还没有进行内容传输
        //4.把file里面的内容写到压缩包中
        FileInputStream fis = new FileInputStream(file);//用于读出数据
        int c;
        while ((c = fis.read()) != -1){
            zos.write(c);
        }

      fis.close();
        zos.closeEntry();
        zos.close();

    }
}

压缩流(压缩文件夹)

关于ZipEntry entry = new ZipEntry("her.txt")的解析

该构造方法可以把her.txt变成ZipEntry的对象,然后把他打包成压缩包,但当我们把her.txt化成了cc\her.txt,发现压缩包多了cc文件夹

可以得出结论:构造方法里面的参数既表示被压缩文件的路径,也表示压缩包的目录

注意:对于压缩的过程有疑问,可能学的不扎实,特此标记

package com;


import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

//利用压缩流压缩一个文件夹
public class ZipStream3 {
    public static void main(String[] args) throws IOException {
        //1.创建一个对象表示要压缩的文件夹
        File src = new File("D:\\aa");
        //2.创建File表示压缩包放在哪里(压缩包的父级路径)
        File destParent = new File(src.getParent());
        //3.创建对象表示压缩包的路径
        File dest = new File(destParent,src.getName()+".zip");
        System.out.println(dest);

        //以上这样操作,在修改文件夹的路径时,压缩包的路径可以跟着一起修改

        //4.创建压缩流并关联压缩包
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dest));
        //5.获取src里面的每一个文件,变成ZipEntry对象,放入到压缩包中
         toZip(src,zos,src.getName());

        //6.释放资源
        zos.close();
    }
    /*
    作用:获取src里面每一个文件,变成ZipEntry对象,放入到压缩包当中
    参数一:数据源
    参数二:压缩流
    参数三:压缩包内部路径
     */
    public static void toZip(File src,ZipOutputStream zos ,String name) throws IOException {
        //1.进入src文件夹
        final File[] files = src.listFiles();//进入文件夹
        //2.遍历数组
        for (File file : files) {
            if(file.isFile()){
                //判断--文件,变成ZipEntry对象,放入到压缩包中
                ZipEntry entry = new ZipEntry(name+"\\"+file.getName());//这个路径需要自己好好想象
                zos.putNextEntry(entry);
                //读取文件中的数据,写到压缩包
                FileInputStream fis = new FileInputStream(file);
                int b;
                while ((b = fis.read()) != -1){
                    zos.write(b);
                }
                fis.close();
                zos.closeEntry();
            }else {
                //---文件夹,进行递归处理
                toZip(file,zos,name+"\\"+file.getName());
            }

        }

    }
}

常见工具包--Commons-io

以后在开发中专门创建一个文件夹lib用来存放第三方发jar包

Commens-io的使用步骤

  • 文件工具类(里面都是和文件或者文件夹有关的方法)

流的工具类

2个复制文件夹的方法不一样,自己体会

Hutool工具包


FileWriter和FileReader和java中的类重名了,使用的时候要注意导包

Hubtool工具包重要网址

课后寻找Hutool和Commens的jar包

笔记

1.file方法创建file对象:可变参数,可以用字符串拼接路径

2.touch根据参数创建文件:之前的creatFile方法如果父级路径不存在将会报错,这个方法如果父级路径没有会帮你一起创建

关于Hutool和Commens工具包以后在深入掌握

posted @ 2023-02-08 10:42  一往而深,  阅读(104)  评论(0编辑  收藏  举报