0
点赞
收藏
分享

微信扫一扫

Java笔记15 文件和IO流

泠之屋 2022-03-19 阅读 22

15.1 文件

15.1 文件概念

文件是以计算机硬盘为载体存储在计算机上的信息集合,文件可以是文本文档、图片、程序,等等

15.2 文件流

文件在程序中是以流的形式操作的

在这里插入图片描述

  • 流:数据在数据源(文件)和程序(内存)之间经历的路径

  • 输入流:数据从数据源(文件)到程序(内存)的路径

  • 输出流:数据从程序(内存)到数据源(文件)的路径

15.2 常用的文件操作

15.2.1 创建文件对象

在这里插入图片描述

  1. new File(String pathname):根据路径构建一个File对象
  2. new File(File parent, String child):根据父目录文件+子路径构建
  3. new File(String parent, String child):根据父目录+子路径构建
  4. createNewFile():通过File对象调用该方法创建新文件
  5. 实现了 Comparable 接口和 Serializable 接口可比较、可序列化
//Windows下路径可以是C:\\Users\\ZHF\\Desktop\\newFile1.txt这种格式
//也可以是C:/Users/ZHF/Desktop/newFile1.txt这种格式
public class FileCreate {
    public static void main(String[] args) {

    }
    //方式1 `new File(String pathname)`
    @Test
    public void create01() {
        String filePath = "C:\\Users\\ZHF\\Desktop\\newFile1.txt";
        File file = new File(filePath);
        try {
            file.createNewFile();
            System.out.println("文件"+filePath+"创建成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //方式2 `new File(File parent, String child)`
    @Test
    public void create02() {
        File parent = new File("C:\\Users\\ZHF\\Desktop\\");//父目录文件
        String fileName = "newFile2.txt";
        File file = new File(parent, fileName);
        try {
            file.createNewFile();
            System.out.println("文件"+fileName+"创建成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //方式3 `new File(String parent, String child)`
    @Test
    public void create03() {
        String parentPath = "C:/Users/ZHF/Desktop/";
        String fileName = "newFile3.txt";
        File file = new File(parentPath, fileName);
        try {
            file.createNewFile();
            System.out.println("文件"+fileName+"创建成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

15.2.2 常用方法

常用的方法有:

  1. getName():获取文件名
  2. getAbsolutePath():获取文件绝对路径
  3. getParent():获取文件父级目录
  4. length():获取文件大小(字节)
  5. exists():判断文件是否存在
  6. isFile():判断是否为文件
  7. isDirectory():判断是否为目录
  8. mkdir:创建一级目录
  9. mkdirs:创建多级目录
  10. delete:删除文件或目录

15.2.3 示例

public class GetFileInfo {
    public static void main(String[] args) {

    }
    @Test
    //获取文件信息
    public void getInfo() throws IOException {
        //先创建文件对象
        File file = new File("C:\\Users\\ZHF\\Desktop\\newFile1.txt");
        //调用相应方法得到相应信息
        System.out.println("文件名:"+file.getName());
        System.out.println("文件是否存在:"+file.exists());
        if (!file.exists())//不存在则创建该文件
            System.out.println("创建结果:"+file.createNewFile());
        System.out.println("文件绝对路径:"+file.getAbsolutePath());
        System.out.println("文件父级目录:"+file.getParent());
        System.out.println("文件大小(字节):"+file.length());
        System.out.println("是不是一个文件:"+file.isFile());
        System.out.println("是不是一个目录:"+file.isDirectory());
        if (file.exists())
            System.out.println("文件删除是否成功:"+file.delete());
        File newDir = new File("C:\\Users\\ZHF\\Desktop\\newDir");
        System.out.println("目录是否存在:"+newDir.exists()+",存在则删除,不存在则创建");
        if (newDir.exists())
            System.out.println("删除目录:"+newDir.delete());
        else
            System.out.println("创建目录:"+newDir.mkdir());
        File dirs = new File("C:\\Users\\ZHF\\Desktop\\a\\b\\c");
        if (!dirs.exists())
            System.out.println("创建多级目录:"+dirs.mkdirs());
        File dirA = new File("C:\\Users\\ZHF\\Desktop\\a");
        System.out.println("删除a目录:"+dirA.delete());//非空文件夹,删除失败
    }
}

15.3 IO流原理及流的分类

15.3.1 IO流原理

  1. I/O 是 Input/Output 的缩写,I/O 技术是非常实用的技术,用于处理数据传输,如读写文件、网络通讯等
  2. Java程序中,对于数据的 I/O 操作以**流(stream)**的方式进行
  3. java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过方法输入或输出数据
  4. 输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中
  5. 输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中

15.3.2 流的分类

  1. 按操作数据单位不同分为:
    1. 字节流:分为字节输入流和字节输出流,按字节(8 bit)操作,操作二进制文件方便
    2. 字符流:分为字符输入流和字符输出流,按字符操作。字符大小不定,操作文本文件方便
  2. 按数据的流向不同分为:输入流 和 输出流
  3. 按流的角色不同分为:节点流、处理流/包装流
    在这里插入图片描述

15.3.3 常用的类

Java的IO流共涉及40多个类:

分类字节输入流字节输出流字符输入流字符输出流
抽象基类InputStreamOutputStreamReaderWriter
访问文件FileInputStreamFileOutputStreamFileReaderFileWriter
访问数组ByteArrayInputStreamByteArrayOutputStreamCharArrayReaderCharArrayWriter
访问管道PipedInputStreamPipedOutputStreamPipedReaderPipedWriter
访问字符串StringReaderStringWriter
缓冲流BufferedInputStreamBufferedOutputStreamBufferedReaderBufferedWriter
转换流InputStreamReaderOutputStreamWriter
对象流ObjectInputStreamObjectOutputStream
抽象基类FilterInputStreamFilterOutputStreamFilterReaderFilterWriter
打印流PrintStreamPrintWriter
推回输入流PushbackInputStreamPushbackReader
特殊流DataInputStreamDataOutputStream

注意:

(抽象基类)字节流字符流
输入流InputStreamReader
输出流OutputStreamWriter
  1. Java的IO流共涉及40多个类,实际上非常规则,都是从如上4个抽象基类派生的

  2. 由这四个类派生出来的子类名称都是以其父类名作为子类名后缀

  3. InputStream 抽象类是所有类字节输入流的超类,常用的派生类有:

    在这里插入图片描述

  4. OutputStream 抽象类是所有类字节输出流的超类,常用的派生类有:在这里插入图片描述

  5. Reader 抽象类是所有类字符输入流的超类,常用的派生类有:在这里插入图片描述

  6. Writer 抽象类是所有类字符输出流的超类,常用的派生类有:在这里插入图片描述

15.4 FileInputStream 和 FileOnputStream

在这里插入图片描述

15.4.1 FileInputStream 常用方法

  1. 构造方法:
    1. FileInputStream(File file):通过打开与实际文件的连接创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
    2. FileInputStream(FileDescriptor fdObj):创建 FileInputStream通过使用文件描述符 fdObj ,其表示在文件系统中的现有连接到一个实际的文件。
    3. FileInputStream(String name):通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。
  2. int read() 从该输入流读取一个字节的数据。
    1. 返回值为读取到的字节数据,返回-1表示读取完毕
    2. 如果没有输入可用,此方法将阻止
    3. 如果读取中文,每次读取中文的一部分,将会乱码
  3. int read(byte[] b) 从该输入流读取最多 b.length个字节的数据为字节数组。
    1. 会将数据读取到数组 b 中
    2. 返回值为实际读取到的字节数(若规定一次性读取8个字节,实际只有6个字节,则返回6)
    3. 返回-1表示读取完毕
  4. void close() 关闭此文件输入流并释放与流相关联的任何系统资源。

15.4.2 FileInputStream 示例

public class FileInputStreamDemo {
    public static void main(String[] args) {

    }

    //int read() 从该输入流读取一个字节的数据。
    @Test
    public void readFromFile01() {
        String filePath = "C:\\Users\\ZHF\\Desktop\\hello.txt";
        int readData = 0;//用于存储读取到的字节数据
        FileInputStream fileInputStream = null;
        try {
            //创建 FileInputStream 对象,用于读取文件
            fileInputStream = new FileInputStream(filePath);
            //read():从该输入流中读取一个字节的数据。
            // 返回值为读取到的字节数据,返回-1表示读取完毕
            // 如果没有输入可用,此方法将阻止
            // 如果读取中文,每次读取中文的一部分,将会乱码
            while ((readData = fileInputStream.read()) != -1) {
                System.out.print((char) readData);//将字节数据转回字符
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fileInputStream.close();//关闭文件流,释放资源
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //int read(byte[] b) 从该输入流读取最多 b.length个字节的数据为字节数组。
    @Test
    public void readFromFile02() {
        String filePath = "C:\\Users\\ZHF\\Desktop\\hello.txt";
        int readLen = 0;
        //定义字节数组
        byte[] buf = new byte[8];//一次读取8个字节
        FileInputStream fileInputStream = null;
        try {
            //创建 FileInputStream 对象,用于读取文件
            fileInputStream = new FileInputStream(filePath);
            //从该输入流读取最多b.length字节的数据到参数传入的字节数组。 此方法将阻塞,直到某些输入可用。
            //返回值为实际读取到的字节数(若规定一次性读取8个字节,实际只有6个字节,则返回6)
            //返回-1表示读取完毕
            while ((readLen = fileInputStream.read(buf)) != -1) {//将数据读取到buf中
                System.out.print(new String(buf, 0, readLen));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileInputStream != null)
                    fileInputStream.close();//关闭文件流,释放资源
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

15.4.3 FileOnputStream 常用方法

  1. 构造方法:
    1. FileOutputStream(File file) 创建文件输出流以写入由指定的 File对象表示的文件。
    2. FileOutputStream(File file, boolean append) 创建文件输出流以写入由指定的 File对象表示的文件。append 如果 true ,则字节将被写入文件的末尾而不是开头
    3. FileOutputStream(FileDescriptor fdObj) 创建文件输出流以写入指定的文件描述符,表示与文件系统中实际文件的现有连接。
    4. FileOutputStream(String name) 创建文件输出流以指定的名称写入文件。
    5. FileOutputStream(String name, boolean append) 创建文件输出流以指定的名称写入文件。 append 如果 true ,则字节将被写入文件的末尾而不是开头
  2. void write(byte[] b)b.length个字节从指定的字节数组写入此文件输出流。
  3. void write(byte[] b, int off, int len)len字节从位于偏移量 off的指定字节数组写入此文件输出流。
  4. void write(int b) 将指定的字节写入此文件输出流。
  5. close() 关闭此文件输出流并释放与此流相关联的任何系统资源。

15.4.4 FileOutputStream 示例

  1. 写入数据到文件

    public class FileOutputStreamDemo {
        public static void main(String[] args) {}
    
        /**
         *演示使用 FileOutputStream 将数据写到文件中,
         *如果该文件不存在,则创建该文件
         */
        @Test
        public void write2File1() {
            String filePath = "C:\\Users\\ZHF\\Desktop\\a.txt";
            //创建 FileOutputStream 对象
            FileOutputStream fileOutputStream = null;
            try {
                //得到FileOutputStream对象,false为覆盖,true为追加
                fileOutputStream = new FileOutputStream(filePath,false);
                
                //写入一个字节
                fileOutputStream.write('a');
                
                //写入字符串
                String str = "hello,world";
                fileOutputStream.write(str.getBytes());//getBytes()将字符串转成字节数组
                
                //void write(byte[] b,  int off, int len)写入字节数组b中从off开始len长度的字节数据
                fileOutputStream.write(str.getBytes(),0,5);//写入hello
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (fileOutputStream != null)
                        fileOutputStream.close();//释放资源
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
        }
    }
    
  2. 文件拷贝

//将C:\Users\ZHF\Desktop\bg.png拷贝到C:\Users\ZHF\Desktop\a\bg.png
public class FileCopy {
    public static void main(String[] args) {
        //创建文件输入流,读取文件
        //创建文件输出流,将读取到的数据写入到指定的文件中
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        String srcPath = "C:\\Users\\ZHF\\Desktop\\bg.png";
        String destPath = "C:\\Users\\ZHF\\Desktop\\a\\bg.png";
        try {
            fileInputStream = new FileInputStream(srcPath);
            fileOutputStream = new FileOutputStream(destPath);
            //定义一个字节数组,提高读取效率
            byte[] buf = new byte[1024];//一次读取1024个字节
            int readLen = 0;//记录读取到的字节长度
            while ((readLen = fileInputStream.read(buf)) != -1) {
                //将读取到的数据写入到文件,即边读边写
                fileOutputStream.write(buf,0,readLen);
            }
            System.out.println("copy success...");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileInputStream != null)
                    fileInputStream.close();
                if (fileOutputStream != null)
                    fileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

15.5 FileReader 和 FileWriter

在这里插入图片描述

15.5.1 FileReader 常用方法

  1. new FileReader(File/String):构造方法
  2. read():每次读取单个字符,返回该字符,如果到文件末尾返回-1
  3. read(char[]):批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回-1
  4. 相关API:
    1. new String(char[]):char[]转换成String
    2. new String(char[],off,len):将char[]的指定部分转换成String

15.5.2 FileReader 示例

public class FileReaderDemo {
    public static void main(String[] args) {

    }

    //每次单个字符读取
    @Test
    public void ReadFromFile1() {
        String filePath = "C:\\Users\\ZHF\\Desktop\\FileCopy.txt";
        FileReader fileReader = null;
        int data = 0;
        try {
            fileReader = new FileReader(filePath);
            //单个字符读取
            while ((data = fileReader.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (fileReader != null)
                    fileReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //字符数组读取
    @Test
    public void ReadFromFile2() {
        String filePath = "C:\\Users\\ZHF\\Desktop\\FileCopy.txt";
        FileReader fileReader = null;
        int readLen = 0;
        char[] buf = new char[8];//每次读取8个字符
        try {
            fileReader = new FileReader(filePath);
            //读取
            while ((readLen = fileReader.read(buf)) != -1) {
                System.out.print(new String(buf,0,readLen));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (fileReader != null)
                    fileReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

15.5.3 FileWriter 常用方法

  1. new FileWriter(File/String):覆盖模式,相当于流的指针在首端
  2. new FileWriter(File/String,true):追加模式,相当于流的指针在尾端
  3. write(int):写入单个字符
  4. write(char[]):写入指定数组
  5. write(char[],off,len):写入指定数组的指定部分
  6. write (string) :写入整个字符
  7. write(string,off,len):写入字符串的指定部分
  8. 相关API: String类的toCharArray:将String转换成char[]
  9. 注意: FileWriter 使用后,必须要关闭(close)或刷新(flush),否则写入不到指定的文件!

15.5.4 FileWriter 示例

public class FileWriterDemo {
    public static void main(String[] args) {
        String filePath = "C:\\Users\\ZHF\\Desktop\\note.txt";
        FileWriter fileWriter = null;
        try {
            fileWriter = new FileWriter(filePath);
            //1) write(int):写入单个字符
            fileWriter.write('h');//h
            //2) write(char[]):写入指定数组
            char[] chars = {'a', 'b', 'c', 'd'};
            fileWriter.write(chars);//abcd
            //3) write(char[],off,len):写入指定数组的指定部分
            fileWriter.write("数据结构".toCharArray(),0,2);//数据
            //4) write (string) :写入整个字符
            fileWriter.write("计算机网络");//计算机网络
            //5) write(string,off,len):写入字符串的指定部分
            fileWriter.write("计算机组成原理",0,3);//计算机

            //数据量大的情况下循环操作
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                //FileWriter使用后,
                //必须要关闭(close)或刷新(flush),否则写入不到指定的文件!
                if (fileWriter != null)
                    //fileWriter.flush();
                    fileWriter.close();//等价于flush()+close()
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

15.6 节点流和处理流

15.6.1 介绍

  1. 节点流可以从一个特定的数据源读写数据,如FileReader, FileWriter
  2. 处理流(也叫包装流)是“连接”在已存在的流(节点流或处理流)之上,为程序提供 更为强大的读写功能,也更加灵活,如BufferedReader, BufferedWriter

在这里插入图片描述

15.6.2 区别和联系

  1. 节点流是底层流/低级流,直接跟数据源相接。
  2. 处理流(包装流)包装节点流,既可以消除不同节点流的实现差异,也可以提供更方 便的方法来完成输入输出。
  3. 处理流(也叫包装流)对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连【修饰器设计模式】

模拟修饰器设计模式:

public abstract class Reader_ {//抽象类
    //后面在调用时,利用对象动态绑定机制,绑定到对应的实现子类
    public abstract void read();
}
//节点流
public class FileReader_ extends Reader_{
    @Override
    public void read() {
        System.out.println("读取文件数据...");
    }
}
//节点流
public class StringReader_ extends Reader_{
    @Override
    public void read() {
        System.out.println("读取字符串...");
    }
}
//处理流
public class BufferedReader_ extends Reader_ {
    private Reader_ reader_;//属性是Reader_类型

    public BufferedReader_(Reader_ reader_) {
        this.reader_ = reader_;
    }

    @Override
    public void read() {
        reader_.read();
    }

    //让方法更灵活
    public void readFiles(int num) {
        for (int i = 0; i < num; i++) {
            reader_.read();
        }
    }

    public void readStrings(int num) {
        for (int i = 0; i < num; i++) {
            reader_.read();
        }
    }
}
public class BufferedReader_Demo {
    public static void main(String[] args) {
        BufferedReader_ bufferedReader_ = new BufferedReader_(new FileReader_());
        bufferedReader_.read();
        bufferedReader_.readFiles(3);

        BufferedReader_ bufferedReader_1 = new BufferedReader_(new StringReader_());
        bufferedReader_1.read();
        bufferedReader_1.readStrings(3);
    }
}

15.6.3 处理流的功能主要体现

  1. 性能的提高:主要以增加缓冲的方式来提高输入输出的效率。
  2. 操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使 用更加灵活方便

15.7 BufferedReader 和 BufferedWriter

15.7.1 说明

在这里插入图片描述

  1. BufferedReader 和 BufferedWriter属于字符流,是按照字符来读取数据的,不要操作二进制文件(音视频、word文档等),可能会造成文件损坏
  2. 关闭处理流,只需要关闭外层流即可
  3. 构造方法不能设定覆盖或追加,要在传入的节点中设定

15.7.2 示例

  1. /**
     * 演示BufferedReader使用
     */
    public class BufferedReaderDemo {
        public static void main(String[] args) throws IOException {
            String filePath = "C:\\Users\\ZHF\\Desktop\\a.txt";
            //创建BufferedReader对象
            BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
            //读取
            String line;
            //bufferedReader.readLine()按行读取,性能高,但没有换行符
            //当返回 null 时,读取完毕
            while ((line = bufferedReader.readLine()) != null) {
                System.out.println(line);
            }
            //关闭流,只需要关闭BufferedReader,底层会自动关闭节点流
            if (bufferedReader != null)
                bufferedReader.close();
            /**    BufferedReader的close()源码:
             *     public void close() throws IOException {
             *         synchronized (lock) {
             *             if (in == null)
             *                 return;
             *             try {
             *                 in.close();  //in为传入的节点流new FileReader(filePath)
             *             } finally {
             *                 in = null;
             *                 cb = null;
             *             }
             *         }
             *     }
             */
        }
    }
    
  2. /**
     * 演示BufferedWriter的使用
     */
    public class BufferedWriterDemo {
        public static void main(String[] args) throws IOException {
            String filePath = "C:\\Users\\ZHF\\Desktop\\ok.txt";
            //创建BufferedWriter对象
            BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath,true));
            //若需要设定写入为追加或覆盖模式,需要在节点流的构造方法中添加true或false(默认false)
            bufferedWriter.write("hello,world");
            bufferedWriter.write("hello,world");
            //要有换行效果,需要插入一个换行符
            bufferedWriter.newLine();//插入一个和系统相关的换行符
            bufferedWriter.write("hello,world");
            //关闭外层流即可
            if (bufferedWriter != null)
                bufferedWriter.close();
        }
    }
    
  3. public class BufferedCopy {
        public static void main(String[] args) {
            String srcFile = "C:\\Users\\ZHF\\Desktop\\a.txt";
            String destFile = "C:\\Users\\ZHF\\Desktop\\a2.java";
    
            BufferedReader br = null;
            BufferedWriter bw = null;
            String line;
            try {
                br = new BufferedReader(new FileReader(srcFile));
                bw = new BufferedWriter(new FileWriter(destFile));
                //按行读取
                while ((line = br.readLine()) != null) {
                    //每读一行就写入
                    bw.write(line);
                    bw.newLine();//需要换行,若不换行新文件数据都在同一行
                }
                System.out.println("copy succeed");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (br != null)
                        br.close();
                    if (bw != null)
                        bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

15.8 BufferedInputStream 和 BufferedInputStream

15.8.1 介绍

在这里插入图片描述

  1. BufferedInputStream 是字节流,在创建 BufferedInputStream 时,会创建一个内部缓冲区数组
  2. BufferedInputStream 是字节流,实现缓冲的输出流,可以将多个字节写入到底层输出流中,而不必对每次字节写入调用底层系统
  3. BufferedInputStream 和 BufferedInputStream 包装的节点流对象(in、out)是从父类(FilterInputStream和FilterOutputStream)继承过来的
  4. BufferedInputStream 和 BufferedInputStream 是字节流,既可以处理二进制文件,也可以处理文本文件

15.8.2 示例

完成二进制文件的拷贝

public class BufferedCopy2 {
    public static void main(String[] args) {
        String srcFile = "C:\\Users\\ZHF\\Desktop\\bg.png";
        String destFile = "C:\\Users\\ZHF\\Desktop\\bg2.png";
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        try {
            bis = new BufferedInputStream(new FileInputStream(srcFile));
            bos = new BufferedOutputStream(new FileOutputStream(destFile));
            //循环读取文件并写入到新文件
            byte[] buf = new byte[1024];
            int readLen = 0;
            while ((readLen = bis.read(buf)) != -1) {
                bos.write(buf,0,readLen);
            }
            System.out.println("copy succeed");

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                //关闭外层流
                if (bis != null)
                    bis.close();
                if (bos != null)
                    bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

15.9 ObjectInputStream 和 ObjectOutputStream

15.9.1 序列化和反序列化

  1. 序列化就是在保存数据时,保存数据的值数据类型
  2. 反序列化就是在恢复数据时,保存数据的值数据类型
  3. 需要让某个对象支持序列化机制,需要其类实现以下接口之一,从而使其可序列化
    1. Serializable:是一个标记接口,没有方法,一般使用该方法
    2. Externalizable:该接口有方法需要实现

15.9.2 介绍

在这里插入图片描述

  1. 提供了对基本类型或对象类型的序列化和反序列化的方法
  2. ObjectInputStream 提供了 序列化 功能
  3. ObjectOutputStream 提供了 反序列化 功能

15.9.3 使用细节

  1. 读写顺序要一致
  2. 要求序列化或反序列化的对象,需要实现 Serializable 接口
  3. 序列化的类中建议添加 SerialVersionUID ,以提高版本的兼容性。添加之后,后续对类进行修改,序列化或反序列化时,会认为是同一个类,只是不同的版本
  4. 序列化对象时,默认将里边所有的属性进行序列化,除了 static 或 transient 修饰的成员
  5. 序列化对象时,要求里边的属性的类型也需要实现序列化接口
  6. 序列化具有可继承性,也就是说如果某类已经实现了系列化,则它的子类也已经默认实现了序列化

15.9.4 示例

public class ObjectOutputStreamDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //序列化后保存的文件格式不是纯文本的,而是按照它的格式保存的
        String filePath = "C:\\Users\\ZHF\\Desktop\\data.dat";
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
        //序列化数据到 data.dat 中
        oos.writeInt(100); //int -> Integer (Integer实现了Serializable接口)
        oos.writeBoolean(true); // boolean -> Boolean
        oos.writeChar('a'); //char -> Character
        oos.writeDouble(9.5);   //double -> Double
        oos.writeUTF("字符串");    //String
        Dog dog = new Dog("旺财", 10, "白色","中国");
        //若Dog中含有没有实现序列化的类的属性(如没有Serializable接口的Master),
        //在对Dog类的对象进行序列化时,会抛NotSerializableException异常
        //将Master类实现Serializable接口,Dog类即可进行序列化
        oos.writeObject(dog);
        //保存的对象的类必须实现了Serializable接口
        //否则会抛NotSerializableException
        oos.close();//关闭流
        System.out.println("write succeed");

        //反序列化data.dat中的数据
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
        //读取的顺序要和存放的顺序一致
        System.out.println(ois.readInt());
        System.out.println(ois.readBoolean());
        System.out.println(ois.readChar());
        System.out.println(ois.readDouble());
        System.out.println(ois.readUTF());
        Object dog1 = ois.readObject();  //dog的编译类型为Object,运行类型为Dog
        System.out.println("运行类型:"+dog1.getClass());
        Dog.setNation("俄罗斯");
        System.out.println("对象信息:"+dog1);//Object -> object.Dog
        //对象信息:object.Dog{name='旺财', age=10, color='null', nation='俄罗斯', Master='object.Master@50040f0c'}
        //要注意,static类型的属性并没有被序列化,
        //这里能够输出nation的值是因为前边创建Dog对象使对静态属性nation进行了赋值
        //在对Dog类的nation属性的值进行修改过后,输出的是修改后的值
        //可以看出,此处相当于直接读取了Dog类的nation属性的值,而非从文件中读取到的反序列化后的值


        //重要细节
        //如果我们需要调用Dog的方法,需要向下转型 Object -> object.Dog
        //需要我们将Dog类的定义在可以访问的位置
        //不可以简单地拷贝过来,拷贝一个Dog类的话,跟原本的Dog不是一个类
        //xxxx.object.Dog -> yyyy.object.Dog 类型转换异常
        Dog dog2 = (Dog) dog1;
        System.out.println(dog2.getName());

        ois.close();//关闭流
    }
}
//需要序列化类的对象必须实现Serializable接口
class Dog implements Serializable {
    private String name;
    private int age;
    //序列化版本号,可以提高序列化的兼容性
    //添加之后,后续对类进行修改,序列化或反序列化时,会认为是同一个类,只是不同的版本
    private static final long serialVersionUID = 1L;

    //对于static和transient修饰的属性不会进行序列化
    private static String nation;
    private transient String color;

    //序列化对象时,要求里边的属性的类型也需要实现序列化接口
    //否则会抛NotSerializableException异常
    private Master master = new Master();

    public Dog(String name, int age, String color, String nation) {
        this.name = name;
        this.age = age;
        this.color = color;
        this.nation = nation;
    }

    public static void setNation(String nation) {
        Dog.nation = nation;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "object.Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                ", nation='" + nation + '\'' +
                ", Master='" + master + '\'' +
                '}';
    }
}

class Master implements Serializable{}

15.10 标准输入输出流

15.10.1 介绍

类型默认设备
System.in 标准输入InputStream键盘
System.out 标准输出PrintStream显示器
  1. System.in 表示标准输入,其中的 in 是 System 类的一个属性,其编译类型是InputStream,运行类型是 BufferedInputStream

    public final static InputStream in = null;
    
  2. System.out 表示标准输出,其中的 out 也是 System 类的一个属性,其编译类型和运行类型都是PrintStream

    public final static PrintStream out = null;
    

15.10.2 示例

public class InputAndOutput {
    public static void main(String[] args) {
        System.out.println(System.in.getClass());//class java.io.BufferedInputStream
        System.out.println(System.out.getClass());//class java.io.PrintStream

        System.out.println("hello");

        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入:");
        String next = scanner.next();
        System.out.println(next);
    }
}

15.11 InputStreamReader 和 OutputStreamWriter

15.11.1 介绍

在这里插入图片描述

  1. InputStreamReader 和 OutputStreamWriter 是转换流,作用是将字节流转换为字符流
  2. InputStreamReader:Reader的子类,可以将 InputStream(字节流)包装(转换)成Reader(字符流)
  3. OutputStreamWriter:Writer的子类,可以将 OutputStream(字节流)包装成 Writer(字符流)
  4. 当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文乱码问题,所以建议将字节流转换为字符流
  5. 可以在使用时指定编码格式(如 utf-8、gbk、gb2312、ISO8859-1等)

15.11.2 示例

/**
 * 演示使用 InputStreamReader 转换流,解决中文乱码问题
 * 将字节流 FileInputStream 转成字符流 InputStreamReader ,指定编码gbk/utf-8
 */
public class InputStreamReaderDemo {
    public static void main(String[] args) throws IOException {
        String filePath = "C:\\Users\\ZHF\\Desktop\\a.txt";//a.txt为gbk编码
        BufferedReader br = new BufferedReader(new FileReader(filePath));
        String line;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
        //直接用BufferedReader输出,结果乱码
        //原因是BufferedReader默认情况下,读取文件是按照 utf-8 编码
        br.close();

        //解决方式:用转换流
        //1.将FileInputStream转成InputStreamReader,指定编码为gbk
        //InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk");
        //2.把InputStreamReader传入BufferedReader
        //br = new BufferedReader(isr);
        //可以将1和2合并
        br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath),"gbk"));
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }//输出正常
        br.close();
    }
}
/**
 * 演示 OutputStreamWriter 使用
 * 把 FileOutputStream 转成 OutputStreamWriter,指定编码为gbk
 */
public class OutputStreamWriterDemo {
    public static void main(String[] args) throws IOException {
        String filePath = "C:\\Users\\ZHF\\Desktop\\newFile.txt";
        String charSet = "gbk";
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath), charSet);
        osw.write("hello,Java编程语言");
        System.out.println("write " + charSet + " file succeed");
        osw.close();
    }
}

15.12 打印流

15.12.1 介绍

在这里插入图片描述

  1. 打印流只有输出流,没有输入流
  2. 打印流不仅可以将信息打印到显示器(默认),也可以打印到文件中
  3. PrintStream:字节打印流,底层用 OutputStream
  4. PrintWriter:字符打印流,底层用 Writer

15.12.2 示例

/**
 * 演示PrintStream
 */
public class PrintStreamDemo {
    public static void main(String[] args) throws IOException {
        PrintStream out = System.out;
        //默认情况下,PrintStream 输出的位置是标准输出,即显示器
        out.print("hello");
        /*
            public void print(String s) {
                if (s == null) {
                    s = "null";
                }
                write(s);
            }
         */
        //因为print()方法的底层是调用write()方法,
        //因此可以直接调用write()方法进行打印/输出
        out.write("你好".getBytes());

        //可以修改PrintStream输出的位置 -> t1.txt
        System.setOut(new PrintStream("C:\\Users\\ZHF\\Desktop\\t1.txt"));
        /*
            public static void setOut(PrintStream out) {
                checkIO();
                setOut0(out);//native方法,修改了out
            }
         */
        System.out.println("计算机组成原理");
        //会将 计算机组成原理 保存到t1.txt中
        out.close();
    }
}
/**
 * 演示PrintWriter
 */
public class PrintWriterDemo {
    public static void main(String[] args) throws IOException {
        //传入标准输出,打印到显示器
        PrintWriter printWriter = new PrintWriter(System.out);
        printWriter.print("hello");
        printWriter.close();

        //传入FileWriter,修改输出位置为 -> t2.txt
        printWriter = new PrintWriter(new FileWriter("C:\\Users\\ZHF\\Desktop\\t2.txt"));
        printWriter.print("hello");
        printWriter.close();//没有关闭的话,不会输出
    }
}

15.13 Properties类

15.13.1 介绍

  1. 专门用于读写配置文件的集合类,格式为:

    key=value
    key=value
    
  2. 键值对不需要有空格,值不需要用引号引起来,默认类型是String

15.13.2 常用方法

  1. load(Reader reader/InputStream inStream):通过Reader/InputStream或其子类对象加载配置文件的键值对到 Properties 对象
  2. list:将数据显示到指定的设备
  3. getProperty(key):根据 key 获取 value
  4. setProperty(key, value):设置键值对到 Properties 对象
  5. store(Writer writer/OutputStream out, String comments):传入Writer/OutputStream或其子类的对象,以及String类型的注释,将 Properties 对象中的键值对以及注释存储到配置文件

15.13.3 示例

  1. 读取mysql.properties

    //对mysql.properties文件进行读写
    /*
    ip=192.168.100.100
    user=root
    pwd=123456
    */
    public class PropertiesDemo {
        public static void main(String[] args) throws IOException {
            //1.传统方法读取mysql.properties文件,并获取相应的ip、user和pwd
            BufferedReader br = new BufferedReader(new FileReader("src\\mysql.properties"));
            String line;
            while ((line = br.readLine()) != null) {
                String[] split = line.split("=");
                System.out.println(split[0] + "的值为" + split[1]);
            }
    
            //2.使用 Properties 类
            //读取 mysql.properties 文件
            //创建 Properties 对象
            Properties properties = new Properties();
            //加载 mysql.properties 文件
            properties.load(new FileReader("src\\mysql.properties"));
            //输出到屏幕
            properties.list(System.out);
            //根据 key 获取 value
            System.out.println("用户名为:"+properties.get("user"));//用户名为:root
        }
    }
    
  2. 存储键值对到 mysql2.properties,修改 mysql2.properties 中的键值对

    public class PropertiesDemo2 {
        public static void main(String[] args) throws IOException {
            //1.使用 Properties 创建配置文件
            //创建 Properties 对象
            Properties properties = new Properties();
            //若文件中有要添加的key,setProperty()就会将key-value添加,若有,则会修改value的值
            //Properties父类为Hashtable,setProperty()底层调用的是Hashtable的put()方法
            /*
                public synchronized Object setProperty(String key, String value) {
                    return put(key, value);
                }
             */
            properties.setProperty("charset", "utf8");
            properties.setProperty("user", "汤姆");
            properties.setProperty("pwd", "admin");
    
            //存储 Properties
            //以字节流进行保存,中文会被保存为 Unicode 码,将中文显示为Unicode码
            //properties.store(new FileOutputStream("src\\mysql2.properties"),"注释");
    
            //以字符流进行保存,写入的数据和注释在idea中会显示中文乱码,记事本打开并未乱码,解决方法:
            //在设置里设置文件编码为utf-8,并勾选transparent native-to-ascll conversion选项
            //勾选后,即便使用字节流保存的properties文件,
            //在idea中中文也不会乱码,但其原文件中文仍显示为Unicode码
            properties.store(new FileWriter("src\\mysql2.properties"),"注释");
            System.out.println("store mysql2.properties succeed");
        }
    }
    

15.14 练习

  1. /**
     * 1.判断d盘是否有文件夹mytemp,如果没有就创建mytemp
     * 2.在d:\\mytemp目录下,创建文件hello.txt
     * 3.如果hello.txt已存在,提示文件已存在,就不要再重复创建了
     * 在hello.txt文件中写入"hello,world~"
     */
    public class Exercise01 {
        public static void main(String[] args) {
            String dirPath = "D:\\mytemp";
            File dir = new File(dirPath);
            if (!(dir.exists() && dir.isDirectory()))
                if (dir.mkdir())
                    System.out.println("文件夹" + dirPath + "创建成功");
                else
                    System.out.println("文件夹" + dirPath + "创建失败");
            else
                System.out.println("文件夹" + dirPath + "已存在");
            String fileName = "hello.txt";
            File file = new File(dir, fileName);
            try {
                if (!(file.exists() && file.isFile()))
                    if (file.createNewFile()) {
                        System.out.println("文件" + fileName + "创建成功");
                        FileWriter fr = null;
                        try {
                            fr = new FileWriter(dir+ "\\" + fileName);
                            fr.write("hello,world~");
                            System.out.println("写入成功");
                        } catch (IOException e) {
                            e.printStackTrace();
                        } finally {
                            try {
                                if (fr != null)
                                    fr.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    } else
                        System.out.println("文件" + fileName + "创建失败");
                else
                System.out.println("文件" + fileName + "已存在");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
  2. /**
     * @author 赵豪峰
     * @create 2022-03-18 22:39
     * 使用BufferedReader读取一个文本文件(gbk文件),
     * 为每行加上行号,再连同内容一并输出到屏幕
     */
    public class Exercise02 {
        public static void main(String[] args) {
            BufferedReader br = null;
            InputStreamReader isr = null;
            String line;
            int lineNum = 1;
            try {
                isr = new InputStreamReader(new FileInputStream("C:\\Users\\ZHF\\Desktop\\a.txt"),"gbk");
                br = new BufferedReader(isr);
                while ((line = br.readLine()) != null) {
                    System.out.println(lineNum++ + " " + line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (br != null)
                        br.close();
                    if (isr != null)
                        isr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
  3. /**
     * 1.编写一个dog.properties
     * name=tom
     * age=5
     * color=red
     * 2.编写Dog类(name,age,color),创建一个dog对象
     * 读取dog.properties用相应内容完成属性初始化并输出
     * 将dog对象序列化到dog.dat文件
     */
    public class Exercise03 {
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            //读取dog.properties
            Properties properties = new Properties();
            properties.load(new FileReader("src\\dog.properties"));
            String name = properties.getProperty("name");
            int age = Integer.parseInt(properties.getProperty("age"));//String -> int
            String color = properties.getProperty("color");
            //实例化Dog类
            Dog dog = new Dog(name, age, color);
            System.out.println(dog);
            //序列化dog
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\ZHF\\Desktop\\dog.dat"));
            oos.writeObject(dog);
            oos.close();
            //反序列化
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\ZHF\\Desktop\\dog.dat"));
            Dog dog1 = (Dog) ois.readObject();
            System.out.println(dog1);
            ois.close();
        }
    }
    
    class Dog implements Serializable {
        private String name;
        private int age;
        private String color;
    
        public Dog(String name, int age, String color) {
            this.name = name;
            this.age = age;
            this.color = color;
        }
    
        @Override
        public String toString() {
            return "Dog{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", color='" + color + '\'' +
                    '}';
        }
    }
    
举报

相关推荐

0 条评论