'); } '); } IO流 | Journey to paradise

IO流


IO流简述

IO流简述

     IO流即输入输出流,通过IO可以完成硬盘文件的读和写,输入输出是相对于内存而言的。


IO流的分类

有多种分类方式:
    一种方式是按照流的方向进行分类:
        以内存作为参照物,
        往内存中去,叫输入(Input)读(Read) 
        从内存中出来,叫输出(output)或者写(Write)

    另一种方式是按照读取薮据方式不同进行分类:
        有的流是按照字节的方式读取数据,一次读取1个字节byte(8bit),这种流是万能的,
        什么类型的文件都可以读取。包括:文本文件,图片,声音文件,视频等
        假设文件file.txt,采用字节流的话是这样读的:
            a中国bc张三fe
            第一次读:一个字节,正好读到'a'
            第二次读:一个字节,正好读到'中'字符的一半
            第三次读:一个字节,正好读到'中'字符的另外一半

        有的流是按照字符的方式读取数据的,一次读取一个字符,这种流是为了方便读取普通文本文件而存在的,
        这种流不能读取:图片、声音、视频等文件。只能读取纯文本文件,连word文件都无法读取。
            假设文件file.txt,采用字符流的话是这样读的:
            a中国bc张三fe
            第一次读:'a'字符('a'字符在windows系统中占用1个字节。)
            第二次读:'中'字符(中字符在windows系统中占用2个字节。)

Java中的IO流

     Java中IO流都已经写好了,我们需要掌握java中的各种流及其操作。java中IO流主要有四大家族(都是抽象类):java.io.InputStream(字节输入流)、java.io.OutputStream(字节输出流)、java.io.Reader(字符输入流)、java.io.Writer(字符输入流)。以Reader、Writer结尾的都是字符流,以Stream结尾的都是字节流。

     所有的流都实现了java.io.Closeable接口,都有close()方法,都是可关闭的。流是内存和硬盘之间的通道,用完一定要关,减少资源损耗。所有输出流都实现了java.io.Flushable接口,都有flush()方法,都是可刷新的。刷新表示将通道中剩余未输出的数据强行输出完(清空管道)。输出流在最终输出之后一定要flush(),避免数据丢失。

java.io包下需要掌握的流有16个:

文件专属:
    java.io.FileInputStream
    java.io.FileOutputStream
    java.io.FileReader
    java.io.FileWriter

转换流:(将字节流转换成字符流)
    java.io.InputStreamReader
    java.io.OutputStreamWriter

缓冲流专属:
    java.io.BufferedReader
    java.io.BufferedWriter
    java.io.BufferedInputStream
    java.io.BufferedOutputStream

数据流专属:
    java.io.DataInputStream
    java.io.DataOutputStream

标准输出流:
    java.io.PrintWriter
    java.io.PrintStream

对象专属流:
    java.io.ObjectInputStream
    java.io.ObjectOutputStream

***重点掌握 FileInputStream和FileOutputStream,其余查文档。

FileInputStream

要点

java.io.FileInputStream:文件输入流,万能的,任何文件类型都可以读,以字节方式完成输入操作(硬盘-->内存)。

int read():一次读一个字节

int read(byte[] b):一次最多读取b.length个字节,减少硬盘和内存的交互,提高程序效率。

int available():返回流当中剩余的没有读到的字节数量

long skip(long n):跳过n个字节不读

实例一

public class FileInputStreamTest01 {
    public static void main(String[] args) {
        //创建文件字节输入流对象
        FileInputStream fis=null;
        //文件绝对路径:E:\java\test(IDEA会自动将\编程\\转义
        try {
            // fis=new FileInputStream("E:\\java\\test.txt");
            //写成这样也可以
            fis=new FileInputStream("E:/java/test.txt");
            //开始读
            /*
            int readData=0;
            //read()返回值尾-1则读到文件末尾,一个字节一个字节地读
            //返回值是读到的字节
            //内存和硬盘交互过于频繁,大量时间都花费在数据传输上
            while((readData=fis.read())!=-1){
                System.out.println(readData);
            }
        */

            byte[] bytes=new byte[4];//准备一个4个字节长度的byte数组abcdef
            //这个方法返回值是读字节数量,不是文件数据本身。
            //没读到数据则返回-1
            /*
            int readCount=fis.read(bytes);
            System.out.println(readCount);//4
            System.out.println(new String(bytes));//abcd
            System.out.println(new String(bytes,0,readCount));//abcd

            readCount=fis.read(bytes);
            System.out.println(readCount);//2
            //将bytes数组中所有数据转成字符串
            System.out.println(new String(bytes));//efcd
            //读多少转多少
            System.out.println(new String(bytes,0,readCount));//ef
    */

            int readCount=0;
            while((readCount=fis.read(bytes)) != -1){
                System.out.println(new String(bytes,0,readCount));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        //确保流关闭
            if(fis!=null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

实例二

public class FileInputStreamTest02 {
    public static void main(String[] args) {
        FileInputStream fis=null;
        try {
            fis=new FileInputStream("E:/java/test.txt");

        /*
            int readByte = fis.read();
            System.out.println("剩下多少个字节没有读:"+fis.available());//5

            //这种方式不太适合太大的文件,应为byte[]数组不能太大
            byte[] bytes = new byte[fis.available()];

            //不需要循环,一次读完文件所有剩下数据
            int readCount=fis.read(bytes);
            System.out.println(new String(bytes));//bcdef
        */

            //skip跳过几个字节不读
            fis.skip(3);
            System.out.println(fis.read());//100

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

FileOutputStream

要点

实例

public class FileOutputStreamTest01 {
    public static void main(String[] args) {
        FileOutputStream fos=null;
        try {
            //文件不存在会新建
            //这种方式会将文件清空然后再写,谨慎使用!
            //fos=new FileOutputStream("tempfile.txt");

            //以追加的方式在文件末尾写入,不会清空原文件内容。
            fos = new FileOutputStream("Chapter23/src/tempfile",true);
            byte[] bytes ={97,98,99,100};
            //将bytes数组的全部写入
            fos.write(bytes);
            //将bytes数组的一部分写入
            fos.write(bytes,0,2);

            String s = "我是一个中国人,我骄傲!!";
            //将字符串转换成字符串数组
            byte[] bs = s.getBytes();
            fos.write(bs);

            //写完一定要刷新
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭文件
        if (fos!=null){
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        }

    }
}

文件复制

要点

使用FileInputeStream + FileOutputStream完成文件拷贝,拷贝过程是一边读一边写 使用以上的字节流拷贝文件的时候,文件类型随意。



拷贝文件

public class Copy01 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;

        try {
            //创建一个输入流对象
            fis=new FileInputStream("E:\\动漫剪辑素材\\场景\\studio_video_1626758642687.mp4");
            //创建一个输出流对象
            fos = new FileOutputStream("E:\\java\\dom.mp4");

            //边读边写
            byte[] bytes = new byte[1024*1024];//1MB
            int readCount=0;
            while((readCount=fis.read(bytes))!=-1){
                fos.write(bytes,0,readCount);
            }
            //刷新
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //分开try catch,一起try其中一个出异常可能影响另一个的关闭
            if(fos!=null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fis!=null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

FileReader和FileWriter

要点

FileReader:
    文件输入流,只能读取普通文本,一次读取一个字符,用法和FileInputStream相同,只不过存储数据用char[]数组,而不是byte[]数组。读取文本内容时,比较方便快捷。
FileWriter:
    文件输出流,只能输出普通文本,一次输出一个字符,用法和FileOutputStream相同,只不过存储数据用char[]数组,而不是byte[]数组。写入文本内容时,比较方便快捷。
能用记事本打开编辑的都是普通文本文件不仅限于.txt文件。

缓冲流

要点

BufferedReader:
    带有缓冲区的输入流,使用这个流时不需要自定义char数组或byte数组,自带缓冲区。
BufferedWriter:
    带有缓冲的字符输出流。
OutputStreamWriter和OutputStreamReader:转换流

实例一

public class BufferedReaderTest01 {
    public static void main(String[] args) throws IOException {
        //当一个流的构造方法中需要一个流的时候,这个北传进来的流叫节点流
        //外部负责包装的流叫包装流/处理流
        //FileReader是结点流,BufferReader是包装流/处理流
        FileReader reader=new FileReader("Copy01.java");
        BufferedReader br = new BufferedReader(reader);

        String s=null;
        while((s =br.readLine())!=null){
            System.out.println(s);//默认读一个文本行但不带换行符
            System.out.println("\n");
        }

        //关闭流
        //对于包装流来说,只需要关闭最外层流,里面的流会自动关闭。(可以看源码)
        br.close();

        /*
        //字节流
        FileInputStream in = new FileInputStream("Copy01.java");
        //通过转换流转换
        InputStreamReader reader1 = new InputStreamReader(in);
        //这个构造方法只能传一个字符流,不能传字节流
        BufferedReader br1 =  new BufferedReader(reader);
*/
        BufferedReader br1=new BufferedReader(new InputStreamReader(new FileInputStream("Copy1.java")));

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

实例二

public class BufferWriterTest {
    public static void main(String[] args) throws IOException {
        //带有缓冲区的字符输出流
        //BufferedWriter out = new BufferedWriter(new FileWriter("copy"));
        //以追加方式写
        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("copy",true)) );
        //开始写
        out.write("hello world");
        out.write("\n");
        out.write("hello kitty");
        //刷新
        out.flush();
        //关闭最外层流
        out.close();
    }
}

数据流

java.io.DataOutputStream:数据专属流。
DateOutputStream写的文件,只能使用DataInputStream去读,并且读的时候需要知道写入的顺序,按写入的顺序读,才可以正常取出数据,可用于加密(用的少)。

标准输出流

要点

java.io.PrintStream:标准的字节输出流,默认输出到控制台。标准输出流不需要手动关闭。

实例

public class Logger {
    /*
    记录日志
    * */
    public  static void log(String msg){
        try {
            //指向一个日志文件
            PrintStream out=new PrintStream(new FileOutputStream("log.txt",true));
            //改变输出方向
            System.setOut(out);
            //日期当前时间
            Date nowTIme = new Date();
            SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:ss SSS");
            String strTime = sdf.format(nowTIme);

            System.out.println(strTime+":"+msg);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

    }
}

public class LogTest {
    public static void main(String[] args) {
        Logger.log(msg:"调用了System类的gc()方法,建议启动垃圾回收")
        Logger.log(msg:"用户尝试登录,验证失败");
    }
}

File类简述

要点

File类的常用方法:
方法很多,查文档就好了。listFiles()方法获取当前目录下所有的子文件。

实例

public class FileTest01 {
public static void main(String[] args) throws Exception {
//创建一个File对象
File f1 = new File( pathname: “D:\file”);
//判断是否存在
System.out.println(f1.exists());

    //如果:\file不存在,则以文件的形式创建出来
    /*if(!f1.exists()) {
        //以文件形式新建
        f1. createNewFile();
    }*/

    //如果D:\file不存在 ,则以目录的形式创建出来
    /*if(!f1. exists()) {
        //以目录的形式新建。
        f1. mkdir();
    }*/

    //创建多重目录
    File f2 = new File( pathname: "D:/a/b/c/d/e/f");
    /*if(!f2.exists()) {
        //多重目录的形式新建。
        f2. mkdirs();
    }*/

    File f3 = new File( pathname: "D:\\course\\学习方法.txt");
    //获取文件的父路径
    String parentPath = f3.getParent();
    System.out.println(parentPath); //D: \course

    File parentFile = f3.getParentFile();
    System.out.prihtln("获取绝对路径:"+ parentFile.getAbsolutePath());

    File f4 = new File( pathname: "copy");
    System.out.println("绝对路径:"+ f4.getAbsolutePath());

    File f5 = new File("pathname: "D:\\course\\asdf.txt");
    System.out.println(f5.getName());//asdf

    //判断是否是一个目录
    System.out.println(f5.isDirectory()); // false
    //判断是否是一个文件
    System.out.println(f5.isFile()); // true

    //获取文件最后一次修改时间
    long haoMiao = f5.lastModified(); //这个毫秒是从1970年到现在的总毫秒数。
    //将总毫秒数转换成日期
    Date time = new Date(haoMiao); 
    SimpleDateFormat sdf = new SimpleDateFormat( pattern: "yyyy-MM-dd HH:mm:ss SSs");
    String strTime = sdf.format(time);
    System.out.println(strTime);

    //获取文件大小
    System.out.println(f5.length());

    // File[] listFiles()
    //获取当前目录下所有的子文件。
    File f= new File( pathname: "D:\\course");
    File[] files = f.listFiles();
    // foreach
    for(File file : files){
    //System.out.println(file.getAbsolutePath());
    System.out.println(file.getName());
    }
}
}

对象流

序列化和反序列化


要点

1、参与序列化和反序列化的对象,必须实现Serializable接口。
2、注意:通过源代码发现,Serializable接口只是一个标志接口:
public interface Serializable {
}
这个接口当中什么代码都没有。
那么它起到一个什么作用呢?
    起到标识的作用,标志的作用,Serializable这个标志接口是给java虚拟机参考的,
    java虚拟机看到这个接口之后,会为该类自动生成一个序列化版本号。

序列化版本号有什么用?
    Java语言区分类机制:
        ①首先通过类名进行比对,如果类名不同则不是同一个类
        ②如果类名相同,则通过序列化版本号区分,如果序列化版本号不同则不是同一个类,否则为同一个类。
    不同人开发编写了类名相同的不同类(这两个了类都实现了Serializable接口),这时候就可以通过序列化版本号来区分这两个类。

自动生成序列化版本号的缺陷?
    一旦代码确定之后不能进行后续的修改,因为只要修改,必然会重新编译生成全新的序列化版本号,
    这个时候java虚拟机会认为这是一个全新的类。
凡是一个类实现了Serializable接口,建议将该类的序列号版本号手动写死( private static final long  serialVersionUID = ),不建议自动生成。Idea可以为类生成固定的序列化版本号。

序列化版本号不一致报错:
InvalidClassException: Student; 
local class incompatible: stream classdesc serialVersionUID = 412425857988772568, 
local class serialVersionUID = 3891390038314571630

3、序列化多个对象:将对象放在集合当中,序列化集合。
4、transient关键字:游离的,不参与序列化。在对象属性前加transient关键字表示该属性不参与序列化操作,反序列化时该属性值为null。

序列化的实现
public class ObjectOutputStreamTest {
    public static void main(String[] args) throws IOException {
        //创建java对象
        Student s = new Student(1111,"zhangsan");
        //序列化对象
        ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream("students"));
        oos.writeObject(s);
        oos.flush();
        oos.close();

        List<Student> studentList = new ArrayList<>();
        studentList.add(new Student(001,"wangwu"));
        studentList.add((new Student(002,"lisi")));
        studentList.add(s);
        //序列化studentList(List已经实现Serializable接口),批量序列化student对象
        ObjectOutputStream oos1 = new ObjectOutputStream(new FileOutputStream("students2"));
        oos1.writeObject(studentList);
        oos1.flush();
        oos1.close();
    }
}

反序列化
public class ObjectInputStreamTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream("students"));
        //开始反序列化,读
        Object obj=ois.readObject();
        //反序列化回来是一个学生对象
        System.out.println(obj);//Student{no=1111, name='zhangsan'}
        ois.close();
        //反序列化集合
        ObjectInputStream ois1=new ObjectInputStream(new FileInputStream("students2"));
        //Object obj1=ois.readObject();
    // System.out.println(obj1 instanceof  List);//true
        List<Student> studentList = (List<Student>) ois1.readObject();
        for (Student student : studentList){
            System.out.println(student);
        }
        ois1.close();
    }
}

IO + Properties

要点

经常改变的数据可以单独写到一个文件中,使用程序动态读取。
将来只需呀修改这个文件的内容,不需要改动java代码,不需要重新编译,服务器也不需要重启,
就可以拿到动态的信息。以上机制的文件称为配置文件。
并且当配置文件的内容格式为:
    key=value;
时,这种配置文件叫属性配置文件。属性配置文件以.properties结尾,但这不是必须的。
属性配置文件中#代表注释,key重复则value会自动覆盖,等号两端不要有空格。
Properties对象专门存放属性配置文件数据。

实例

public class IOPropertiesTest {
    public static void main(String[] args) throws IOException {
        /*
        Properties是一个Map集合,key和value都是String类型。
        将userinfo文件中的数据加载到Properties对象中。
        * */
        //新建一个输入流
        FileReader reader = new FileReader("chapter23/userinfo");
        //新建一个map集合
        Properties pro=new Properties();
        //调用Properties对象的load方法将文件中数据加载到map集合中
        pro.load(reader);//文件中的数据顺着管道以键值对的方式加载到map集合
        String username=pro.getProperty("username");
        System.out.println(username);

        //有更简单的方法:直接用Bundle属性类,这里不说了
    }
}

文章作者: 涂爽
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 涂爽 !
评论
 上一篇
反射机制 反射机制
反射机制和注解简介 反射机制简介        通过java语言中的反射机制可以操控(读/写)字节码文件。反射机制相关类在java.lang.
2021-11-15
下一篇 
java中集合(Map部分) java中集合(Map部分)
java中集合(Map部分) 集合的继承结构 要点 集合在java中是一个容器,一个对象。集合不能直接存储基本数据类型,也不能直接存储java对象, 集合当中存储的都是java对象的内存地
2021-10-24
  目录