Java基础——IO流

一、引言

IO流是Java编程中常用的一种输入输出方式,它提供了一种机制,用于在程序和外部资源(如文件、网络连接等)之间进行数据的传输。在实际的软件开发中,IO流被广泛应用于读取和写入文件、网络通信、数据库操作等场景。理解和掌握IO流的概念和用法,对于开发高效、可靠的Java程序至关重要。

二、作用

将数据在虚拟机内存和本地磁盘之间进行传输

I:input 输入

O:output 输出

三、流

相当于管道,作用为进行数据传输

流的分类

  1. 从传输方向上看

    • 输入流:本地磁盘的数据向JVM传输
    • 输出流:JVM数据向本地磁盘传输
  2. 从传输单位上看

    • 字节流:以字节为单位进行数据传输。可以传输任意类型的数据,如文本、图片、视频、音频等
    • 字符流:以字符为单位进行数据传输。只能传输文本类型的数据,如.txt、.java、.html等
  3. 从传输功能上看

    • 节点流:具有传输功能和意义的流
    • 过滤流:没有传输功能,用来给节点流增强传输能力或增加附加功能

四、字节流

  • 输入流:InputStream 抽象父类
  • 输出流:OutputStream 抽象父类

1.输入流

  • 节点流:FileInputStream

(1)创建

FileInputStream fis=new FileInputStream("本地的文件路径");
  • 知识点补充:

    • 绝对路径:以电脑磁盘为基点的完整路径

      FileInputStream fis = new FileInputStream("D:\\test\\a.txt");
      FileInputStream fis = new FileInputStream("D:/test/a.txt");
      
    • 相对路径:以当前项目路径为基点的路径,前提是文件必须在项目下

      FileInputStream fis = new FileInputStream("file\\a.txt");
      FileInputStream fis = new FileInputStream("file/a.txt");
      
    • 路径书写必须截至至文件

    • 文件必须存在,否则抛出异常

(2)常用方法

  1. void close():关闭流链接,释放相关资源。(每个流中都有)
  2. int read(): 读取一个字节返回,读取到达末尾,返回-1
  3. int read(byte[] b): 尝试读取数组长度的数据至数组中, 返回实际读取个数.读取到达末尾,返回-1
package com.by.test;

import java.io.FileInputStream;

public class TestFIS {
    public static void main(String[] args)throws Exception {
        // FileInputStream fis = new FileInputStream("D:\\test\\a.txt");
        //  FileInputStream fis = new FileInputStream("D:/test/a.txt");

        // FileInputStream fis = new FileInputStream("C:\\Users\\Administrator\\IdeaProjects\\Chp16\\file\\a.txt");
         FileInputStream fis = new FileInputStream("file\\a.txt");
         //FileInputStream fis = new FileInputStream("file/a.txt");
        //读取一个字节
        //利用循环读取文件所有内容
        while (true) {
            //先接收本次读取内容
            int n = fis.read();
            //判断读取是否到达末尾
            if (n == -1) {
                break;
            }
            //未到达末尾,输出查看
            System.out.println((char) n);
        }

        //利用read(byte[])+循环读取文件所有内容
        while (true) {
            byte[] bs = new byte[3];
            //接收本次读取结果
            int n = fis.read(bs);
            //判断读取是否到达末尾
            if (n == -1) {
                break;
            }
            //遍历数组查看本次读取结果
            for (int i = 0; i < bs.length; i++) {
                System.out.println(bs[i]);
            }
        }



        //关流
        fis.close();

        System.out.println("执行成功!");
    }
}

2.输出流

  • 节点流:FileOutputStream

(1)创建

FileOutputStream fos=FileOutputStream("本地的文件路径",true|false);
  • 如果文件不存在,会自动创建
    • 无法创建文件夹
  • true表示数据追加,false表示数据覆盖
    • 默认是false

(2)常用方法

  1. void flush(): 刷新缓冲区,所有输出流中都具备该方法
  2. void write(int ): 向目标文件写入一个字节
  3. void write(byte[] ): 向目标文件写入一个数组的数据
package com.by.test;

import java.io.FileOutputStream;

public class TestFOS {
    public static void main(String[] args)throws Exception {
        FileOutputStream fos=new FileOutputStream("file/b.txt");
        //写入一个字节
        fos.write(65);
        fos.write(66);
        fos.write(67);
        //写入一个数组
        String str = "abcdefg123456";
        byte[] bs = str.getBytes();
        fos.write(bs);

        //关流
        fos.close();

        System.out.println("执行成功!");
    }
}

3.标准异常处理

  • JDK7.0之后,提供了自动关流的语法结构,简化了finally的工作内容
try(
	//需要自动关流的创建语句
){
    
}catch(){
    
}
  • 原理: JDK7.0之后,所有的流都默认实现了AutoCloseable接口,该接口中提供了自动关流所需的close方法
package com.by.test;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class TestFOS2 {
    public static void main(String[] args) {
        try (
                FileOutputStream fos = new FileOutputStream("file/b.txt");
                ){

            //写入一个字节
            fos.write(65);
            fos.write(66);
            fos.write(67);
            //写入一个数组
            String str = "abcdefg123456";
            byte[] bs = str.getBytes();
            fos.write(bs);
        } catch (FileNotFoundException e) {
            System.out.println("文件路径不正确");
        } catch (IOException e) {
            System.out.println("读写失败");
        } catch (Exception e) {
            System.out.println("未知异常!");
            e.printStackTrace();
        }

        System.out.println("执行成功!");
    }
}

4.文件复制

  • 原理: 先将文件A中的内容读取到JVM中,再从JVM中将读取内容写入到文件B, 借助JVM来实现A与B之间的数据复制
    • 先读后写
package com.by.test;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class TestFileCopy {
    public static void main(String[] args) {
        copy1();
        copy2();
    }
    /**
     * 一次复制一个字节
     */
    public static void copy1(){
        try (
                //创建输出节点流-复制到的文件路径
                FileOutputStream fos=new FileOutputStream("d:/test/2.pdf");
                //创建输入节点流-被复制的文件路径
                FileInputStream fis=new FileInputStream("d:/test/1.pdf")
                ) {
            //先循环读取文件所有内容
            while (true) {
                int n = fis.read();
                if (n == -1) {
                    break;
                }
                //将本次读取的字节写入到目标文件
                fos.write(n);
            }

            System.out.println("复制成功!");

        } catch (FileNotFoundException e) {
            System.out.println("文件路径不正确");
        } catch (IOException e) {
            System.out.println("读写失败!");
        } catch (Exception e) {
            System.out.println("未知异常!");
            e.printStackTrace();
        }
    }
    /**
     * 一次复制一个字节数组
     */
    public static void copy2(){
        try (
                //创建输出节点流-复制到的文件路径
                FileOutputStream fos=new FileOutputStream("d:/test/3.pdf");
                //创建输入节点流-被复制的文件路径
                FileInputStream fis=new FileInputStream("d:/test/1.pdf")
        ) {
            //先循环读取文件所有内容
            while (true) {
                //创建数组
                byte[] bs = new byte[1024];
                //读取一个数组的数据
                int n = fis.read(bs);
                if (n == -1) {
                    break;
                }
                //将数组中的数据写入到目标文件
                fos.write(bs);
            }

            System.out.println("复制成功!");

        } catch (FileNotFoundException e) {
            System.out.println("文件路径不正确");
        } catch (IOException e) {
            System.out.println("读写失败!");
        } catch (Exception e) {
            System.out.println("未知异常!");
            e.printStackTrace();
        }
    }
}

5.缓冲过滤流

  • BufferedInputStream: 输入缓冲过滤流
  • BufferedOutputStream: 输出缓冲过滤流

(1)创建

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

(2)作用

拥有一个内置的数据缓冲区, 文件数据将对接至数据缓冲区中,在缓冲区刷新或关闭时再将内部内容一并的给到目标文件

 /**
     * 一次复制一个字节+缓冲过滤流
     */
    public static void copy3(){
        try (
                //创建输出节点流-复制到的文件路径
                FileOutputStream fos=new FileOutputStream("d:/test/4.pdf");
                //创建输入节点流-被复制的文件路径
                FileInputStream fis=new FileInputStream("d:/test/1.pdf");
                //添加缓冲过滤流
                BufferedOutputStream bos=new BufferedOutputStream(fos);
                BufferedInputStream bis=new BufferedInputStream(fis)
        ) {
            //先循环读取文件所有内容
            while (true) {
                int n = bis.read();
                if (n == -1) {
                    break;
                }
                //将本次读取的字节写入到目标文件
                bos.write(n);
            }

            System.out.println("复制成功!");

        } catch (FileNotFoundException e) {
            System.out.println("文件路径不正确");
        } catch (IOException e) {
            System.out.println("读写失败!");
        } catch (Exception e) {
            System.out.println("未知异常!");
            e.printStackTrace();
        }
    }

(3)使用

  1. 如果先写后读,需要在写入完成后刷新缓冲区才能保证读取的正常进行

    • 调用flush():强刷缓冲区 (推荐)
    • 调用close(): 关流之前也会刷新缓冲区
  2. 关流时外层过滤流关闭内层节点流会一并关闭

package com.by.test;

import java.io.*;

public class TestBuffered {
    public static void main(String[] args) {
        try(
               //输出
               BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("file/a.txt"));
               //输入
               BufferedInputStream bis=new BufferedInputStream(new FileInputStream("file/a.txt"))
                ){
            //先写
            bos.write("abcd".getBytes());
            //刷新缓冲区
            bos.flush();   
            //bos.close();
            //再读
            while (true) {
                int n = bis.read();
                if (n == -1) {
                    break;
                }
                System.out.println((char) n);
            }

        }catch (FileNotFoundException e) {
            System.out.println("文件路径不正确");
        } catch (IOException e) {
            System.out.println("读写失败!");
        } catch (Exception e) {
            System.out.println("未知异常!");
            e.printStackTrace();
        }
    }
}

6.对象过滤流

  • ObjectInputStream 对象输入过滤流
  • ObjectOutputStream 对象输出过滤流
  • 附加功能1: 读写基本类型
  • 附加功能2: 读写引用类型

(1)读写基本类型

读取: ois.readXxx()
写入: oos.writeXxx();: Xxx对应的为基本类型名,首字母大写
  • 由于对象过滤流底层嵌套了缓冲区,所以先写后读操作时,仍然需要在写入完成后刷新缓冲区
  • 为了保证数据的安全性,所以在写入时数据会根据魔数机制对其加密,在读取时在进行解密
package com.by.test;

import java.io.*;

public class TestObject_Double {
    public static void main(String[] args) {
        try (
                //输出流
                ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("file/c.txt"));
                //输入流
                ObjectInputStream ois=new ObjectInputStream(new FileInputStream("file/c.txt"))

                ) {
            //先写
            oos.writeDouble(10.5);
            //强刷缓冲区
            oos.flush();
            //读取
            System.out.println(ois.readDouble());


        } catch (FileNotFoundException e) {
            System.out.println("文件路径不正确");
        } catch (IOException e) {
            System.out.println("读写失败!");
            e.printStackTrace();
        } catch (Exception e) {
            System.out.println("未知异常!");
            e.printStackTrace();
        }
    }
}

(2)读写引用类型

读取: Object ois.readObject()  读取到达末尾,抛出EOFException异常
写入: oos.writeObject(对象)  可以自动刷新缓冲区,所以先写后读时无需进行flush
读写String
package com.by.test;

import java.io.*;

public class TestObject_String {
    public static void main(String[] args) {
        try (
                //输出流
                ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("file/c.txt"));
                //输入流
                ObjectInputStream ois=new ObjectInputStream(new FileInputStream("file/c.txt"))

                ) {
            //先写
            oos.writeObject("床前明月光");
            oos.writeObject("疑是地上霜");
            oos.writeObject("举头望明月");
            oos.writeObject("低头思故乡");
            //后读
            while (true) {
                try {
                    String s =(String) ois.readObject();
                    System.out.println(s);
                } catch (EOFException e) {
                    //读取到达末尾,跳出循环
                    break;
                }
            }


        } catch (FileNotFoundException e) {
            System.out.println("文件路径不正确");
        } catch (IOException e) {
            System.out.println("读写失败!");
            e.printStackTrace();
        } catch (Exception e) {
            System.out.println("未知异常!");
            e.printStackTrace();
        }
    }
}
读写自定义类型
  • 自定义类必须实现Serializable接口,表示允许被序列化,否则IO流没有读写权限

    序列化:拆分对象信息的过程

    反序列化:通过信息组装对象的过程

  • 将属性通过transient修饰符修饰则可以防止其参与序列化

  • 如果对象中有自定义类型的属性,则必须使该属性类型也实现序列化接口或者通过transient修饰符对其修饰

package com.by.entity;

import java.io.Serializable;

public class Student implements Serializable {
    private String name;
    private int age;
    //防止被序列化
    private transient double score;
   // private Teacher tea;
//省略getter、setter、构造、toString
}

package com.by.test;

import com.by.entity.Student;

import java.io.*;

public class TestObject_Student {
    public static void main(String[] args) {
        try (
                //输出流
                ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("file/c.txt"));
                //输入流
                ObjectInputStream ois=new ObjectInputStream(new FileInputStream("file/c.txt"))

                ) {
            //先写
            oos.writeObject(new Student("zhangsan", 20, 88.5));
            //后读
            System.out.println((Student)ois.readObject());



        } catch (FileNotFoundException e) {
            System.out.println("文件路径不正确");
        } catch (IOException e) {
            System.out.println("读写失败!");
            e.printStackTrace();
        } catch (Exception e) {
            System.out.println("未知异常!");
            e.printStackTrace();
        }
    }
}

怎么理解输出语句?

System是类库中的工具类,out为该工具类中的静态属性,类型为标准输出流类型,print和println系列方法是该流中提供的写入方法,作用为向控制台写入一个内容

五、字符流

传输char和String类型的数据

1.输入流

  • 抽象父类:Reader
  • 节点流:FileReader

(1)常用方法

  1. int read():读取一个字符,读取到达末尾,返回-1
package com.by.test2;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class TestReader {
    public static void main(String[] args) {
        try(
                //字符输入节点流
                FileReader fr=new FileReader("file/d.txt")
                ){
            while (true) {
                int n = fr.read();
                if (n == -1) {
                    break;
                }
                System.out.println((char) n);
            }

        }catch (FileNotFoundException e) {
            System.out.println("文件路径不正确");
        } catch (IOException e) {
            System.out.println("读写失败!");
            e.printStackTrace();
        } catch (Exception e) {
            System.out.println("未知异常!");
            e.printStackTrace();
        }
    }
}

2.输出流

  • 抽象父类:Writer
  • 节点流:FileWriter

(1)常用方法

  1. write(int ):向目标文件写入一个字符
  2. write(String ):向目标文件写入一个字符串
package com.by.test2;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class TestWriter {
    public static void main(String[] args) {
        try(
                //字符输出节点流
                FileWriter fw=new FileWriter("file/e.txt")
                ){
            fw.write(65);
            fw.write(66);
            fw.write(67);
            //写入字符串
            fw.write("一二三四五");
            fw.write("上山打老虎");

            System.out.println("写入成功!");

        }catch (FileNotFoundException e) {
            System.out.println("文件路径不正确");
        } catch (IOException e) {
            System.out.println("读写失败!");
            e.printStackTrace();
        } catch (Exception e) {
            System.out.println("未知异常!");
            e.printStackTrace();
        }
    }
}

3.输入缓冲过滤流

  • BufferedReader

常用方法

  1. String readLine(): 一次读取一行数据,读取到达末尾返回null
package com.by.test2;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class TestBufferedReader {
    public static void main(String[] args) {
        try(
                //字符输入节点流
                FileReader fr=new FileReader("file/d.txt");
                //添加缓冲过滤流
                BufferedReader br=new BufferedReader(fr)
                ){
            while (true) {
                //接收本次读取内容
                String s = br.readLine();
                //判断读取是否到达末尾
                if (s == null) {
                    break;
                }
                System.out.println(s);
            }

        }catch ...
    }
}

4.输出缓冲过滤流

  • PrintWriter
    • BufferedWriter中的方法没有PrintWriter更多更实用

常用方法

  1. print(值):向目标文件写入一个数据,默认不换行
  2. println(值): 向目标文件写入一个数据,默认换行
  3. println(): 向目标文件写入一个空行
package com.by.test2;

import com.by.entity.Student;

import java.io.*;

public class TestPrintWriter {
    public static void main(String[] args) {
        try(
                //字符输出节点流
                FileWriter fw=new FileWriter("file/e.txt");
                //添加缓冲过滤流
                PrintWriter pw=new PrintWriter(fw)
                ){
            pw.print(5.5);
            pw.print(true);
            pw.println();
            pw.println("一二三四五");
           pw.println("上山打老虎");

            Student s = new Student("zhangsan", 20, 99.5);
            pw.println(s);

            System.out.println("写入成功!");

        }catch...
    }
}

pw对象中的print|println方法写对象与对象过滤流写对象的区别?

pw只是在读写对象引用的toString方法的结果,并未读写对象的完整信息,所以无法对对象进行序列号及反序列化

对象过滤流是在读写对象的完整信息,所以可以对对象进行序列号及反序列化

pw中的print|println方法与输出语句中的有何不同?

pw是将数据写入到目标文件长久保存

标准输出流是将数据写入到控制台临时查看

5.字符编码集

  • 编码: 原内容–>加密–>数字
  • 解码: 数字–>解密–>原内容

常见编码集

  • GBK: 简体中文
  • Big5: 繁体中文
  • ASC||: 美国
  • ISO-8859-1: 西欧
  • Unicode:
    • UTF-16: java默认编码集,所有内容统一占用2个字节
    • UTF-8: 行业标准, 所占字节由内容大小决定,通常为1-3个字节

不同编码集拥有独立的编解码方式,之间互不相通

6.桥转换流

  • 输入: InputStreamReader
  • 输出: OutputStreamWriter

(1)创建

  • 基于字节节点流对象. 将字节流中的内容转换为字符流,并在转换过程中设置数据传输的编码集
InputStreamReader isr=new  InputStreamReader(fis对象,"编码集");
OutputStreamWriter osw=new OutputStreamWriter(fos对象,"编码集");

(2)步骤

  1. 创建字节节点流,声明被操作的文件路径
  2. 创建桥转换流,声明操作文件时所用的编码集
  3. 添加缓冲过滤流,提高数据操作的便捷性

(3)使用

对同一文件的输入输出操作必须保证编解码集一致

package com.by.test2;

import java.io.*;

public class Test_ISR_OSW {
    public static void main(String[] args) {
        try(
                //创建输出流
                //创建字节输出节点流
                FileOutputStream fos=new FileOutputStream("file/m.txt");
                //创建桥转换流
                OutputStreamWriter osw=new OutputStreamWriter(fos,"GBK");
                //添加缓冲过滤流
                PrintWriter pw=new PrintWriter(osw);

                //创建输入流
                BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream("file/m.txt"),"GBK"))

                ){
            //先写
            pw.println("一二三四五");
            pw.println("上山打老虎");
            pw.println("老虎没打着");
            pw.println("打着小松鼠");
            //刷新缓冲区
            pw.flush();
            //后读
            while (true) {
                String s = br.readLine();
                if (s == null) {
                    break;
                }
                System.out.println(s);
            }
        }catch...
    }
}

六、结语

掌握IO流的使用技巧,能够帮助我们灵活处理各种输入输出需求,实现数据的传输和存储。在实际的项目开发中,合理地运用IO流,能够提高程序的性能和可维护性。同时,我们也要注意IO流的资源释放和异常处理,避免资源泄露和程序崩溃。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值