JavaseLearn23-IO流
JavaseLearn23-IO流
1. 什么是IO流?
I:Input,通过输入流将文件从硬盘输入到内存。
O:Output,通过输出流将文件从内存输出到硬盘。
通过IO可以完成硬盘文件的读和写。

2. IO流的分类
有多种分类方式:
1.一种方式是按照流的方向进行分类:
以内存为参照物
- 往内存中去,叫做输入(Input),或者叫做读(Read)。
- 从内存中出,叫做输出(Output),或者叫做写(Write)。
2.另一种是按照读取数据方式不同进行分类:
- 按照字节的方式读取数据的流,一次读取1个字节byte。等同于8个二进制位。
- 这种流是万能流,什么类型的文件都能读取。包括文本、图片、声音、视频等文件。
- 假设文件File.TXT,采用字节流的话是这样读的:
- File.TXT中数据:f啊巴bbc
- 第一次读:一个字节,刚好读到'f' 。('f'字符在Windows系统中占用1个字节)
- 第二次读:一个字节,刚好读到'啊'字符的一半。('啊'字符在Windows系统中占用2个字节)
- 第三次读:一个字节,刚好读到'啊'字符的另一半。
 
 
- 按照字符的方式读取数据的流,一次读取一个字符char。
- 这种流是为了方便读取普通文本文件而存在的。
- 这种流不能读取:图片、声音、视频等文件,只能读取纯文本文件,连word文件都不能读取。
- 假设文件File.TXT,采用字符流的话是这样读的:
- File.TXT中数据:f啊巴bbc
- 第一次读:一个字符,刚好读到'f'字符('f'字符在Windows系统中占用1个字节)
- 第二次读:一个字符,刚好读到'啊'字符('啊'字符在Windows系统中占用2个字节)
 
 
综上,流的分类:
输入流、输出流.
字节流、字符流.
3. 流的四大家族
3.1 四大家族的首领
- 字节输入流:java.io.InputStream
- 字节输出流:java.io.OutputStream
- 字符输入流:java.io.Reader
- 字符输出流:java.io.Writer
四大家族的首领都是抽象类(abstract class)。
3.2 close和flush方法
1.所有的流都实现了java.io.Closeable接口
- 所有流都是可关闭的,都有close方法。
- 流是一个管道,连通着内存和硬盘,用完后一定要关闭,不然会耗费(占用)很多的资源。
2.所有的输出流都实现了java.io.Flushable接口
- 所有输出流都是可刷新的,都有flush方法。
- 在输出流输出完毕后,一定要记得调用flush()方法刷新一下,表示将通道里剩余未输出的数据强行输出完(疏通管道)。
- 如果没有flush()方法,可能会丢失数据。
注意:
- 在java中只要是类名以Stream结尾的,都是字节流。
- 以"Reader"或者"Writer"结尾的都是字符流。
4. 需要掌握的流
4.1 java.io包下需要掌握的流有16个
1.文件专属:
- java.io.FileInputStream
- java.io.FileOutputStream
- java.io.FileReader
- java.io.FileWriter
2.转换流:(将字节流转换为字符流)
- java.io.InputStreamReader
- java.io.OutputStreamWriter
3.缓冲流专属:
- java.io.BufferedReader
- java.io.BufferedWriter
- java.io.BufferedInputStream
- java.io.BufferedOutputStream
4.数据流专属:
- java.io.DateInputStream
- java.io.DataOutputStream
5.标准输出流:
- java.io.PrintWriter
- java.io.PrintStream
6.对象专属流:
- java.io.ObjectInputStream
- java.io.ObjectOutputStream
4.2 文件流
4.2.1 FileInputStream
文件字节输入流,万能流,任何类型的数据都可以采用这个流来读。
采用字节的方式完成输入的操作,完成读的操作:(硬盘--->内存)
4.2.1.1 代码实现读文件中的数据
在工程目录下创建一个IDEAPathTest.TXT,写入abcdef,然后通过FileInputStream来读该文件中的数据:
package com.java.input;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
 * @Author: TSCCG
 * @Date: 2021/07/09 21:33
 * FileInputStream
 */
public class IoDemo04 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            //创建输入流对象
            //相对路径,位于项目根目录,也可以传入绝对路径
            fis = new FileInputStream("IDEAPathTest.TXT");
            //创建一个容量为4的byte数组,表示可以一次读4个字节到内存里去
            byte[] bytes = new byte[4];
            //一次读取到的字节数量
            int readCount = 0;
            //当读取不到字节时,会返回-1,此时结束循环
            while ((readCount = fis.read(bytes)) != -1) {
                //将byte数组转换为String字符串,读多少,转多少
                System.out.print(new String(bytes,0,readCount));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //在finally里关闭流
            //防止空指针异常
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
abcdef
注意:
在IDEA中,默认的当前目录是项目工程根目录。
4.2.1.2 FileInputStream中的其他常用方法
1.available:返回流当中剩余未读字节的数量
2.skip:跳过几个字节不读
package com.java.input;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
 * @Author: TSCCG
 * @Date: 2021/07/09 18:36
 * available:返回流当中剩余未读字节的数量
 * skip:跳过几个字节不读
 */
public class IoDemo03 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("IDEAPathTest.TXT");
            //这种方法能够一次性读所有的字节,但不适于较大的文件,因为byte数组不能太大
//            byte[] bytes = new byte[fis.available()];//6
//            int readDate = fis.read(bytes);
//            System.out.println(new String(bytes));
            fis.skip(3);//跳过abc
            System.out.println(fis.read());//100
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
4.2.2 FileOutputStream
输出流
通过FileInputStream来输出数据,在工程目录下创建一个Output.TXT的txt文件,写入Hello World
package com.java.output;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
 * @Author: TSCCG
 * @Date: 2021/07/09 19:05
 */
public class OutputDemo01 {
    public static void main(String[] args) {
        FileOutputStream fos = null;
        try {
            //每次输出流都会清空文件里的所有数据,重新写入。添加true可以阻止清空,也就是在原数据后面追加写入
            fos = new FileOutputStream("Output.TXT",true);
            //创建字符串
            String str1 = "Hello World";
            //将字符串转化为byte数组
            byte[] bytes = str1.getBytes();
            //将数据写入Output.TXT文件
            fos.write(bytes);
            //刷新
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4.2.3 字节流实现文件的拷贝
通过FileInputStream和FileOutputStream将a目录中的文件拷贝到b目录中。
这种方式能够拷贝所有类型的文件。
package com.java.filecopy;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
 * @Author: TSCCG
 * @Date: 2021/07/09 19:41
 * 拷贝文件
 */
public class FileCopyDemo01 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("FileCopyTest/a/01进程与线程的关系.png");
            fos = new FileOutputStream("FileCopyTest/b/进程与线程的关系.png");
            //1024个字节是1kb,(1024 * 1024)等于1MB,一次读1Mb
            byte[] bytes = new byte[1024 * 1024];
            //读取字节的数量
            int readDate = 0;
            while ((readDate = fis.read(bytes)) != -1) {
                //读多少,写多少
                fos.write(bytes,0,readDate);
            }
            //刷新
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //分开try,避免影响各自流的关闭
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


4.2.4 FileWriter
跟FileOutputStream大同小异,实现写文件的步骤是一致的,但是只能写文本文件。
package com.java.writer;
import java.io.FileWriter;
import java.io.IOException;
/**
 * @Author: TSCCG
 * @Date: 2021/07/10 10:19
 */
public class FileWriterDemo01 {
    public static void main(String[] args) {
        FileWriter writer = null;
        try {
            writer = new FileWriter("FileCopyTest/a/字符测试.TXT");
            String str = "ab哈哈欧拉哈哈哈哈";
            char[] chars = str.toCharArray();
            writer.write(chars);
            //换行
            writer.write("\n");
            //只写一部分
            writer.write(chars,1,4);
            writer.write("\n");
            writer.write("后续字符");
            writer.write("\n");
            writer.write("HelloWorld");
            //刷新
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4.2.5 FileReader
与FileInputStream大致一样,但只能读文本文件
package com.java.reader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
/**
 * @Author: TSCCG
 * @Date: 2021/07/10 10:16
 */
public class FileReadDemo01 {
    public static void main(String[] args) {
        FileReader reader = null;
        try {
            reader = new FileReader("FileCopyTest/a/字符测试.TXT");
            char[] chars = new char[4];
            int readCount = 0;
            while ((readCount = reader.read(chars)) != -1) {
                System.out.print(new String(chars,0,readCount));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
ab哈哈欧拉哈哈哈哈
b哈哈欧
后续字符
HelloWorld
4.2.6 字符流实现普通文本文件的拷贝
能够被记事本正常打开的文件都是普通文本文件。
word文件不是普通文本文件。
package com.java.filecopy;
import java.io.*;
/**
 * @Author: TSCCG
 * @Date: 2021/07/10 09:55
 */
public class FileCopyDemo02 {
    public static void main(String[] args) {
        FileReader in = null;
        FileWriter put = null;
        try {
            in = new FileReader("FileCopyTest/a/字符测试.TXT");
            put = new FileWriter("FileCopyTest/b/02字符测试.TXT");
            char[] chars = new char[1024 * 1024];
            int readCount = 0;
            while ((readCount = in.read(chars)) != -1) {
                put.write(chars,0,readCount);
            }
            put.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (put != null) {
                try {
                    put.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4.3 缓冲流
4.3.1 BufferedReader
- 自带缓冲区的流,使用时不需要自定义char数组,可以直接读取一整行,但不会读取换行符。
- 在创建缓冲流对象时,需要传入一个字符输入流。
- 我们可以创建一个FileReader流对象传入。
- 当一个流的构造方法中需要一个流的时候,这个被传入的流叫做:节点流。
- 外部负责包装的流叫做:包装流/处理流。
 
- 如果节点流是字节流就需要用到InputStreamReader将字节流转换成字符流再传入。
- 关闭时只需要关闭最外层的流即可,底层close方法会自动关闭所有流。
package com.java.buffer;
import java.io.*;
/**
 * @Author: TSCCG
 * @Date: 2021/07/10 11:23
 * BufferedReader
 */
public class BufferedReaderDemo01 {
    public static void main(String[] args){
        BufferedReader br = null;
        try {
//            br = new BufferedReader(new FileReader("Output.TXT"));
            br = new BufferedReader(new InputStreamReader(new FileInputStream("Output.TXT")));
//        //读取一整行,但不会读取换行符
//        String str1 = br.readLine();
//        System.out.println(str1);
//        String str2 = br.readLine();
//        System.out.println(str2);
            String s = null;
            while ((s = br.readLine()) != null) {
                System.out.println(s);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    //只需关闭最外层的包装流即可,底层会自动关闭所有流
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
1Hello World
2Hello World
3Hello World
4Hello World
阿巴巴巴阿巴巴
阿巴巴巴阿巴巴
4.3.2 BufferedWriter
也是需要传入一个字符输出流。
创建一个FileWriter流对象传入。
package com.java.buffer;
import java.io.*;
/**
 * @Author: TSCCG
 * @Date: 2021/07/10 11:37
 */
public class BufferedWriterDemo01 {
    public static void main(String[] args) {
        BufferedWriter bw = null;
        try {
//            bw = new BufferedWriter(new FileWriter("Output.TXT",true));
            bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("Output.TXT",true)));
            bw.write("阿巴巴巴阿巴巴");
            bw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
4.4 数据流
数据专属的流。
可以通过DataOutputStream将数据连同数据类型一同写入文件中。
这个文件不是普通文本文档,用记事本打不开,可以看作是一个加密后的文件。
只能通过DataInputStream按照写入的顺序读取数据。
4.4.1 DataOutputStream
将数据及数据类型按照一定顺序写入文件里。
package com.java.data;
import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
 * @Author: TSCCG
 * @Date: 2021/07/10 16:13
 */
public class DataOutputStreamDemo01 {
    public static void main(String[] args) {
        java.io.DataOutputStream dw = null;
        try {
            dw = new java.io.DataOutputStream(new FileOutputStream("FileCopyTest/a/data.TXT"));
            byte b = 100;
            short s = 200;
            int i = 400;
            long l = 800L;
            float f = 3.3F;
            double d = 3.14;
            boolean bl = true;
            char c = 'a';
            dw.writeByte(b);
            dw.writeShort(s);
            dw.writeInt(i);
            dw.writeLong(l);
            dw.writeFloat(f);
            dw.writeDouble(d);
            dw.writeBoolean(bl);
            dw.writeChar(c);
            dw.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (dw != null) {
                try {
                    dw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4.4.2 DataInputStream
数据字节输入流。
DataOutputStream写的文件,只能使用DataInputStream去读。
并且在读的时候需要提前知道写入的顺序。
读的顺序必须要跟写入的顺序一致,才能正常读取顺序。
package com.java.data;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
 * @Author: TSCCG
 * @Date: 2021/07/10 16:31
 * 按写入时的顺序读取数据
 */
public class DataInputStreamDemo01 {
    public static void main(String[] args) {
        DataInputStream dis = null;
        try {
            dis = new DataInputStream(new FileInputStream(("FileCopyTest/a/data.TXT")));
            System.out.println(dis.readByte());
            System.out.println(dis.readShort());
            System.out.println(dis.readInt());
            System.out.println(dis.readLong());
            System.out.println(dis.readFloat());
            System.out.println(dis.readDouble());
            System.out.println(dis.readBoolean());
            System.out.println(dis.readChar());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (dis != null) {
                try {
                    dis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
100
200
400
800
3.3
3.14
true
a
4.5 标准输出流
java.io.PrintStream:标准的字节输出流,默认输出到控制台。
//默认输出到控制台
//联合起来写
System.out.println("HelloWorld");
//分开写
PrintStream ps = System.out;
ps.println("欧拉欧拉欧拉");
HelloWorld
欧拉欧拉欧拉
4.5.1 改变标准输出流输出方向
package com.java.print;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
/**
 * @Author: TSCCG
 * @Date: 2021/07/10 17:26
 * 标准输出流
 */
public class PrintStreamDemo01 {
    public static void main(String[] args) {
        //默认输出到控制台
        System.out.println("HelloWorld");
        //改变输出方向,标准输出流不再输出到控制台,转而输出到Log.TXT文件中
        try {
            PrintStream ps2  = new PrintStream(new FileOutputStream("Log.TXT"));
            //改变输出方向
            System.setOut(ps2);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } //标准输出流不需要手动关闭流
        //输出
        System.out.println("12345");
        System.out.println("阿巴阿巴阿巴");
    }
}

4.5.2 记录日志
实现记录日志的功能:
记录具体时间及操作,并写入Log.TXT文件里。
实现类Logger:
package com.java.print;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
 * @Author: TSCCG
 * @Date: 2021/07/10 17:55
 * 记录日志
 */
public class Logger {
    public static void log(String arg) {
        try {
            //设置为可追加:true
            PrintStream ps = new PrintStream(new FileOutputStream("Log.TXT",true));
            System.setOut(ps);
            //获取系统当前时间
            Date nowDate = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss SSS");
            System.out.println(sdf.format(nowDate) + ":" + arg);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}
测试类:
package com.java.print;
/**
 * @Author: TSCCG
 * @Date: 2021/07/10 18:01
 */
public class LoggerTest01 {
    public static void main(String[] args) throws InterruptedException {
        Logger.log("在河南登录了账号");
        Thread.sleep(1000);
        Logger.log("在北京登录了账号");
        Thread.sleep(1000);
        Logger.log("在上海登录了账号");
    }
}
运行:

5. File类
5.1 File概述
1.File类和四大家族没有关系,所以File类不能完成文件的读和写。
2.File对象代表什么?
- 文件和目录名的抽象表示形式。
- "C:\Program Files (x86)\Tencent\QQ\Bin\QQScLauncher.exe"这是一个File对象。
- "C:\Program Files (x86)\Tencent\QQ\Bin"这也是一个File对象。
- 一个File对象可能是一个文件,也有可能是一个目录。
- File只是一个路径名的抽象表示形式。
5.2 File常用方法
5.2.1 判断文件或目录是否存在
调用exists()方法来判断:
//创建File对象,传入目标文件或目录的路径
File file = new File("D:\\FileClassTest");
//判断D:\FileClassTest是否存在
System.out.println(file.exists());//true
5.2.2 创建文件和目录
1.创建文件:
File file = new File("D:\\FileClassTest");
//如果D:\FileClassTest不存在,以文件的形式创建出来
if (! file.exists()) {
    try {
        //创建文件
        file.createNewFile();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

2.创建目录:
1)创建单级目录
File file = new File("D:\\FileClassTest");
//如果D:\FileClassTest不存在,以目录的形式创建出来
if (! file.exists()) {
    file.mkdir();
}

2)创建多级目录
File file2 = new File("D:\\FileClassTest\\ClassTest\\Test");
if (!file2.exists()){
    file2.mkdirs();
}

5.2.3 获取文件路径
File file3 = new File("D:\\WeChat\\WeChat.exe");
//获取文件父路径
String parentPath = file3.getParent();
System.out.println(parentPath);
//获取file3的父目录对象
File file4 = file3.getParentFile();
//获取绝对路径
System.out.println("绝对路径:" + file4.getAbsolutePath());
//获取相对路径文件的绝对路径
File file5 = new File("Log.TXT");
System.out.println(file5.getAbsolutePath());
D:\WeChat
绝对路径:D:\WeChat
D:\code\Java\IdeaJava\JavaLearn\Log.TXT
5.2.4 获取文件名
File file1 = new File("D:\\WeChat\\WeChat.exe");
//获取文件名
System.out.println(file1.getName());
WeChat.exe
5.2.5 获取当前目录下所有子文件
listFiles方法可以获取当前目录下所有的子文件,返回一个File数组
package com.java.file;
import java.io.File;
/**
 * @Author: TSCCG
 * @Date: 2021/07/10 20:29
 * 获取当前目录下所有的子文件:listFiles方法
 */
public class FileDemo02 {
    public static void main(String[] args) {
        File file1 = new File("D:\\code");
        //获取所有子文件对象,返回一个File数组
        File[] files = file1.listFiles();
        System.out.println("------获取所有子文件名------");
        for (File file : files) {
            System.out.println(file.getName());
        }
        System.out.println("------获取所有子文件路径------");
        for (File file : files) {
            System.out.println(file.getAbsolutePath());
        }
    }
}
------获取所有子文件名------
desktop.ini
Java
Markdown使用.md
Python
Qt
前端
------获取所有子文件路径------
D:\code\desktop.ini
D:\code\Java
D:\code\Markdown使用.md
D:\code\Python
D:\code\Qt
D:\code\前端

5.2.6 判断是否是一个文件或目录
File file1 = new File("D:\\WeChat\\WeChat.exe");
//判断是否是一个文件
System.out.println(file1.isFile());//true
//判断是否是一个目录
System.out.println(file1.isDirectory());//false
5.2.7 获取文件最后一次的修改时间
File file1 = new File("D:\\WeChat\\WeChat.exe");
long t1 = file1.lastModified();
Date date = new Date(t1);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
System.out.println("WeChat.exe的最后修改时间为:" + sdf.format(date));

5.3 拷贝目录
将源目录整个拷贝到目标路径下。
源目录:D:\code\Java\楠哥课件
目标目录:F:\
思路:
- 
1.通过递归遍历所有的File。 
- 
2.如果遍历到目录,则在目标路径下创建同名的目录,继续递归, 
- 
3.如果遍历到文件,则拷贝到目标目录下,并终止递归。 
- 
private static void copyDir(File srcFile, File destFile) { if (srcFile.isFile()) { //拷贝文件操作 //如果是文件则结束递归 return; } //拿到所有子文件 File[] files = srcFile.listFiles(); for (File file: files) { if (file.isDirectory()) { //拷贝目录操作 } //递归调用,遍历所有子文件 copyDir(file,destFile); } }
5.3.1 通过递归遍历所有的File
private static void copyDir(File srcFile, File destFile) {
    //拿到所有子文件
    File[] files = srcFile.listFiles();
    for (File file: files) {
        //打印所有的File名
        System.out.println(file.getName());
        //递归调用,遍历所有子文件
        copyDir(file,destFile);
    }
}
5.3.2 如果遇到目录,则在目标路径下创建目录,继续递归
这里我们需要得到源路径和拷贝后的目标路径才可以实现目录的拷贝。
源路径:直接调用getAbsolutePath()方法即可获得。
目标路径:我们要将目录从D盘拷贝到F盘根目录下,这就需要将源路径第三个位置之后的路径截取出来,然后和F盘的路径拼接起来,从而得到目标路径。如:
源路径:D:\code\Java\楠哥课件
拼接后的目标路径:F:\code\Java\楠哥课件
- 这里需要注意的是,如果目标目录是多级目录,且最后带有"\",如"F:\a\b\c\d\"
- 直接调用getAbsolutePath()方法的话会丢失最后的"\"
- 如此拼接出来的目标路径就会出错,如:F:\a\b\c\dcode\Java\楠哥课件
private static void copyDir(File srcFile, File destFile) {
    //拿到所有子文件
    File[] files = srcFile.listFiles();
    for (File file: files) {
        if (file.isDirectory()) {
            //源路径
            String srcDir = file.getAbsolutePath();
            //目标路径
            String destDir = (destFile.getAbsolutePath().endsWith("\\") ?
                              destFile.getAbsolutePath() : destFile.getAbsolutePath()  + "\\")
                + srcDir.substring(3);
            //新建目录
            File newDir = new File(destDir);
            if (!newDir.exists()) {
                newDir.mkdirs();
            }
        }
        //递归调用,遍历所有子文件
        copyDir(file,destFile);
    }
}
5.3.3 如果遍历到文件,则拷贝到目标路径下,并终止递归
遍历到文件,同样需要获取到源路径和目标路径,和拷贝目录那里大同小异。
private static void copyDir(File srcFile, File destFile) {
    //拷贝文件
    if (srcFile.isFile()) {
        FileInputStream in = null;
        FileOutputStream out = null;
        try {
            //源路径D:\\code\\Java\\楠哥课件
            in = new FileInputStream(srcFile);
            //目标路径:F:\\code\\Java\\楠哥课件
            out = new FileOutputStream((destFile.getAbsolutePath().endsWith("\\") ?
                                        destFile.getAbsolutePath() : destFile.getAbsolutePath()  + "\\")
                                       + srcFile.getAbsolutePath().substring(3));
            byte[] bytes = new byte[1024 * 1024];
            int readCount = 0;
            //边读边写
            while ((readCount = in.read(bytes)) != -1) {
                out.write(bytes,0,readCount);
            }
            //刷新
            out.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        //如果是文件则结束递归
        return;
    }
    //拿到所有子文件
    File[] files = srcFile.listFiles();
    for (File file: files) {
        //拷贝目录操作
        //递归调用,遍历所有子文件
        copyDir(file,destFile);
    }
}
5.3.4 完整代码:
package com.java.file;
import java.io.*;
/**
 * @Author: TSCCG
 * @Date: 2021/07/11 15:06
 * 拷贝目录
 */
public class IndexCopyDemo02 {
    public static void main(String[] args) {
        //拷贝源
        File srcFile = new File("D:\\code\\Java\\楠哥课件");
        //拷贝目标
        File destFile = new File("F:\\");
        copyDir(srcFile,destFile);
    }
    /**
     * 拷贝目录
     * @param srcFile 拷贝源
     * @param destFile 拷贝目录
     */
    private static void copyDir(File srcFile, File destFile) {
        //拷贝文件
        if (srcFile.isFile()) {
            FileInputStream in = null;
            FileOutputStream out = null;
            try {
                //源路径D:\\code\\Java\\楠哥课件
                in = new FileInputStream(srcFile);
                //目标路径:F:\\code\\Java\\楠哥课件
                out = new FileOutputStream((destFile.getAbsolutePath().endsWith("\\") ?
                        destFile.getAbsolutePath() : destFile.getAbsolutePath()  + "\\")
                        + srcFile.getAbsolutePath().substring(3));
                byte[] bytes = new byte[1024 * 1024];
                int readCount = 0;
                //边读边写
                while ((readCount = in.read(bytes)) != -1) {
                    out.write(bytes,0,readCount);
                }
                //刷新
                out.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (out != null) {
                    try {
                        out.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            //如果是文件则结束递归
            return;
        }
        //拿到所有File
        File[] files = srcFile.listFiles();
        for (File file: files) {
            //创建目录
            if (file.isDirectory()) {
                //源路径
                String srcDir = file.getAbsolutePath();
                //目标路径  
                String destDir = (destFile.getAbsolutePath().endsWith("\\") ?
                        destFile.getAbsolutePath() : destFile.getAbsolutePath()  + "\\")
                        + srcDir.substring(3);
                //新建目录
                File newDir = new File(destDir);
                if (!newDir.exists()) {
                    newDir.mkdirs();
                }
            }
            //递归调用,遍历所有File
            copyDir(file,destFile);
        }
    }
}

6. 序列化
6.1 序列化原理

6.2 实现序列化
参与序列化和反序列化的对象,必须实现Serializable接口。
注意:通过查看源码,发现Serializable接口只是一个标志接口:
public interface Serializable {
}
接口中什么东西都没有。但起了标识的作用:java虚拟机看到了这个类实现了Serializable接口,会自动生成一个序列化版本号。这点我们待会儿再讲。
将一个java对象序列化到一个文件中:
package com.java.object;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
 * @Author: TSCCG
 * @Date: 2021/07/11 17:36
 * 序列化
 */
public class ObjectOutputStreamDemo01 {
    public static void main(String[] args) {
        //创建Student对象
        Student stu1 = new Student("张三",20);
        ObjectOutputStream oos = null;
        try {
            //创建ObjectOutputStream对象输出流
            oos = new ObjectOutputStream(new FileOutputStream("FileCopyTest\\a\\Students.TXT"));
            //序列化对象
            oos.writeObject(stu1);
            //刷新
            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
/**
* Student类
* 必须实现Serializable才能将该类创建的对象序列化
*/
class Student implements Serializable {
    private String name;
    private int age;
    public Student() {
    }
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

6.3 实现反序列化
将硬盘里的数据恢复到内存中,恢复为java对象。
package com.java.object;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
/**
 * @Author: TSCCG
 * @Date: 2021/07/11 17:36
 */
public class ObjectInputStreamDemo01 {
    public static void main(String[] args) {
        ObjectInputStream ois = null;
        try {
            //创建ObjectInputStream对象输入流
            ois = new ObjectInputStream(new FileInputStream("FileCopyTest\\a\\Students.TXT"));
            //进行反序列化,返回的是一个学生对象
            Object obj = ois.readObject();
            System.out.println(obj instanceof Student);//true
            System.out.println(obj);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ois != null) {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
true
Student{name='null', age=20}
6.4 序列化多个对象
一次序列化多个对象的时候需要用到集合。
将对象放进一个List集合中,然后序列化集合对象。
注意:参与序列化的List集合以及集合中的元素类型Student都需要实现Serializable接口。
package com.java.object;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
 * @Author: TSCCG
 * @Date: 2021/07/11 17:36
 * 序列化对个对象
 */
public class ObjectOutputStreamDemo01 {
    public static void main(String[] args) {
        List<Student> list = new ArrayList<>();
        list.add(new Student("张三",20));
        list.add(new Student("李四",22));
        list.add(new Student("王五",30));
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("FileCopyTest\\a\\Students.TXT"));
            oos.writeObject(list);
            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
class Student implements Serializable {
    private String name;
    private int age;
    public Student() {
    }
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
反序列化:
package com.java.object;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.List;
/**
 * @Author: TSCCG
 * @Date: 2021/07/11 17:36
 * 反序列化多个对象
 */
public class ObjectInputStreamDemo01 {
    public static void main(String[] args) {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("FileCopyTest\\a\\Students.TXT"));
            //返回的是一个List集合
//            Object obj = ois.readObject();
//            System.out.println(obj instanceof List);//true
            //将返回的Object对象强转成List对象
            List<Student> userList = (List<Student>)ois.readObject();
            for (Student student : userList) {
                System.out.println(student);
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ois != null) {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
Student{name='张三', age=20}
Student{name='李四', age=22}
Student{name='王五', age=30}
6.5 transient关键字
假如说在Student类里,我不希望name这个属性被序列化,就可以使用transient关键字来修饰name。
transient修饰的属性不参与序列化
class Student implements Serializable {
    //transient修饰的属性不参与序列化
    private transient String name;
    private int age;
    public Student() {
    }
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
重新序列化,反序列化:
Student{name='null', age=20}
Student{name='null', age=22}
Student{name='null', age=30}
6.6 序列化版本号
6.6.1 序列化版本号有什么用?
Java虚拟机看到一个类实现Serializable接口之后,会自动在这个类里生成一个序列化版本号。
那么序列化版本号有什么用呢?
首先,我们要知道java语言中是怎么区分类的:
- 通过类名进行对比,如果类名不一样,那么肯定不是同一个类。
- 如果类名一样,那么此时就靠序列化版本号进行区分。
比如说有两个人写了两个类名一致的类:
- 张三:com.java.test.Student implements Serializable
- 李四:com.java.test.Student implements Serializable
但这两个类在本质还是不同的。
此时,因为两个类都实现了Serializable接口,都有各自默认的序列化版本号。
java虚拟机通过序列化版本号就可以区分这两个类。
这是自动生成序列化版本号的好处。
结论:序列化版本号可用于区分两个不同的类。
6.6.2 自动生成序列化版本号的弊端
假设我们上面的序列化Student对象程序是十年之前写的。期间,Student这个类源码改动了。
class Student implements Serializable {
    private String name;
    private int age;
    //新添加的属性
    private String address;
    
    public Student() {
    }
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
源码改动后,需要重新编译,编译之后生成了全新的字节码文件。
并且class文件再次运行时,java虚拟机自动生成的序列化版本号也会发生相应的改变。
那么现在,我们在没有将对象重新序列化的前提下,将文件中存储的十年前序列化的数据反序列化,程序就会报错:
java.io.InvalidClassException:
      com.java.object.Student;
      local class incompatible:
           stream classdesc serialVersionUID = -371881081229791201(十年前的序列化版本号)
           local class serialVersionUID = 7498648349082775721(现在的序列化版本号)
虚拟机对比序列化版本号,发现不一样,就认定我们十年前写的Student类和我们现在改动过的Student类不是同一个类,我们就无法读取十年前存入文件中的数据。
总结:
- 这种自动生成的序列化版本号的优点是:
- 可以区分同名类。
 
- 这种自动生成的序列化版本号的缺点是:
- 一旦代码确定之后,就不能进行后续的修改。
- 如果修改,必然会重新编译,生成全新的序列化版本号。
- 此时,java虚拟机就会认为这是一个全新的类。(负面)
 
结论:凡是一个类实现了Serializable接口,建议给该类一个固定不变的序列化版本号。如此,即使过了很长的时间,源码更改了,只要序列化版本号没有改变,java虚拟机就认为还是最初的那个类。
class Student implements Serializable {
    //建议手动的给类写一个固定不变的序列化版本号
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    //新添加的属性
    private String address;
    
    public Student() {
    }
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
7. IO和Properties联合使用
IO流:文件的读和写
Properties:是一个Map集合,key和value都是String类型的。
现在创建一个无后缀的文件"test",在里面写入:
username=TSCCG
password=123456
然后编写一个IO+Properties联合使用的程序,读取该文件中的数据。
Properties是一个Map集合,key和value都是String类型,想将test文件里的数据加载到Properties对象之中。
package com.java.object;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;
/**
 * @Author: TSCCG
 * @Date: 2021/07/11 22:10
 * IO + Properties的联合使用
 */
public class IoPropertiesDemo01 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            //创建输入流
            fis = new FileInputStream("FileCopyTest\\a\\test");
            //创建Map集合
            Properties pro = new Properties();
            //调用Properties对象的load方法将文件里的数据加载到Map集合当中。其中等号左边做key,等号右边做value
            pro.load(fis);
            //通过key获取value
            String str1 = pro.getProperty("username");
            String str2 = pro.getProperty("password");
            System.out.println(str1);
            System.out.println(str2);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
TSCCG
123456
假如此时,我们将文件里的数据替换为:
username=皮皮虾
password=666666
程序无需更改,重新运行:
皮皮虾
666666
如此,程序无需重新编译即可拿到了不同的数据。
在以后的开发中,如果遇到经常改变的数据,可以单独写到一个文件中,使用程序动态读取。
将来只需要修改这个文件的内容,java代码不需要改动,不需要重新编译,服务器也不需要重启,即可拿到动态的信息。
类似上述机制的这种文件被称为配置文件。
并且当配置文件中的内容格式是:
key1=value1
key2=value2
的时候,我们把这种配置文件叫做属性配置文件。
java规范中有要求:属性配置文件建议以 ".properties"的后缀结尾,但不是必须的。
这种以".properties"后缀结尾的文件在java中被称为:属性配置文件。
在java中,Properties是专门存放属性配置文件的一个类。
注意:
- 在属性配置文件中,"#"是注释。
- 属性配置文件的key重复的话,value会自动覆盖。
- 在等号两边最好不要有空格。
- 用冒号替代等号也可以,但不建议使用。

 
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号