zoukankan      html  css  js  c++  java
  • I/O流

    基本概念

    什么是 I/O?

    通过 I/O 可以完成硬盘文件的读和写。

    概念图:输入、输出是相对于内存而言的,以内存为参照物。

    image-20200715160836266

    1、I/O 流的分类方式:

    • 按照流的方向进行分类:

      以内存为参照物,往内存中去称为输入(Input)或者读(Read)。

    • 按照读取数据方式不同进行分类:

      有的流是按照字节的方式读取数据,一次读取 1 个字节(byte)= 8 bit。这种流是万能的,什么类型文件都可以读取(文本文件、音频文件、图片文件等等)。

    • 按照字符的方式读取数据,一次读取一个字符,这种流是为了方便读取普通文本文件(能用记事本编辑的文件,比如java文件、txt 文件等)而存在的,不能读取其他类型的文件,甚至 word 文件也无法读取( word 文件不是普通文本)。

    示例:a中国bc张三fe

    a等字符在 windows 占用一个字节(但是在Java中占用两个字节),等汉字在 windows 占用两个字节。

    按字节读:

    第一次:一个字节,读到a

    第二次:一个字节,读到字符的一半;

    ……

    按字符读:

    第一次:一个字符,读到a

    第二次:一个字符,读到

    ……

    2、I/O 四大流:(都是抽象类)

    java.io.InputStream:字节输入流

    java.io.OutputStream:字节输出流

    java.io.Reader:字符输入流

    java.io.Writer:字符输出流

    所有的都实现了java.io.Closeable接口,都有close()方法,都是可关闭的,流相当于内存和硬盘之间的管道用完后要及时关闭,否则会占用很多资源。

    所有的输出流都实现了java.io.Flushable接口,都是可刷新的,都有flush()方法表示将通道当中剩余未输出的数据强行输出完(清空管道),输出流在最终输出之后一定要记得刷新一下清空管道。(如果没有flush()可能会导致丢失数据)

    注意:在 java 中只要类名stream结尾的都是字节流;以 Reader/Writer 结尾的都是字符流。

    文件专属:

    1、java.io.FileInputStream(掌握)

    public static void main(String[] args){
            //创建文件字节流输入对象
            FileInputStream files = null;
            try{
                /*
                方式一:
                 files = new  FileInputStream("E:/java课堂笔记/file");
                * */
                //方式二:文件绝对路径是“E:java课堂笔记file”,"\"其中一个是转义字符表示“”
                files=new FileInputStream("E:\java课堂笔记\file"); //存储内容:abcdef
    
               /*
                   读数据
                int readeData=files.read(); //返回值:读取到的字节本身,读到文件末尾没有数据就返回-1
                System.out.println(readeData);//结果:97 (字符a的ASCII码)*/
               
               /*while(true){//循环读出
                    int readeData=files.read();
                    if(readeData == -1){
                        break;
                    }
                    System.out.println(readeData);
                }*/
                //改造循环
                int readeData;
                while((readeData=files.read()) != -1){
                    System.out.println(readeData);
                }
    
            } catch(FileNotFoundException e){
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //确保一定关闭流
                if(files != null){
                    //如果files为空则不用关闭流
                    try {
                        files.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    

    注意:如果是空格则返回 32 。

    缺点:一次读取一个字节,硬盘和内存的数据交互太频繁效率较低,开发时使用较少。

    改进:使用int read(byte[] b)一次最多读取b.length个字节,减少硬盘和内存的交互,提高程序的执行效率,往byte[]数组当中读。

    相对路径是从当前所在的位置作为起点。

    IDEA默认的当前路径是工程Project的根。

    image-20200715180837525

    例如:上图中的根就是ideaProjects,和train并列的文件路径为

    files = new FileInputStream("file");
    
    //在train文件夹中的文件
    files = new FileInputStream("train/file");
    

    读取数据:

    public static void main(String[] args){
            //创建文件字节流输入对象
            FileInputStream files = null;
            try{
                /*
                方式一:
                 files = new  FileInputStream("E:/java课堂笔记/file");
                * */
                //方式二:文件绝对路径是“E:java课堂笔记file”,"\"其中一个是转义字符表示“”
                files=new FileInputStream("E:\java课堂笔记\file"); //存储内容:abcdef
    	//准备一个4个长度的byte数组,一次最多读取4个字节
            byte[] bytes = new byte[4];
    		//返回值:读取到的字节数量
             int readCount = files.read(bytes);
                System.out.println(readCount);//第一次读到4个字节
                
                readCount = files.read(bytes);//第二次只能读到2个字节
          System.out.println(readCount);//2
                
                   readCount = files.read(bytes);//第三次一个字节都没,返回-1
          System.out.println(readCount);//-1
                
            } catch(FileNotFoundException e){
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //确保一定关闭流
                if(files != null){
                    //如果files为空则不用关闭流
                    try {
                        files.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    

    将读出的字节数组转换成字符串输出:

    	//准备一个4个长度的byte数组,一次最多读取4个字节
            byte[] bytes = new byte[4];
    		//返回值:读取到的字节数量
             int readCount = files.read(bytes);
                System.out.println(readCount);//第一次读到4个字节
              System.out.println(new String(bytes));//返回:abcd
    
                readCount = files.read(bytes);//第二次只能读到2个字节
               System.out.println(readCount);//2
                System.out.println(new String(bytes));//返回:efcd
    
                readCount = files.read(bytes);//第三次一个字节都没,返回-1
              System.out.println(readCount);//-1
                
    

    由以上代码看出第二次读出的数据是efcd,与我们的期望不符合,我们应该读取多少个字节就转换多少个。

    System.out.println(new String(bytes,0,readCount));
    //表示从数组bytes的第0个下标开始,读取长度为readCount的字节数组
    

    读取的最终版本:

    public static void main(String[] args){
            //创建文件字节流输入对象
            FileInputStream files = null;
            try{
                /*
                方式一:
                 files = new  FileInputStream("E:/java课堂笔记/file");
                * */
                //方式二:文件绝对路径是“E:java课堂笔记file”,"\"其中一个是转义字符表示“”
                files=new FileInputStream("E:\java课堂笔记\file"); //存储内容:abcdef
    	//准备一个4个长度的byte数组,一次最多读取4个字节
            byte[] bytes = new byte[4];
               /* while(true){
             		//返回值:读取到的字节数量
             int readCount = files.read(bytes);
                    if(readCount = -1){
                        break;
                    }
                  System.out.println(new String(bytes,0,readCount));  
                }*/
                //改进
                int readCount = 0;
                while((readCount = files.read(bytes)) != -1){
                    System.out.println(new String(bytes,0,readCount));
                }
                
            } catch(FileNotFoundException e){
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //确保一定关闭流
                if(files != null){
                    //如果files为空则不用关闭流
                    try {
                        files.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    

    FileInputStream类的常用方法:

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

    应用:获取文件的总字节数量,然后通过read()方法一次性读完,但是因为数组不能太大所以不适合太大的文件。

    public static void main(String[] args){
            //创建文件字节流输入对象
            FileInputStream files = null;
            try{
                //方式二:文件绝对路径是“E:java课堂笔记file”,"\"其中一个是转义字符表示“”
                files=new FileInputStream("E:\java课堂笔记\file"); //存储内容:abcdef
                System.out.println("总字节数量:"+ files.available());
    		   //准备一个4个长度的byte数组,一次最多读取4个字节
           		byte[] bytes = new byte[files.available()];
             	//知道了总字节数不需要循环了,一次性读取完
                int readCount = files.read(bytes);
                System.out.println(new String(bytes));
                
                
            } catch(FileNotFoundException e){
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //确保一定关闭流
                if(files != null){
                    //如果files为空则不用关闭流
                    try {
                        files.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    

    2、long skip(long n)跳过几个字节不读。

    public static void main(String[] args){
            //创建文件字节流输入对象
            FileInputStream files = null;
            try{
                //方式二:文件绝对路径是“E:java课堂笔记file”,"\"其中一个是转义字符表示“”
                files=new FileInputStream("E:\java课堂笔记\file"); //存储内容:abcdef
                System.out.println("总字节数量:"+ files.available()); 
    	//skip跳过几个字节不读取
                files.skip(3);//表示从 d 开始读
                System.out.println(files.read());//返回:100 (即d的ASCII值)
                
                
            } catch(FileNotFoundException e){
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //确保一定关闭流
                if(files != null){
                    //如果files为空则不用关闭流
                    try {
                        files.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    

    2、java.io.FileOutputStream(掌握):将数据从内存中写到硬盘中。

    1)第一种:每次都是先将文件清空,然后重新写入数据。

    public static void main(String[] args){
            //创建文件字节流输入对象
            FileOutputStream fos = null;
            try{
                //方式二:文件绝对路径是“E:java课堂笔记file”,"\"其中一个是转义字符表示“”
                fos = new FileOutputStream("E:\java课堂笔记\myfile"); //将数据写入“E:java课堂笔记myfile”,文件不存在时会自动创建
              //开始写
                byte[] bytes = {97,98,99,100,101};//写入abcde
                //将bytes数组全部写出
                fos.write(bytes);
                //将bytes数组的一部分写出
                fos.write(bytes,0,2);//再写出ab,即abcdeab
                
               //写完之后,最后一定要刷新
                fos.flush();
            } catch(FileNotFoundException e){
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //确保一定关闭流
                if(fos != null){
                    //如果files为空则不用关闭流
                    try {
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    

    2)FileOutputStream(String name,boolean append)写入的内容写在文件已有的数据后面

    public static void main(String[] args){
            //创建文件字节流输入对象
            FileOutputStream fos = null;
            try{
                //方式二:文件绝对路径是“E:java课堂笔记file”,"\"其中一个是转义字符表示“”
                //此处要处理异常
                fos = new FileOutputStream("E:\java课堂笔记\myfile",true); //将数据写入“E:java课堂笔记myfile”,文件不存在时会自动创建
              //开始写
                byte[] bytes = {97,98,99,100,101};//写入abcde
                //将bytes数组全部写出
                fos.write(bytes);
                //将bytes数组的一部分写出
                fos.write(bytes,0,2);//再写出ab,即abcdeab
                //写出字符串
                String s = "我是谁";
                //将字符串转换成byte数组
                byte[] b = s.getBytes();
                //写出
                fos.write(b);
                
               //写完之后,最后一定要刷新
                fos.flush();//此处要处理异常
            } catch(FileNotFoundException e){
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //确保一定关闭流
                if(fos != null){
                    //如果files为空则不用关闭流
                    try {
                        fos.close();//此处要处理异常
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    

    每次执行后都是在原有数据的基础上追加数据。

    3)文件的拷贝(FileInputStream、FileOutputStream)

    拷贝过程:文件一边输入内存,一边从内存中输出。(一边读一边写)使用以上的字节流拷贝文件的时候,文件类型随意,什么文件(文件夹不能,只是单个文件)都能拷贝。

    public static void main(String[] args){
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try{
            //创建一个输入流对象
            fis = new FileInputStream("D://javase");
            //创建一个输出流对象
            fos = new FileOutputStream("E://javase");
            
            //核心:一边读一边写
            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{
            //这里输入输出流的关闭的异常要分开处理;如果一起处理有一个出现异常,可能会影响到另一个流的关闭
            if(fos != null){
                try{
                    fos.close();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
            if(fis != null){
                try{
                    fis.close();
                }catch(IOException e){
                     e.printStackTrace();
                }
            }
        }
    }
    

    3、java.io.FileReader

    文件字符输入流(读,内存读取硬盘信息),只能读普通文本(能用记事本编辑的文件,比如text文件等)。

    public static void main(String[] args){
        FileReader reader = null;
        try{
            reader = new FileReader("E:\java课堂笔记\myfile");
            /*
              显示读出的信息
              //准备一个char数组
              char[] chars = new char[4];
              //往char数组中读
              reader.read(chars);//按字符方式读取,第一次a,第二次b,…(一个汉字也是一个字符)
              //遍历
              for(char c : chars){
              System.out.println(c);
              }
            */
            //一次读取4个字符
            char[] chars = new char[4];
            int readCount = 0;
            while((readCount = reader.read(chars)) != -1){
                System.out.print(new String(chars,0,readCount));
            }
        }catch(FileNotFoundException e){
            e.printStackTrace();
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            if(reader != null){
                try{
                    reader.close();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
        }
    }
    

    4、java.io.FileWriter

    文件字符输出流(写,内存写入硬盘)只能输出普通文本。

    public static void main(String[] args){
        FileWriter out = null;
        try{
            //创建文件字符输出流对象,没有该文件时会自动创建
            out = new FileWriter("file");
           /* //写入时紧接着文件内容的后面写入,不会清空文件内容重新写入
           out = new FileWriter("file",true);
           */
            //开始写
            char[] chars = {'躬','行'};
            //会将文件内容清空然后重新写入
            out.write(chars);
            //接着写入数组的一部分,从下标为0开始长度为1
            out.write(chars,0,1);
            //写入一个字符串
            out.write("我是一名程序员");
            
            //刷新
            out.flush();
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            if(out != null){
                try{
                    out.close();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
        }
    }
    

    复制普通文本文件的过程和复制字节流文件相同。

    缓冲流专属:

    1、java.io.BufferedReader

    带有缓冲区的字符输入流。使用这个流的时候不需要自定义 char 数组,或者说不需要自定义 byte 数组,自带缓冲。

    image-20200717170443223
    public static void main(String[] args) throws Exception{//此处将异常统一处理,不需要try-catch
        FileReader reader = new FileReader("copy.java");
        //当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流。
        //外部负责包装的这个流叫做:包装流(也叫处理流)
        //FileReader就是一个节点流,BufferedReader就是包装流(处理流)
        BufferedReader br = new BufferedReader(reader);
        /*
        一行一行的读:
        String firstLine = br.readLine();
        System.out.println(firstLine);
        
        String secondLine = br.readLine();
        System.out.println(secondLine);
        */
        //当readLine()返回值为null时读操作结束
        String s = null;
        while((s = br.readLine()) != null){//br.readLine()方法读取一个文本行,但不会换行,文本会输出在同一行
            System.out.println(s);//输出一行文本后换行
        }
        
        //关闭流,对于包装流来说,只需要关闭包装流就行了,因为包装流的close()方法中调用了节点流的close()方法
        br.close();
    }
    
    image-20200717171803625

    观察源码发现:关闭流时,对于包装流来说,只需要关闭包装流就行了,因为包装流的close()方法中调用了节点流的close()方法。

    转换流:(将字节流转换成字符流)

    1、java.io.InputStreamReader

    public static void main(String[] args) throws Exception{//此处将异常统一处理,不需要try-catch
    //字节流
        FileInputStream in = new FileInputStream("copy.java");
        //通过转换流转换(InputStreamReader将字节流转换成字符流)
        InputStreamReader reader = new InputStreamReader(in);//in是节点流,reader是包装流
        
        //这个构造方法只能传一个字符流,不能传字节流
        BufferedReader br = new BufferedReader(reader);//reader是节点流,br是包装流
        
        //合并写法
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("copy.java")));
        //输出
        String line = null;
        while((line = br.readLine()) != null){
            System.out.println(line);
        }
    }
    

    2、java.io.OutputStreamWriter

    带有缓冲区的字符输出流。

    public static void main(String[] args) throws Exception{//让写入内容追加在原内容之后
        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("file",true)));
        //开始写
        out.write("hello world");
        out.write("
    "); //换行符
        out.write("world");
        //刷新
        out.flush();
        //关闭最外层
        out.close();
    }
    

    数据流专属:

    1、java.io.DataOutputStream

    这个流可以将数据连同数据类型一并写入文件,这个文件不是普通文本文档(用记事本打不开。)

    public static viod main(String[] args) throws Exception{
        //创建数据专属的字节流
        DataOutputStream dos = new DataOutputStream(new FileOutputStream("data"));
        //写数据
        byte b = 100;
        long l = 400L;
        double d = 3.14;
        char c = 'a';
        //写,把数据及数据类型一并写入文件中
        dos.writeByte(b);
        dos.writeLong(l);
        dos.writeDouble(d);
        dos.writeChar(c);
        
        //刷新
        dos.flush();
        //关闭最外层
        dos.close();
    }
    
    image-20200718095345905

    OutputStream是抽象类不能实例化,可以实例化它的子类FileOutputStream

    使用DataOutputStream写入的数据无法用记事本打开(打开是乱码),我们必须使用DataInputStream还要按照写入时的顺序读出来。

    2、java.io.DataInputStream

    数据字节输入流,读DataOutputStream写的文件,读的时候需要提前知道写入的顺序,读的顺序和写的数据顺序一致才能正常读取出数据。

    public static void main(String[] args) throws Exception{
        DataInputStream dis = new DataInputStream(new FileInputStream("data"));
        //读取数据
        byte b = dis.readByte();
        long l = dis.readLong();
        double d = dis.readDouble();
        char c = dis.readChar();
        
        System.out.println(b);
        System.out.println(l);
        System.out.println(d);
        System.out.println(c);
        
        dis.close();
    }
    

    标准输出流:
    java.io.PrintStream(掌握)

    标准的字节输出流,默认输出到控制台

    public static void main(String[] args){
        PrintStream ps = System.out;
        ps.println("hello world");
        //合并写法
        System.out.println("hello world");
        //标准输出流不需要手动关闭
        
        //标准输出流不再指向控制台,指向“log”文件
        PrintStream printStream = new PrintStream(new FileOutputStream("log"));
        //修改输出方向为“log”文件
        System.setOut(printStream);
        //输出
        System.out.println("hello world");
    } 
    
    image-20200718102540739

    setOut()的参数为PrintStream类型的,而PrintStream的参数是OutputStream抽象类不能实例化,可以用其子类FileOutputStream代替。

    记录日志文件的方法:

    public class Logger{
        public static void log(String msg){
            try{
                //指向一个日志文件
                PrintStream out = new PrintStream(new FileOutputStream("log.txt",true));
                //改变输出方向
                System.setOut(printStream);
                //日期当前时间
                Date nowTime = new Date();
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm: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("调用了。。");
        }
    }
    

    结果:在“log.text”文件中打印了2020-07-18 ……:调用了。。

  • 相关阅读:
    视频智能分析系统EasyCVR视频流媒体安防监控云服务实现城市视频智能化应用
    Windows/Android/iOS平台H265编码视频播放器EasyPlayerRTSP中socket选项之keepalive设置介绍
    【解决方案】人脸识别/车牌识别RTSP/GB28181/SDK/Ehome协议视频平台EasyCVR搭建美丽乡村视频监控系统方案
    【操作说明】新版网络穿透+云端组网+远程运维+视频流拉转推平台EasyNTS上云网关管理平台如何安装?
    Windows/Android/iOS平台H265编码视频播放器EasyPlayerPro支持高码率视频播放和D3D画面旋转代码参考
    【操作说明】人脸识别/车牌识别系统视频智能分析平台EasyCVR如何配置开启HTTPS协议?
    程序员们 不要想一辈子靠技术混饭吃
    jsonObject的使用
    程序员们 不要想一辈子靠技术混饭吃
    从 iBatis 到 MyBatis
  • 原文地址:https://www.cnblogs.com/gyunf/p/13343217.html
Copyright © 2011-2022 走看看