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 ……:调用了。。

原文地址:https://www.cnblogs.com/gyunf/p/13343217.html