I/O操作、File文件操作

目录

I/O操作

流模型

I/O流的分类

I/O流的操作步骤

文件拷贝

try-with-resource

自定义的资源类如何使用try-with-resource

I/O操作 示例:

对象的序列化和反序列化

序列化API -- ObjectOutputStream

反序列化API -- ObjectInputStream

序列化/反序列化 示例:

文件类File


I/O操作

在所有的编程语言当中,I/O操作都是绕不过去的。其中I叫做“输入”--Input,O叫做“输出”--Output。

流模型

在Java的I/O设计当中,把输入输出设计成了所谓“流模型”。流模型描述的是,所有的数据传递都是在“数据源”与“目的地”之间,建立一个管道,然后数据像流水一样通过这跟管道进行传递。
所以在操作Java的I/0操作时,我们都必须要考虑好3个要点:源是谁?目的地是谁?管道是什么管道?

I/O流的分类

从传递方向上分为:输入流和输出流
从管道粗细上分为:字节流和字符流

由上面的两种分类组合成了I/O流当中的4大父类:
输入字节流:InputStream
输出字节流:OutputStream
输入字符流:Reader
输出字符流:Writer

这4大父类是4个抽象类,他们定义了所有流操作的统一的方法外观,然后让各自的子类去实现自己的内部细节。我们真正要用是用子类。

Java在设计子类的名称的时候,循序了一个很任性化的设计方案,那就是子类名很明确的表示了这个类的使用场景。
比如:FileInputStream -- 数据源是File,目的地是程序,管道是字节
     FileReader -- 数据源是File,目的地是程序,管道是字符
     FileOutputStream -- 数据源是程序,目的地是文件,管道是字节
     FileWriter -- 数据源是程序,目的地是文件,管道是字符
     

I/O流的操作步骤

1、选择流类型

通过传递的数据类型选择

如果是文本数据,选择字符流,

如果是二进制数据,选择字节流。

通过传递的方向选择输入还是输出

最后通过另一个端点到底是谁,选择合适的子类流。

2、产生流对象 -- new出来

3、操作流对象 -- read 或 write

4、关闭流(重要)

提示:一旦涉及到输入输出操作,一定会有各种编译时异常需要我们提前处理。

文件拷贝

try-with-resource

在所有的I/O操作中,最讨厌的一件事情就是,我们需要再catch后面加上finally代码,然后固定的执行流的关闭操作。用了几个流就要关几个,每次调用它们的close方法的时候又有try-catch语句。

在JDK7当中,提出了新的语法"try-with-resource"。它的效果是当我们在try块中操作需要关闭的资源(不仅仅是针对I/O流操作,包括以后各位要常用的数据库链接等等)的时候,可以自己去调用close行为。

语法:

try( 产生资源对象--new动作 ){
    使用资源对象
}catch(异常){
    异常处理
}

与普通的try-catch-finally在使用的变化点就两个:

1、try后面多打上一对(),里面new资源对象;

2、不用写资源的关闭动作了。

当我们需要用到多个要求最终关闭的资源,那么只需要在try的()里面new出它们多个,中间用";"分隔。

try(new出第一个资源; 
    new出第二个资源;
    ......){
​
}catch(异常){
    异常处理
}

自定义的资源类如何使用try-with-resource

要想让我们自定义的资源类同样能够享有try-with-resource的待遇,那么我们必须让这个类实现一个java.lang.AutoCloseable的接口。 该接口只提供了一个方法:close方法,供实现类重写。同时,这个接口不仅仅只是提供统一的关闭行为外观,还起到了标识接口的作用。

public class MyRs implements AutoCloseable{
​
    @Override
    public void close(){
    
    }
}

通过实际的测试,我们发现,这个close行为在try块当中执行的时候不管是否发生异常,都一定会被执行,其效果与卸载finally当中是一样的。

try-with-resource的本质 其实它就是一个语法糖,通过编译后的class文件,我们反编译回来看代码,发现编译后其实没有try-with-resource的语法了。 这说明这种语法仅存在于源代码文件当中,编译后它还是普通的try-catch语句,然后在try的最后和catch的最后都有一句close的代码。

注意

1、如果在try的()内有多个资源,那么这些资源的关闭顺序应该是从后往前的;

2、如果我们使用了多个资源,且这多个资源有对接的效果。那么要注意,最好是给每个资源单独new出来,不要直接使用拼接new对象的方式。(参见下一个小结)如果采用拼接new的方式,那么只有第一根管道会被关闭,后面接的那根会被漏掉;所以最好给单独申明。

3、try-with-resource当中如果发生异常,那么是先执行close动作,然后再执行catch块当中的代码。而传统的try-catch-finally,如果发生异常,是先执行catch块当中的代码,然后再关闭close动作。

4、try-with-resource的语法当中,还是允许在最后添加finally块。只不过在这个finally块当中,我们不用再进行关闭资源的动作了,可以根据需要书写其他的“必须要执行的动作”。

I/O操作 示例:

public static void main(String[] args) {

        String poem = """
                床前明月光,
                疑是地上霜。
                """;
        System.out.println(poem);
        writeFile(poem);
        readFile();
        copyFile();
    }

    public static void writeFile(String str){
        FileWriter fw = null;
        try {
            fw = new FileWriter("poem.txt");
            fw.write(str);
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if(fw != null){
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void readFile() {
        try(FileReader fr = new FileReader("poem.txt")) {
            int in = 0;
            while ((in = fr.read()) != -1) {
                char c = (char) in;
                System.out.print(c);
            }
        }catch (IOException ioe){
            ioe.printStackTrace();
        }
    }

    public static void copyFile(){
        try(FileInputStream fis = new FileInputStream("old/最炫民族风.mp4");
            FileOutputStream fos = new FileOutputStream("data/最炫民族风.mp4");
            Scanner scan = new Scanner(System.in)){
            //每次读取一个字节数组,然后再把这个字节数组里面的数据写出去
            byte[] b = new byte[1024];
            int length = 0;
            while(  (length = fis.read(b)) != -1 ){
                fos.write(b,0,length);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

对象的序列化和反序列化

我们可以使用Properties来对数据进行持久化操作。但是这种方式操作数据的虽然持久,但是在一定场景中任然存在问题。

1、麻烦。因为在程序当中,我们往往操作的都是对象,但是在properties文件当中,数据是字符串。所以,我们不得不加上大量的代码完成这两种类型之间的转换;

2、不安全。properties文件是文本文件,意味着只要打开它,就能看懂它。

所以,“对象序列化和反序列”的技术。它指的是:

1、对象序列化 --- 把内存中的Java对象以二进制流的形式输出出去。

2、对象反序列化 --- 把输入进来的对象二进制流直接生成为内存中的一个对象。

注意

1、无论是序列化还是反序列化的概念中,都跟文件没有关系。在它们的概念里面,一个是把对象用对象二进制流输出,但没有说输出到哪里去;一个是把输入的对象二进制流转为一个对象,也没有说这个输入流是哪里来的。

2、对象的序列化 是 Java当中,各位同学学到的第二种产生对象的方式。

序列化API -- ObjectOutputStream

流的三种分类:

1、按方向:输入流 输出流;

2、按传递单位:字节流 字符流 ;

3、按功能: 节点流 操作流。

节点流:如FileInputStream,它控制了输入字节流(InputStream)的数据源是文件。 FileOutputStream,它控制了输出字节流(OutputStream)的目的地是文件。因此这种管道以及具备了I/O流操作所需要的全部三要素,因此可以直接使用。

操作流:ObjectOutputStream,它的作用是进行对象序列化,也就是说它的任务是把对象转成二进制流输出,但是没有指定输出到哪里去。那么,也就是说它只是完成了一个转换操作,但还没有确定传输的另一个节点,所以不能单独使用。所以,它被称之为“操作流”。

而操作流是不能够单独直接使用的,所以必须要至少接一个节点流。 因此,我们看到ObjectOutputStream没有提供公共无参构造,而是一个公共带参构造,要求至少还要传递一个节点流进去进行流的对接。

反序列化API -- ObjectInputStream

它的操作与对象序列化类似,也是两个流的对接动作。ObjectInputStream只是一个操作流,只负责把对象流转成对象这个操作,而没有控制对象流从哪里来的。所以要对接一个输入的节点流,控制哪个节点作为数据源读数据。

注意:

1、如果一个类要参与序列化,那么它应该实现Serializable的接口;并且,它所有的属性都要实现这个接口;JDK自带的常用类,基本上都已经实现了这个接口。

2、JavaBean的书写规范中,除了: 必须要有公共无参构造; 必须为私有属性提供符合命名规范的get/set方法; 应该实现Serializable接口。

3、如果有批量数据需要参与序列化,那么就把它们放到一个集合类当中去,然后序列化整个集合对象。JCF当中提供的集合类,自己本身也都实现了Serializable接口。

4、序列化以后,不要去修改类的代码,如果修改了,那么反序列化回来的时候,会认为两个的版本的类型不一致了,会报错。

序列化/反序列化 示例:

    public static void main(String[] args) {
        HashMap<Integer, Person> allPerson = new HashMap<>();
        allPerson.put(9527,new Person("小明",25));
        allPerson.put(9528,new Person("大张",28));
        testObjectOutputStream(allPerson);

//        Person p = testObjectInputStream();
//        System.out.println(p.getName() + " " + p.getAge());
    }

    //序列化 -- 把对象转成二进制流输出
    public static void testObjectOutputStream(HashMap<Integer,Person> p){
        try( FileOutputStream fos = new FileOutputStream("data/person.data");
              ObjectOutputStream oos = new ObjectOutputStream(fos)){
            /*
                p对象会先进过oos,转为二进制流;
                然后再通过对接fos,输出到文件当中去。
             */
            oos.writeObject(p);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //反序列化 -- 把输入的对象二进制流转成对象
    public static Person testObjectInputStream(){
        Person p = null;
        try( FileInputStream fis = new FileInputStream("data/person.data");
                ObjectInputStream ois = new ObjectInputStream(fis)){

                p = (Person)ois.readObject();

        }catch(IOException io){
            io.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        return p;
    }

文件类File

示例:

public class TestFile {

    public static void main(String[] args) {
        File f = new File("data");
        boolean flag = f.exists();//判断文件是否存在
        if(flag){
            flag = f.isDirectory();//判断是否是目录
            flag = f.isFile();//判断是否是文件
            if(flag){
                String str = f.getPath();//获取文件相对路径
                str = f.getAbsolutePath();//获取文件绝对路径
                str = f.getName();//获取文件名
                long l = f.length();//获取文件的大小--字节
                long mills = f.lastModified();//获取文件上次修改时间
                f.deleteOnExit();//文件删除 -- 先判断
                f.isHidden();//获取该文件是否是隐藏文件
                System.out.println(mills);
            }else{
                String[] subFileNames = f.list();//罗列该目录下的所有子文件(包括子目录)的名字
                for(String sub : subFileNames){
                    System.out.println(sub);
                }
                File[] subFiles = f.listFiles();//罗列该目录下所有子文件(包括子目录)的File对象
                for(File sub : subFiles){
                    System.out.println(sub.getName() + "  " + sub.length() + "  "
                            + (sub.isFile()?"文件":"目录"));
                }
            }
        }else{
            try {
                f.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值