File类和IO流的使用,序列化和反序列化(二十一)

279 阅读34分钟

File类和IO流

java.io.File类

1 File类的介绍

FIle类是文件和目录路径名的抽象表现形式。

(1)File类是java.io包下代表与平台无关的文件和目录(文件夹)。

(2)我们在程序中要表现一个文件或者是目录是通过指定它的“路径”来表示的。

(3)它是一个抽象的表现形式 ==> 我们new的File对象,并不表示它一定(百分百)对应有这个文件或目录存在。

2、可以使用File类的对象干什么?

在程序中操作文件和目录都可以通过File类来完成,File类能新建、删除、重命名文件和目录。

但是我们不能直接通过File对象进行文件内容的“读”和“写”,如果要对内容操作,必须依赖于IO流。

3、API

(1)构造器:

File(String pathname)

File(String parent, String child)

public class TestFile {

    @Test
    public void test02(){
        File file = new File("d:/dyy","1.txt");
        System.out.println(file);
    }

    @Test
    public void test01(){
        File file = new File("d:/1.txt");
        System.out.println(file);
    }
}

(2)方法

  • public String getName() :返回由此File表示的文件或目录的名称。

  • public String getPath():将此File转换为路径名字符串。

  • public long length():返回由此File表示的文件的长度。单位是字节。但是不能直接获取目录(文件夹)的大小。

  • public long lastModified():返回File对象对应的文件或目录的最后修改时间(毫秒值)

    思考:数组.length 字符串.length() File对象.length() 的区别?

    回答:数组.length属性,后面两个是方法。

    思考:如何获取或计算文件夹的大小?

    回答:把文件夹中每一级的文件的大小累加起来。

  • public String getPath():将此File转换为路径名字符串。path:构造器中指定的路径

  • public String getAbsolutePath():返回此File的绝对路径名字符串。 user.dir用户路径 + path

  • public String getCanonicalPath():返回此File对象所对应的规范路径名, 对绝对路径中出现的../(上一级)或.(当前目录)等非规范字符解析后的路径

  • public String getParent():获取父目录 。根据path获取的父目录

    斜杆:

    \ :在Java中是转义

    /:用于表示路径,可以是操作系统路径,也可以是url(网址)的路径

    www.baidu.com/index.html

    早期时windows的文件系统路径是不支持/,只支持\,后来都支持了。

    Java中为了考虑兼容所有的平台,建议大家使用/,或者是使用File中的一个常量

    路径:

    绝对路径:从根目录开始导航的路径,例如:d:\dyy\javase\HelloIO.java

    windows文件目录系统的根是盘符

    ​ linux文件目录系统的根是 /

    相对路径:路径的开头不是 盘符或者是 /。 例如: File f2 = new File("HelloIO.java");

    在Java程序中,相对路径是相对谁?(注意 在JUnit中的相对路径和在main方法中的绝对路径是不同的)

    例如:D:\javaee\JavaSE20210622\code\day0720_teacher_code JUnit读取相对路径是基于模块

    例如:D:\javaee\JavaSE20210622\code main方法读取相对路径是基于项目project

  • public boolean exists() :此File表示的文件或目录是否实际存在。

  • public boolean isDirectory():此File表示的是否为目录。当这个File对象指定的目录不存在时,就算File对象代表的是目录也是返回false

  • public boolean isFile() :此File表示的是否为文件。

    当这个File对象指定的文件不存在时,就算File对象代表的是文件也是返回false。当目录或文件不存在时,仅仅是JVM中一个普通对象,和操作系统中的文件或目录并无关,所以无法判断它是文件还是目录。 包括其他的属性,例如:大小、修改时间等都是默认的。

    为什么目录或文件不存在时,获取路径相关的信息可以呢? 因为路径相关信息是根据new对象时,指定的路径值来计算的。

public class TestFile1 {
    @Test
    public void test11() throws IOException {
        File f3 = new File("D:/1.txt");
        System.out.println(f3.exists());//true
        System.out.println(f3.isDirectory());//false
        System.out.println(f3.isFile());//true
    }
    @Test
    public void test10() throws IOException{
        File f3 = new File("../../HelloIO.java");
        System.out.println(f3.exists());//false
        System.out.println(f3.isDirectory());//false
        System.out.println(f3.isFile());//false
    }
    @Test
    public void test9() throws IOException{
        File f3 = new File("../../HelloIO.java");
        System.out.println("user.dir =" + System.getProperty("user.dir"));//D:\dyy\javaee\JavaSE20210622\code\day0720_teacher_code
        System.out.println("文件/目录的名称:" + f3.getName());//HelloIO.java
        System.out.println("文件/目录的构造路径名:" + f3.getPath());//..\..\HelloIO.java
        System.out.println("文件/目录的绝对路径名:" + f3.getAbsolutePath());//D:\dyy\javaee\JavaSE20210622\code\day0720_teacher_code\..\..\HelloIO.java
        System.out.println("文件/目录的规范路径名:" + f3.getCanonicalPath());//D:\dyy\javaee\JavaSE20210622\HelloIO.java
        System.out.println("文件/目录的父目录名:" + f3.getParent());//..\..
    }

    public static void main(String[] args)throws IOException {
        File f2 = new File("HelloIO.java");
        System.out.println("user.dir =" + System.getProperty("user.dir"));//D:\dyy\javaee\JavaSE20210622\code
        System.out.println("文件/目录的名称:" + f2.getName());//HelloIO.java
        System.out.println("文件/目录的构造路径名:" + f2.getPath());//HelloIO.java
        System.out.println("文件/目录的绝对路径名:" + f2.getAbsolutePath());//D:\dyy\javaee\JavaSE20210622\code\HelloIO.java
        System.out.println("文件/目录的规范路径名:" + f2.getCanonicalPath());//D:\dyy\javaee\JavaSE20210622\code\HelloIO.java
        System.out.println("文件/目录的父目录名:" + f2.getParent());//null
    }

    @Test
    public void test7() throws IOException{
        System.out.println(File.separator);//会根据当前平台自动识别默认的路径分隔符
        File file1 = new File("java" + File.separator + "io" + File.separator + "Hello.java");
        File file2 = new File("java/io/Hello.java");
    }

    @Test
    public void test6() throws IOException{
        File f2 = new File("HelloIO.java");
        System.out.println("user.dir =" + System.getProperty("user.dir"));//D:\dyy\javaee\JavaSE20210622\code\day0720_teacher_code
        System.out.println("文件/目录的名称:" + f2.getName());//HelloIO.java
        System.out.println("文件/目录的构造路径名:" + f2.getPath());//HelloIO.java
        System.out.println("文件/目录的绝对路径名:" + f2.getAbsolutePath());//D:\dyy\javaee\JavaSE20210622\code\day0720_teacher_code\HelloIO.java
        System.out.println("文件/目录的规范路径名:" + f2.getCanonicalPath());//D:\dyy\javaee\JavaSE20210622\code\day0720_teacher_code\HelloIO.java
        System.out.println("文件/目录的父目录名:" + f2.getParent());//null  根据构造器中指定的路径来获取parent
    }
    @Test
    public void test5() throws IOException {
        File f1 = new File("d:\\dyy\\javase\\HelloIO.java");//HelloIO.java是一个文件
        System.out.println("文件/目录的名称:" + f1.getName());//HelloIO.java
        System.out.println("文件/目录的构造路径名:" + f1.getPath());//d:\dyy\javase\HelloIO.java
        System.out.println("文件/目录的绝对路径名:" + f1.getAbsolutePath());//d:\dyy\javase\HelloIO.java
        System.out.println("文件/目录的规范路径名:" + f1.getCanonicalPath());//d:\dyy\javase\HelloIO.java
        System.out.println("文件/目录的父目录名:" + f1.getParent());//d:\dyy\javase
    }

    @Test
    public void test04(){
        File file = new File("d:/hello_210622Java_柴林燕_JavaSE");
        System.out.println("name=" + file.getName());
        System.out.println("path=" + file.getPath());
        System.out.println("length=" + file.length());
        System.out.println("lastModified=" + file.lastModified());
        System.out.println("lastModified=" + new Date(file.lastModified()));
    }

    @Test
    public void test03(){
        File file = new File("d:/1.txt");
        System.out.println("name=" + file.getName());
        System.out.println("path=" + file.getPath());
        System.out.println("length=" + file.length());
        System.out.println("lastModified=" + file.lastModified());
        System.out.println("lastModified=" + new Date(file.lastModified()));
    }
}

../../HelloIO.java

为什么可以这样写?

因为在命令行中:

cd .. 表示返回上级目录

cd . 表示当前目录

  • public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。

  • public boolean delete() :删除由此File表示的文件或目录。 只能删除空目录。 删除后回收站中也没有。操作时一定要谨慎。

  • public boolean mkdir() :创建由此File表示的目录。

  • public boolean mkdirs() :创建由此File表示的目录,如果父目录不存在,也一并创建。

    思考:如何删除非空目录? 思路:逐级删除,先把目录的下一级删除,然后才能删除自己。

  • public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。
  • public File[] listFiles():返回一个File数组,表示该File目录中的所有的子文件或目录。
  • public File[] listFiles(FileFilter filter):返回所有满足指定过滤器的文件和目录。如果给定 filter 为 null,则接受所有路径名。否则,当且仅当在路径名上调用过滤器的 FileFilter.accept(java.io.File) 方法返回 true 时,该路径名才满足过滤器。如果当前File对象不表示一个目录,或者发生 I/O 错误,则返回 null。FileFilter是一个File过滤器接口。

API测试

public class TestFile2 {
    @Test
    public void test11()  {
        File dir = new File("d:/hello_210622Java_柴林燕_JavaSE");
        File[] files = dir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return pathname.isFile();
            }
        });//用匿名内部类的形式实现FileFilter
        for (File file : files) {
            System.out.println(file);
        }
    }

    @Test
    public void test10()  {
        File dir = new File("d:/hello_210622Java_柴林燕_JavaSE");
        File[] files = dir.listFiles();
        for (File sub : files) {
            System.out.println(sub);
        }
    }

    @Test
    public void test09()  {
        File file = new File("d:/hello_210622Java_柴林燕_JavaSE");
        String[] strings = file.list();
        for (String string : strings) {//数组也支持foreach
            System.out.println(string);
        }
    }

    @Test
    public void test08()  {
        File file = new File("java");//相对路径
        file.delete();
    }

    @Test
    public void test07()  {
        File file = new File("hello");//相对路径
        file.delete();
    }

    @Test
    public void test06()  {
        File file = new File("d:/1.txt");//绝对路径
        file.delete();
    }

    @Test
    public void test05() throws IOException {
        File dir = new File("java/dyy");//相对路径
        dir.mkdirs();
    }

    @Test
    public void test04() throws IOException {
        File dir = new File("hello");//相对路径
        dir.mkdir();
    }


    @Test
    public void test03() throws IOException {
        File file = new File("/222.txt");//相对路径
        file.createNewFile();
    }

    @Test
    public void test02() throws IOException {
        File file = new File("22.txt");//相对路径
        file.createNewFile();
    }

    @Test
    public void test01() throws IOException {
        File file = new File("d:/2.txt");
        file.createNewFile();
    }
}

4 如何获取某个目录的所有下一级(包括下一级的下一级)?

思路:先获取第一级,然后如果第一级中有目录,继续获取它的下一级。

如何实现?

我们可以声明一个方法,获取某个目录的下一级,然后得到它的下一级之后,再对下一级递归调用当前的方法。

5 如何计算某个文件夹的大小?

思路:把文件夹中每一级的文件的大小累加起来。

如何实现?

定义一个方法,可以累加某个文件夹的下一级的大小。如果它的下一级仍然是文件夹,那么再递归调用当前方法。

6 删除非空目录?

思路:逐级删除,先把目录的下一级删除,然后才能删除自己。

如何实现?

声明一个方法,可以删除某个目录的下一级如果它的下一级仍然是目录,再继续调用当前方法。 谨慎选择测试目录!!!

IO流

1、IO代表什么?

I:input 输入

O:output 输出

Java中用于数据的输入和输出的API。

io:第一代

nio:第二代

2、IO的特点:

(1)单向的

输入流:只能读数据

输出流:只能写数据

(2)阻塞式

3、IO流的分类

(1)按照方向分:

输入流和输出流

(2)按处理数据的方式:

字节流和字符流

字节流:以字节为单位,适用于处理任意类型的数据。

​ 任意类型:字符、数字、图片、视频、音频等

字符流:以字符为单位,仅仅适用于纯文本数据

​ 哪些文件是纯文本?

​ .txt,.java,.html,.css,.js,....

(3)按照角色不同

节点流和处理流

节点流:与数据的端点(起点和终点)直接连接的IO流,比如:FileInputStream等,ByteArrayInputStream等,

处理流:是指给其他IO流增加辅助功能用的,或者说用于包装其他IO流的IO流,即不能单独使用。

​ 例如:BufferedInputStream等,DataInputStream等,ObjectInputStream等

​ 增加缓冲功能(提高效率) 对其他数据类型做处理用 对对象做处理的

4、四个抽象基类(超类、抽象类)

InputStream:字节输入流系列的父类

OutputStream:字节输出流系列的父类

Reader:字符输入流系列的父类

Writer:字符输出流系列的父类

IO流类型的实现类:

读写文件系列的IO流:

FileInputStream(路径)

FileOutputStream(路径)

FileReader(路径)

FileWriter(路径)

读写文件系列的IO流的包装流:(装饰器模式)

BufferedInputStream(接收FileInputStream)

BufferedOutputStream(接收FileOutputStream)

BufferedReader(接收FileReader)

BufferedWriter(接收FileWriter)

FileInputStream:用于读文件,以字节的方式,适用于任何类型的文件。 但是,如果要在控制台显示文件的内容,只能以文本文件为例。

字节读取转换为字符读取的转换流

InputStreamReader(接收InputStream)

OutputStreamWriter(接收OutStream)

数据流(游戏数据等,存取自定义的文件类型)(变量到文件)

DataInputStream:(接收InputStream)

DataOutputStream:(接收OutStream)

对象流 (对象到文件、网络)

​ ObjectOutputStream(接收OutStream)

​ ObjectInputStream(接收InputStream)

打印流

PrintStream(可以被BufferedWriter包装)

Scanner(可以被BufferedReader包装)

(1)FileInputStream:

(1)int read():一次读取1个字节,如果已经到达流末尾,返回-1,否则返回读取的内容的字节值

(2)int read(byte[] b):一次读取多个字节,读取的字节放到b数组中,如果已经到达流末尾,返回-1,否则返回本次读取的字节数量。

(3)int read(byte[] b,int off,int len):一次读取多个字节,读取的字节放到b数组中,如果已经到达流末尾,返回-1,否则返回本次读取的字节数量。

(3)和(2)不同的是,(2)的话一次最多读取b.length个,(3)的话一次最多读取len个。(2)的话,读取完的数据是从b数组的[0]开始存储(3)的话,读取完的数据是从b数组的[off]开始存储

public class TestFileInputStream {
    @Test
    public void test04() throws IOException {
        //(1)创建FileInputStream的对象,参数:文件的路径名或者File对象
        FileInputStream fis = new FileInputStream("d:/1.txt");

        //(2)开始读取
        byte[] data = new byte[3];
        int len;
        StringBuilder s = new StringBuilder();//可变字符序列
        while((len = fis.read(data))!=-1){
            s.append(new String(data,0, len));
        }
        System.out.println(s);

        //(3)关闭
        fis.close();
    }

    @Test
    public void test03() throws IOException {
        //(1)创建FileInputStream的对象,参数:文件的路径名或者File对象
        FileInputStream fis = new FileInputStream("d:/1.txt");

        //(2)开始读取
        byte[] data = new byte[3];
        int len;
        while((len = fis.read(data))!=-1){
            System.out.println("本次读取:" + len + "个字节");
            System.out.println("本次读取的字节内容:" + Arrays.toString(data));
            System.out.println("本次读取的字符内容:" + new String(data,0,len));
            /*
            把字节数组的内容转为字符-->解码
            String(byte[] bytes) :用整个字节数组的内容构建字符串
            String(byte[] bytes, int offset, int length) :用部分字节数组的内容构建字符串,从bytes[offset]开始,取length个字节
             */
        }

        //(3)关闭
        fis.close();
    }


    @Test
    public void test02() throws IOException {
        //(1)创建FileInputStream的对象,参数:文件的路径名或者File对象
        FileInputStream fis = new FileInputStream("d:/1.txt");

        //(2)开始读取
        byte[] data = new byte[3];
        System.out.println(fis.read(data));//3
        System.out.println(fis.read(data));//1
        System.out.println(fis.read(data));//-1

        //(3)关闭
        fis.close();
    }

    //FileNotFoundException是IOException的子类
    @Test
    public void test01() throws IOException {
        //(1)创建FileInputStream的对象,参数:文件的路径名或者File对象
        FileInputStream fis = new FileInputStream("d:/1.txt");

        //(2)开始读取
        System.out.println(fis.read());//从fis这个输入流中读取1个字节,这个fis流与“d:/1.txt"联通
        System.out.println(fis.read());//从fis这个输入流中读取1个字节,这个fis流与“d:/1.txt"联通
        System.out.println(fis.read());//从fis这个输入流中读取1个字节,这个fis流与“d:/1.txt"联通
        System.out.println(fis.read());//从fis这个输入流中读取1个字节,这个fis流与“d:/1.txt"联通
        System.out.println(fis.read());//从fis这个输入流中读取1个字节,这个fis流与“d:/1.txt"联通

        //(3)关闭IO流
        //因为IO流操作仅仅依赖JVM是办不到的,是需要依赖OS(操作系统)来协助的。
        //IO操作除了相应的对象要在JVM中占内存,还需要占用OS的相应内存
        //所以我们用完之后,需要告诉OS释放对应内存。
        //JVM的内存由GC会自动回收。
        fis.close();
    }
}

(2)FileOutputStream:

用于输出数据到文件,以字节的方式。适用于任意类型的文件。

说明:在当前程序中,暂时以文本数据演示。

OutputStream系列的方法:

(1)write(int b):一次输出一个字节

(2)write(byte[] b):一次输出整个字节数组的内容

(3)write(byte[] b, int off, int len):一次输出部分字节数中的内容,从b[off]开始,len个字节。

public class TestFileOutputStream {
    @Test
    public void test04() throws IOException {
        //(1)创建FileOutputStream流,参数是
        FileOutputStream fos = new FileOutputStream("d:/2.txt", true);//如果文件不存在,会自动创建
                                                                                    //如果文件存在,就在后面追加内容
        //文件存在,会自动覆盖

        //(2)输出内容  " xiao gao",接在2.txt现在内容的后面
        /*
        byte[] getBytes() :以平台默认的字符集将字符串转为字节数据-->编码
         */
        fos.write(" xiao gao".getBytes());

        //(3)关闭
        fos.close();
    }

    @Test
    public void test03() throws IOException {
        File file = new File("d:/2.txt");
        file.createNewFile();//如果存在,就不创建
    }


    @Test
    public void test02() throws IOException {
        //(1)创建FileOutputStream流,参数是
        FileOutputStream fos = new FileOutputStream("d:/2.txt");//如果文件不存在,会自动创建
                                                                        //文件存在,会自动覆盖

        //(2)输出内容  "hi"
        /*
        byte[] getBytes() :以平台默认的字符集将字符串转为字节数据-->编码
         */
        fos.write("hi".getBytes());

        //(3)关闭
        fos.close();
    }

    @Test
    public void test01() throws IOException {
        //(1)创建FileOutputStream流,参数是
        FileOutputStream fos = new FileOutputStream("d:/2.txt");//如果文件不存在,会自动创建

        //(2)输出内容  "hello"
        /*
        byte[] getBytes() :以平台默认的字符集将字符串转为字节数据-->编码
         */
        fos.write("hello".getBytes());

        //(3)关闭
        fos.close();
    }
}

(3)FileReader:

​ 用于读取纯文本文件,以字符的方式Reader系列的IO流的方法:

(1)int read() :一次读取一个字符,如果到达流末尾返回-1,否则返回该字符的Unicode编码值

(2)int read(char[] cbuf):一次多个字符,放到cbuf数组中,从[0]开始存储,如果到达流末尾返回-1,否则返回本次读取的字符数。最多一次读取cbuf.length个

(3)int read(char[] cbuf, int off, int len)一次多个字符,放到cbuf数组中,从[off]开始存储,如果到达流末尾返回-1,否则返回本次读取的字符数,最多一次读取len个

FileWriter:用于输出数据到纯文本,以字符的方式

public class TestFileReader {
    @Test
    public void test01() throws IOException {
        //(1)创建FileReader对象,参数是指定从哪个文件读取
        FileReader fr = new FileReader("d:/2.txt");

        //(2)读取
        int len;
        char[] data = new char[10];
        while((len = fr.read(data)) != -1){
            System.out.print(new String(data,0,len));
        }

        //(3)关闭
        fr.close();
    }
}

(4)FileWriter:

(1) void write(int c):一次写一个字符

(2) void write(char[] cbuf):一次写整个字符数组

(3)void write(char[] cbuf, int off, int len)一次写部分字符数组,从[off]开始写len个

(4)void write(String str):一次写一个字符串

(5)void write(String str, int off, int len) :一次写字符串的一部分,从[off]开始写len个

public class TestFileWriter {
    @Test
    public void test02() throws IOException {
        //(1)创建一个FileWriter的对象,参数:把数据输出到哪个文件
        FileWriter fw = new FileWriter("d:/1.txt",true);

        //(2)写数据  "hello"
        fw.write("hello");

        //(3)关闭
        fw.close();
    }

    @Test
    public void test01() throws IOException {
        //(1)创建一个FileWriter的对象,参数:把数据输出到哪个文件
        FileWriter fw = new FileWriter("d:/1.txt");

        //(2)写数据  "hello"
        fw.write("hello");

        //(3)关闭
        fw.close();
    }
}

需求:实现复制文件的基础版

public class Files {

    /**
     * 复制文件
     * @param srcFilePathName String 源文件路径名
     * @param destFilePathName String 目标文件路径名
     */
    public static void copyFile(String srcFilePathName, String destFilePathName) throws IOException {
        //判断srcFilePathName是否是文件夹或者不存在
        File srcFile = new File(srcFilePathName);
        if(!srcFile.exists()){
            throw new FileNotFoundException(srcFilePathName + "源文件不存在");
        }
        if(!srcFile.isFile()){
            throw new IOException(srcFilePathName +"不是一个文件");
        }
        File destFile = new File(destFilePathName);
        if(destFile.isDirectory()){
            //把srcFilePathName文件复制到destFilePathName文件夹中
            destFilePathName = destFilePathName + "/" + srcFile.getName();
            //destFilePathName是目标文件夹的路径
            //srcFile.getName():是源文件的名称
            //destFilePathName + "/" + srcFile.getName():新文件的路径名
        }

        //(1)先创建FileInputStream的对象,用于读取源文件的数据
        FileInputStream fis = new FileInputStream(srcFilePathName);
        //(2)再创建FileOutputStream的对象,用于输出数据到目标文件
        FileOutputStream fos =  new FileOutputStream(destFilePathName);
        //(3)一边读一边写
        int len;
        byte[] data = new byte[1024];//1KB
        //fis.read(data)把数据从fis读取到data中
        while((len = fis.read(data))!=-1){
            //把数据从data中输出到fos中
            fos.write(data, 0, len);
//            fos.write(data);//如果最后一次读取的字节数少于data.length,会导致上次读取的数据重复写到文件中
        }

        //(4)关闭
        fis.close();
        fos.close();

    }
}
package com.dyy.io;

import org.junit.Test;

import java.io.IOException;

public class TestFiles {
    @Test
    public void test02() throws IOException {
        Files.copyFile("D:\\day0720_01集合框架集图.avi","java");
    }

    @Test
    public void test01() throws IOException {
        Files.copyFile("D:\\dyy\\javaee\\JavaSE20210622\\video\\day0720_01集合框架集图.avi","d:\\day0720_01集合框架集图.avi");
    }
}

(5)转换流:OutputStreamWriter

​ OutputStreamWriter:用于将字节流类型的IO流转为字符流类型的IO流, 数据是从字符类型的数据转为字节类型的数据输出。

​ OutputStreamWriter是一个处理流,用来包装其他IO流,依赖于其他IO流。

​ OutputStreamWriter(OutputStream out):仍然使用平台默认的编码

​ OutputStreamWriter(OutputStream out, String charsetName):可以指定编码

什么情况下用它?

当我们在输出数据时,程序中的编码与文件的编码不一致,那么可以使用它进行处理。

public class TestOutputStreamWriter {
    @Test
    public void test03() throws IOException {
        //(1)创建一个FileWriter的对象,参数:把数据输出到哪个文件
        FileOutputStream fos = new FileOutputStream("d:/1.txt",true);

        //(2)写数据  "hello"
        fos.write("hello".getBytes("GBK"));

        //(3)关闭
        fos.close();
    }

    @Test
    public void test02() throws IOException {
        //(1)创建一个FileWriter的对象,参数:把数据输出到哪个文件
        FileOutputStream fos = new FileOutputStream("d:/1.txt",true);
        //(2)使用OutputStreamWriter进行包装
        OutputStreamWriter osw = new OutputStreamWriter(fos,"GBK");
        //从IO流的类型转换看,是不是像 把fos字节流类型,转为osw字符流

        //(2)写数据  "hello"
        //先把数据"hello"写到osw字符流中,以字符的方式,然后 oswIO流再把数据 处理(编码)写到fos,以字节的方式,最后到达"d:/1.txt"
        osw.write("hello");

        //(3)关闭
        osw.close();
        fos.close();
    }

    @Test
    public void test01() throws IOException {
        //(1)创建一个FileWriter的对象,参数:把数据输出到哪个文件
        FileWriter fw = new FileWriter("d:/1.txt",true);//不支持编码的转换
                                    //默认是以程序的编码输出的,例如当前程序是UTF-8

        //(2)写数据  "hello"
        fw.write("hello");

        //(3)关闭
        fw.close();
    }
}

(6)转换流:InputStreamReader

InputStreamReader:用于将字节流类型的IO流转为字符流类型的IO流, 数据是从字节类型的数据转为字符类型的数据输入。

InputStreamReader也是处理流,用于包装其他IO流。

InputStreamReader(InputStream in):用默认编码处理

InputStreamReader(InputStream in, String charsetName) :指定编码处理

转换流:InputStreamReader和OutputStreamWriter

public class TestInputStreamReader {
    @Test
    public void test03() throws IOException {
        FileInputStream fis = new FileInputStream("d:/1.txt");
        InputStreamReader isr = new InputStreamReader(fis, "GBK");
        char[] data = new char[10];
        isr.read(data);
        System.out.println(new String(data));
        isr.close();
        fis.close();
    }


    @Test
    public void test02() throws IOException {
        FileInputStream fis = new FileInputStream("d:/1.txt");
        byte[] data = new byte[10];
        fis.read(data);
        System.out.println(new String(data, "GBK"));
        fis.close();
    }

    @Test
    public void test01() throws IOException {
        FileReader fr = new FileReader("d:/1.txt");
        char[] data = new char[10];
        fr.read(data);
        System.out.println(new String(data));
        fr.close();
    }
}

(7)缓冲流(BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter)

缓冲IO流:

BufferedInputStream:在其他字节输入流的基础上增加缓冲功能

BufferedOutputStream:在其他字节输出流的基础上增加缓冲功能

BufferedReader:在其他字符输入流的基础上增加缓冲功能

BufferedWriter:在其他字符输出流的基础上增加缓冲功能

以BufferedInputStream为例,说明他们是缓冲流,也是处理流,需要依赖其他IO流。

BufferedInputStream(InputStream in)

BufferedInputStream(InputStream in, int size)

为什么使用缓冲IO流就能加快运行?

​ 缓冲流内部有一个缓冲区(本质就是一个数组),这个数组的大小/长度默认是8192。

​ 当我们从文件读取数据时,会先把数据读取到缓冲流的缓冲区中,然后当缓冲区满的时候,或者我们调用flush()或close()时,会把缓冲区的数据一次读取过来。或者这么说,原来的话,从文件读取的话,byte[] data,一次从文件读取data.length(例如1024个字节),现在的话,从文件先缓冲8192个字节在缓冲区中,然后我们用byte[] data,从缓冲区中一次取1024个字节。从文件读取的话,是从硬盘到JVM 内存,从缓冲区读取的话,是从JVM内存到JVM内存。

public class TestBuffered {
    @Test
    public void test02()throws IOException {
        long start = System.currentTimeMillis();
        Files.copyFile("d:/day0720_01集合框架集图.avi","d:/3.avi");
        long end = System.currentTimeMillis();
        System.out.println("耗时:" + (start - end));//1508
    }
    @Test
    public void test01()throws IOException {
        long start = System.currentTimeMillis();
        FileInputStream fis = new FileInputStream("d:/day0720_01集合框架集图.avi");
        FileOutputStream fos = new FileOutputStream("d:/2.avi");

        BufferedInputStream bis = new BufferedInputStream(fis);
        BufferedOutputStream bos = new BufferedOutputStream(fos);

        byte[] data = new byte[1024];
        int len;
        while((len = bis.read(data)) != -1){
            bos.write(data, 0, len);
        }

        bis.close();
        fis.close();
        bos.close();
        fos.close();
        long end = System.currentTimeMillis();
        System.out.println("耗时:" + (start - end));//301
    }
}

问题:不使用缓冲流,自己把byte[] data数组定义的大一点,是不是也可以很快呢?

答案:肯定的。但是不可能无限大的,系统分配给JVM的内存是有限的。

(8)数据流(DataInputStream,DataOutputStream)

DataInputStream:

DataOutputStream:

一对,用DataOutputStream写,用DataInputStream读,并且读的顺序要与写的顺序一致。

需求:要将程序中的许多变量的值输出到文件中,下次读取时,再给同样类型的变量赋值。

场景:单机游戏 玩的过程中,临时需要退出,需要将一些游戏的状态保存,下次从当前的状态继续玩。

DataOutputStream(OutputStream out) 也是处理流(包装流,装饰流),需要依赖其他IO流

DataOutputStream输出的数据不是纯文本的,所以建议不要用.txt这样的纯文本文件存储,可以用.dat等类型的文件保存。 .dat是我们自己随便写的一个文件后缀名

为什么可以随便写? 因为这个文件不是由其他程序/软件来读取的,它是由我们的另一段Java程序读取。

public class TestDataStream {
    @Test
    public void test04() throws IOException {
        FileInputStream fis = new FileInputStream("d:/game.dat");
        DataInputStream dis = new DataInputStream(fis);

        String role = dis.readUTF();
        int age = dis.readInt();
        char gender = dis.readChar();
        double power = dis.readDouble();
        boolean b = dis.readBoolean();

        System.out.println("role = " + role);
        System.out.println("age = " + age);
        System.out.println("gender = " + gender);
        System.out.println("power = " + power);
        System.out.println("b = " + b);

        dis.close();
        fis.close();
    }
    @Test
    public void test03() throws IOException {
        //游戏角色
        String role = "小高";
        int age = 250;
        char gender = '妖';
        double energy = 89.5;
        boolean isLiveable = false;

        FileOutputStream fos = new FileOutputStream("d:/game.dat");
        DataOutputStream dos = new DataOutputStream(fos);
        dos.writeUTF(role);
        dos.writeInt(age);
        dos.writeChar(gender);
        dos.writeDouble(energy);
        dos.writeBoolean(isLiveable);

        dos.close();
        fos.close();
    }

    @Test
    public void test02() throws IOException {
        FileReader fr = new FileReader("d:/game.txt");

//        String role = fr.read();//读取几个字符是角色名
//        int age = fr.read();//读取几个字符是年龄
    }

    @Test
    public void test01() throws IOException {
        //游戏角色
        String role = "小高";
        int age = 250;
        char gender = '妖';
        double energy = 89.5;
        boolean isLiveable = false;

        //使用FileWriter输出
        FileWriter fw = new FileWriter("d:/game.txt");
        fw.write(role);
        fw.write(age);
        fw.write(gender);
//        fw.write(energy);//错误
//        fw.write(isLiveable);//错误
        fw.write(energy+"");
        fw.write(isLiveable+"");
//        除非进行类型的转换,转换成String,下次读取时,非常麻烦,没法处理
        //如果要这么处理,也可以一个变量的值占一行,恢复时,按行处理

        fw.close();
    }
}

(9)对象流

​ ObjectOutputStream:把对象转为字节序列,这个过程称为“序列化”

​ ObjectInputStream:把字节序列中是数据“重构”或“还原”成Java对象,这个过程称为“反序列化”

什么情况下使用? 当我们Java程序中,想要直接输出“对象”到文件、或者发送“对象”到网络中等情况时,就可以使用对象IO流。

注意:

①要通过ObjectOutputStream流输出的对象的类型必须实现java.io.Serializable接口。否则会发生java.io.NotSerializableException:不支持序列化异常Serializable接口是一个标识型接口,没有抽象方法的。只是标识类型的作用,就像Cloneable接口一样。

​ 在底层中,要被克隆的对象,或者说当我们调用某个对象的clone方法时,JVM会检查这个对象是否属于Cloneable类型,只有属于这个类型的,才能帮你做克隆。

​ 在底层中,要被序列化的对象,或者说当我们调用ObjectOutputStream的writeObject方法时,JVM会检测这个对象是否属于Serializable类型,只有属于这个类型的,才能帮你做序列化。

比喻:出国时候,上岸/下机时,会有人检测你是否拥有“中华人民共和国的护照”, 如果有,那么就正常通关,否则就要遣回。

②通过ObjectOutputStream流输出的数据到达文件后,我们也看不懂,因为里面不是纯文本,它是给另一个段Java程序看的。

③在反序列化过程中,可能会遇到ClassNotFoundException异常

④结论:凡是实现Serializable接口,都要加一个private static final long serialVersionUID。

​ 当我们序列化完成之后,对所序列化的对象的类型(例如:Student)做了修改,然后再去运行反序列化程序时,就报:java.io.InvalidClassException,类无效异常。

例如:

java.io.InvalidClassException:

com.dyy.io.Student; local class incompatible:

stream classdesc serialVersionUID = -355407171577198872,

local class serialVersionUID = -6337391953310496088

​ 因为我们Student类实现了Serializable接口,每次编译Student类,编译器会自动给这个类的.class标记一个序列化版本ID(serialVersionUID),只要重新编译(即修改了类),serialVersionUID就会改变。这样做的目的是,防止我们序列化对象之后,对类做大的改动,影响我们对象的反序列化。

​ 例如:原来有name,age,分别是String和int类型,后面把这两个的类型给变了,这样的话,如果没有机制阻止这种情况的话,是有安全问题的。一律修改都不允许。

如何解决呢?

​ 我们刚才的修改操作(加了无参构造),并不会影响对象的反序列化的安全问题。我们希望这样的修改操作,被允许,那么怎么办?可以给这个类(例如:Student类)加一个序列化版本ID(serialVersionUID)。

序列化版本ID(serialVersionUID)要求是:

A:long类型

B:static和final

为什么? static表示静态的,所有对象共享的。

​ final表示最终的,不能修改的

C:通常也会是private

​ 说明:如果是在所有序列化之前,就加了serialVersionUID,那么这个值是多少都可以,通常我们会在源码中看到1L。

⑤是不是对象中所有的属性都要序列化?

A:对象的属性参与序列化,都要实现Serializable接口。(基本数据类型除外)

B:静态的变量是不会序列化的

​ 为什么?

​ 因为我们序列化是针对“对象”,也就是说,要保存/持久化的是对象的状态(非静态的变量),和类变量无关。

​ 因为类变量是所有对象“共享的”,不应该由其中一个对象来“保存”。

C:如果某个成员变量的值不想要序列化,可以加一个transient来修饰,否则实例变量都会序列化。

transient:瞬时的,易变化的。

举例:

学生类型对象

//学生类
public class Student implements Serializable {
    private static final long serialVersionUID = -355407171577198872L;
    private String name;
    private int age;
    private transient String password; //密码
    private Teacher teacher; //班主任
    private static String country;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Student(String name, int age, String password, Teacher teacher) {
        this.name = name;
        this.age = age;
        this.password = password;
        this.teacher = teacher;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Teacher getTeacher() {
        return teacher;
    }

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }

    public static String getCountry() {
        return country;
    }

    public static void setCountry(String country) {
        Student.country = country;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", password='" + password + '\'' +
                ", teacher=" + teacher +
                ", country=" + country +
                '}';
    }
}
//
public class TestObjectIO {
    @Test
    public void test04() throws IOException, ClassNotFoundException {
        //读取stu2.dat文件中的Student对象
        //要从文件读取,需要:FileInputStream
        FileInputStream fis = new FileInputStream("stu2.dat");//相对路径
        //要把stu.dat的字节序列,还原成一个Java对象,所以需要用ObjectInputStream
        ObjectInputStream ois = new ObjectInputStream(fis);

        //读取对象
        Object object = ois.readObject();
        System.out.println(object);

        //关闭
        ois.close();
        fis.close();
    }

    @Test
    public void test03() throws IOException {
        //创建学生对象
        Student stu = new Student("张三",78,"123456", new Teacher());
        Student.setCountry("中国");//静态变量
//        stu.setCountry("中国");
        //要把stu对象直接保存到 stu2.dat
        //要输出到文件:FileOutputStream
        FileOutputStream fos = new FileOutputStream("stu2.dat");//相对路径
        //因为要直接输出对象,所以需要用ObjectOutputStream,
        ObjectOutputStream oos = new ObjectOutputStream(fos);

        //输出stu对象
        oos.writeObject(stu);//把对象转为字节序列输出
        //如果stu对象的类型Student没有实现java.io.Serializable接口
        //那么就会把java.io.NotSerializableException:不支持序列化异常

        //关闭
        oos.close();
        fos.close();
    }

    @Test
    public void test02() throws IOException, ClassNotFoundException {
        //读取stu.dat文件中的Student对象
        //要从文件读取,需要:FileInputStream
        FileInputStream fis = new FileInputStream("stu.dat");//相对路径
        //要把stu.dat的字节序列,还原成一个Java对象,所以需要用ObjectInputStream
        ObjectInputStream ois = new ObjectInputStream(fis);

        //读取对象
        Object object = ois.readObject();
        System.out.println(object);

        //关闭
        ois.close();
        fis.close();
    }

    @Test
    public void test01() throws IOException {
        //创建学生对象
        Student stu = new Student("张三",78);

        //要把stu对象直接保存到 stu.dat
        //要输出到文件:FileOutputStream
        FileOutputStream fos = new FileOutputStream("stu.dat");//相对路径
        //因为要直接输出对象,所以需要用ObjectOutputStream,
        ObjectOutputStream oos = new ObjectOutputStream(fos);

        //输出stu对象
        oos.writeObject(stu);//把对象转为字节序列输出
        //如果stu对象的类型Student没有实现java.io.Serializable接口
        //那么就会把java.io.NotSerializableException:不支持序列化异常

        //关闭
        oos.close();
        fos.close();
    }
}

问题:如何序列化多个对象

当我们序列化不止一个对象,怎么办?

问题:

使用追加模式,先后输出了两个对象,

在反序列化时,连续读取两个对象时,发生了java.io.StreamCorruptedException: invalid type code: AC异常。

这个异常的意思时,无效的编码标识。

因为我们每次序列化完成之后,会在后面加一个“序列化结束标识”,

那么我们如果使用追加模式,会在“序列化结束标识”后面追加新的对象信息。

在反序列化时,读完一个对象,再读第二个对象时,它先遇到的是“序列化结束标识”,而不是一个对象的信息, 所以认为invalid type code: AC

如何解决?

通过我们把多个对象是用“容器”保存,然后输出“容器”,

下次如果还要再加新对象,会先把原来的数据反序列化,在反序列化的容器中,添加新对象,然后再序列化。

public class TestManyObjects {
    @Test
    public void test08() throws IOException, ClassNotFoundException {
        //读取stu3.dat文件中的Student对象
        //要从文件读取,需要:FileInputStream
        FileInputStream fis = new FileInputStream("stu4.dat");//相对路径
        //要把stu.dat的字节序列,还原成一个Java对象,所以需要用ObjectInputStream
        ObjectInputStream ois = new ObjectInputStream(fis);

        //读取对象
        Object object1 = ois.readObject();
        System.out.println(object1);


        //关闭
        ois.close();
        fis.close();
    }

    @Test
    public void test07() throws IOException, ClassNotFoundException {
        //创建学生对象
        Student stu = new Student("李四",96,"123456", new Teacher());

        //先把原来的读取回来
        FileInputStream fis = new FileInputStream("stu4.dat");//相对路径
        ObjectInputStream ois = new ObjectInputStream(fis);

        //读取对象
        ArrayList<Student> list = (ArrayList<Student>) ois.readObject();
        list.add(stu);

        ois.close();
        fis.close();

        //要把stu对象直接保存到 stu3.dat
        //要输出到文件:FileOutputStream
        FileOutputStream fos = new FileOutputStream("stu4.dat");//相对路径
        //因为要直接输出对象,所以需要用ObjectOutputStream,
        ObjectOutputStream oos = new ObjectOutputStream(fos);

        //输出list对象
        oos.writeObject(list);//把对象转为字节序列输出
        //如果stu对象的类型Student没有实现java.io.Serializable接口
        //那么就会把java.io.NotSerializableException:不支持序列化异常

        //关闭
        oos.close();
        fos.close();
    }

    @Test
    public void test06() throws IOException {
        //创建学生对象
        Student stu = new Student("张三",78,"123456", new Teacher());
        ArrayList<Student> list = new ArrayList<>();
        list.add(stu);

        //要把list对象直接保存到 stu4.dat
        //要输出到文件:FileOutputStream
        FileOutputStream fos = new FileOutputStream("stu4.dat",true);//相对路径
        //因为要直接输出对象,所以需要用ObjectOutputStream,
        ObjectOutputStream oos = new ObjectOutputStream(fos);

        //输出list对象
        oos.writeObject(list);//把对象转为字节序列输出
        //如果stu对象的类型Student没有实现java.io.Serializable接口
        //那么就会把java.io.NotSerializableException:不支持序列化异常

        //关闭
        oos.close();
        fos.close();
    }

    @Test
    public void test05() throws IOException, ClassNotFoundException {
        //读取stu3.dat文件中的Student对象
        //要从文件读取,需要:FileInputStream
        FileInputStream fis = new FileInputStream("stu3.dat");//相对路径
        //要把stu.dat的字节序列,还原成一个Java对象,所以需要用ObjectInputStream
        ObjectInputStream ois = new ObjectInputStream(fis);

        //读取对象
        Object object1 = ois.readObject();
        System.out.println(object1);

        Object object2 = ois.readObject();
        System.out.println(object2);

        //关闭
        ois.close();
        fis.close();
    }

    @Test
    public void test04() throws IOException {
        //创建学生对象
        Student stu = new Student("李四",96,"123456", new Teacher());
        //要把stu对象直接保存到 stu3.dat
        //要输出到文件:FileOutputStream
        FileOutputStream fos = new FileOutputStream("stu3.dat",true);//相对路径
        //因为要直接输出对象,所以需要用ObjectOutputStream,
        ObjectOutputStream oos = new ObjectOutputStream(fos);

        //输出stu对象
        oos.writeObject(stu);//把对象转为字节序列输出
        //如果stu对象的类型Student没有实现java.io.Serializable接口
        //那么就会把java.io.NotSerializableException:不支持序列化异常

        //关闭
        oos.close();
        fos.close();
    }
    @Test
    public void test03() throws IOException {
        //创建学生对象
        Student stu = new Student("张三",78,"123456", new Teacher());
        //要把stu对象直接保存到 stu3.dat
        //要输出到文件:FileOutputStream
        FileOutputStream fos = new FileOutputStream("stu3.dat");//相对路径
        //因为要直接输出对象,所以需要用ObjectOutputStream,
        ObjectOutputStream oos = new ObjectOutputStream(fos);

        //输出stu对象
        oos.writeObject(stu);//把对象转为字节序列输出
        //如果stu对象的类型Student没有实现java.io.Serializable接口
        //那么就会把java.io.NotSerializableException:不支持序列化异常

        //关闭
        oos.close();
        fos.close();
    }
}

补充:Externalizable接口

问题:除了Serializable接口之外,还可以实现什么接口进行序列化?

答案:java.io.Externalizable接口

这个Externalizable接口有两个抽象方法,

  • void readExternal(ObjectInput in)

  • void writeExternal(ObjectOutput out)

这两个抽象方法需要重写。

作用是定义,序列化时,

(1)哪些属性序列化,包括static和transient也可以手动序列化,但是不建议这么做

(2)顺序

关于哪些属性序列化和反序列化,由程序员自己定。

Externalizable接口有一个要求,被序列化的对象的类型必须包含“无参构造”

public class Chinese implements Externalizable {
    private static final long serialVersionUID = 1L;
    private static String county;
    private String name;
    private transient int age;

    public Chinese() {
    }

    public Chinese(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static String getCounty() {
        return county;
    }

    public static void setCounty(String county) {
        Chinese.county = county;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(county);
        out.writeUTF(name);
        out.writeInt(age);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        county = in.readUTF();
        name = in.readUTF();
        age = in.readInt();
    }

    @Override
    public String toString() {
        return "Chinese{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class TestExternalizable {
    @Test
    public void test02() throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream("chinese.dat");
        ObjectInputStream ois = new ObjectInputStream(fis);

        Chinese.setCounty("中华人民共和国");
        System.out.println(ois.readObject());
        System.out.println(Chinese.getCounty());

        ois.close();
        fis.close();
    }

    @Test
    public void test01() throws IOException {
        Chinese chinese = new Chinese("张三",23);
        Chinese.setCounty("中国");

        FileOutputStream fos = new FileOutputStream("chinese.dat");
        ObjectOutputStream oos = new ObjectOutputStream(fos);

        oos.writeObject(chinese);

        oos.close();
        fos.close();
    }
}

(10)打印流

PrintStream: PrintWriter:学习web时,服务器端给客户端返回消息时,response响应对象.getWriter得到的就是它

System.out对象就是PrintStream类型的。

PrintStream中重载了很多print和println方法,用于输出信息。 细节: (1)println()有无参形式 print(x)没有无参形式 (2)print和println方法支持char[]

public class TestPrintStream {
    @Test
    public void test03(){
        int[] arr = {1,2,3,4};
        System.out.println(arr);//[I@5d6f64b1
        // 自动调用对象的toString方法,默认的toString方法就是返回 对象的运行时类型@对象的hashCode值

        char[] letters = {'a','b','c'};
        System.out.println(letters);//abc
    }


    @Test
    public void test02(){
        System.out.println();
//        System.out.print();//报错
    }

    @Test
    public void test01(){
        PrintStream ps = System.out;
        ps.println(1);
        ps.println(1.0);
        ps.println("hello");
    }
}

(11)System类中的IO流

System类中有三个IO流对象:

System.in:InputStream

System.out:PrintStream

System.err:PrintStream

疑问1:在System类中三个常量对象的声明如下:

public final static InputStream in = null;

public final static PrintStream out = null;

public final static PrintStream err = null;

​ in,out,err对象是final修饰,不能修改的。

看代码,它们已经初始化为null,后面怎么弄的?

​ 我们所说的final修饰的常量不能修改是针对Java语法来说的。

​ System类中in,out,err对象的创建是由C来完成的。

在System类初始化时:

  private static native void registerNatives();

   static {
        registerNatives();//在这个本地方法中,完成了对in,out,err对象的初始化
    }

疑问2:在System类中,提供了如下三个set方法

setIn

setOut

setErr

疑问的点,在Java中final修饰的成员变量,是没有set方法。

回答:这些set方法最终的实现,是由C完成的。

疑问3:既然有set方法,说明可以改,即可以重定向

public class TestSystem {
    public static void main(String[] args) throws FileNotFoundException {
        //重定向System.out输出到d:/1.txt
        PrintStream old = System.out;
        System.setOut(new PrintStream("d:/1.txt"));
        System.out.println("hello");
        System.out.println("world");
        System.setOut(old);
        System.out.println("java");
    }
}

class MyData{
    private static final String str = null;
/*    MyData(){
        str = "hello";//不能修改
    }
    static{
        str = "world";//不能修改
    }*/

    public static String getStr() {
        return str;
    }
}

补充:按行读和按行写

按行写:

(1)PrintStream

(2)BufferedWriter

(3)自己在内容后面加"\r\n"

按行读:

(1)Scanner

(2)BufferedReader

public class TestLine {
    @Test
    public void test04() throws IOException {
        FileReader fr = new FileReader("d:/3.txt");
        BufferedReader br = new BufferedReader(fr);

        String line;
        while((line = br.readLine()) != null){
            System.out.println(line);
        }

        br.close();
        fr.close();
    }

    @Test
    public void test03() throws FileNotFoundException {
        InputStream in = System.in;//保存原来的in对象,可以方便重定向回来
        System.setIn(new FileInputStream("d:/2.txt"));
        Scanner scanner = new Scanner(System.in);
        while(scanner.hasNextLine()){
            System.out.println(scanner.nextLine());
        }
        scanner.close();
        System.setIn(in);//重定向回键盘输入
    }


    @Test
    public void test02() throws IOException {
        //从键盘输入内容,按行输出到d:/3.txt
        Scanner input = new Scanner(System.in);
        FileWriter fw = new FileWriter("d:/3.txt");
        BufferedWriter bw = new BufferedWriter(fw);

        while(true){
            System.out.print("请输入内容:");
            String line = input.nextLine();

            if("stop".equalsIgnoreCase(line)){
                break;
            }

            bw.write(line);
            bw.newLine();//换行
        }

        bw.close();
        fw.close();
        input.close();
    }

    @Test
    public void test01() throws FileNotFoundException {
        //从键盘输入内容,按行输出到d:/2.txt
        Scanner input = new Scanner(System.in);
        PrintStream ps = new PrintStream("d:/2.txt");

        while(true){
            System.out.print("请输入内容:");
            String line = input.nextLine();

            if("stop".equalsIgnoreCase(line)){
                break;
            }

            ps.println(line);
        }

        ps.close();
        input.close();
    }
}

补充:流关闭的问题

问题:IO流出现很多层包装时,关闭IO流是否有顺序问题?

部分的IO流包装会出现问题。

有没有统一的关闭IO流的公式:

(1)方法一:先关外层,再关里面的

FileWriter fw = new FileWriter("d:/3.txt");

BufferedWriter bw = new BufferedWriter(fw);

bw是外层,fw是内层

关闭顺序:

​ bw.close()

​ fw.close();

(2)Java中IO流有改进,只要关闭最外层的IO流,即可。

public class TestClose {
    @Test
    public void test05() throws IOException {
        BufferedWriter bw = new BufferedWriter(new FileWriter("d:/3.txt"));

        bw.write("hello");

        bw.close();
    }


    @Test
    public void test04() throws IOException {
        FileWriter fw = new FileWriter("d:/3.txt");
        BufferedWriter bw = new BufferedWriter(fw);
        //BufferedWriter包装FileWriter
        //BufferedWriter依赖FileWriter

        bw.write("hello");

        fw.close();//先关被依赖的fw
        bw.close();//后关闭外层的包装IO流
        //运行上面的代码,发生java.io.IOException: Stream closed
        //因为BufferedWriter中的数据是先写到缓冲区中,
        //只有在(1)缓冲区满了,(2)调用flush或close方法时,才会刷出数据
        //此时如果我们bw.close()要刷数据到文件时,需要用到fw流,但是它已关闭
    }
}

补充:新的try...catch

JDK1.7之后,给我们提供了一种新的try...catch形式

语法格式:

try( 需要自动关闭的IO流的声明和初始化){
        其他的IO流操作代码
    }catch(异常类型 e){
        处理异常的代码
    }

try...catch新结构除了支持IO流的自动关闭,还支持其他的资源类的关闭,但是必须要求该资源类实现Closeable接口。

public class TestTryCatch {
    public static void copyFile2(String srcFilePathName, String destFilePathName) {
        try (
                FileInputStream fis = new FileInputStream(srcFilePathName);
                FileOutputStream fos =  new FileOutputStream(destFilePathName);
                ){
            //(3)一边读一边写
            int len;
            byte[] data = new byte[1024];//1KB
            //fis.read(data)把数据从fis读取到data中
            while((len = fis.read(data))!=-1){
                //把数据从data中输出到fos中
                fos.write(data, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();//打印异常
            //如果想告知调用者这里发生异常,然后需要对方处理
//            throw new RuntimeException(e);//把编译时异常IOException包装为运行时异常扔出去
        }
    }

    public static void copyFile(String srcFilePathName, String destFilePathName) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            //(1)先创建FileInputStream的对象,用于读取源文件的数据
            fis = new FileInputStream(srcFilePathName);
            //(2)再创建FileOutputStream的对象,用于输出数据到目标文件
            fos =  new FileOutputStream(destFilePathName);
            //(3)一边读一边写
            int len;
            byte[] data = new byte[1024];//1KB
            //fis.read(data)把数据从fis读取到data中
            while((len = fis.read(data))!=-1){
                //把数据从data中输出到fos中
                fos.write(data, 0, len);
    //            fos.write(data);//如果最后一次读取的字节数少于data.length,会导致上次读取的数据重复写到文件中
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            //(4)关闭
            try {
                if(fis!=null) {
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }finally{
                try {
                    if(fos!=null) {
                        fos.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}