IO流的用法及其介绍

IO流

概念:

IO(Input/Output)流是Java中用于处理输入和输出数据的机制。它允许程序与外部设备(如文件、网络连接、内存等)进行数据交换。IO流按照操作单位的不同可以分为字节流和字符流两种类型。

IO的分类

根据数据的流向分为:输入流输出流

  • 输入流 :把数据从其他设备上读取到内存中的流。
  • 输出流 :把数据从内存 中写出到其他设备上的流。

格局数据的类型分为:字节流字符流

  • 字节流 :以字节为单位,读写数据的流。
  • 字符流 :以字符为单位,读写数据的流。

顶级父类

输入流输出流
字节流字节输入流 InputStream字节输出流 OutputStream
字符流字符输入流 Reader字符输出流 Writer

InputStream(输入流)及其子类:

FileInputStream:用于从文件中读取数据的输入流
BufferedInputStream:带有缓冲区的输入流,可以提高读取性能。
ObjectInputStream:用于反序列化对象的输入流,可以将对象从字节流恢复为原来的对象。
InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流。

OutputStream(输出流)及其子类:

FileOutputStream:用于向文件中写入数据的输出流。
BufferedOutputStream:带有缓冲区的输出流,可以提高写入性能。
ObjectOutputStream:用于序列化对象的输出流,可以将对象转换为字节流进行持久化存储。
OutputStreamWriter(OutputStream in, String charsetName): 创建一个指定字符集的字符流。

Reader(输入字符流)及其子类:

FileReader:用于从文件中读取字符数据的输入字符流

BufferedReader:带有缓冲区的输入字符流,可以提高读取性能。

Writer(输出字符流)及其子类:

FileWriter:用于向文件中写入字符数据的输出字符流

BufferedWriter:带有缓冲区的输出字符流,可以提高写入性能。

字节流(基本流)

概念

​ 一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。

字节输出流【OutputStream】

java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。

  • public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
  • public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
  • public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。
  • public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
  • public abstract void write(int b) :将指定的字节输出流。

FileOutputStream类

OutputStream的子类,用于将数据写出到文件。

构造方法
  • public FileOutputStream(File file)
  • public FileOutputStream(String name)
写出字节数据

1.写出字节write(int b)

  1. 虽然参数为int类型四个字节,但是只会保留一个字节的信息写出。
  2. 流操作完毕后,必须释放系统资源,调用close方法

2.写出字节数组write(byte[] b)

//将字符串转换为字节数组
byte[] b="funnyboy".getBytes();
//写出字节数据
fos.write(b);
//关闭
fos.close();

3.写出指定长度字节数组write(byte[] b, int off, int len) ,每次写出从off索引开始,len个字。

数据追加续写
  • public FileOutputStream(File file, boolean append): 创建文件输出流以写入由指定的 File对象表示的文件。
  • public FileOutputStream(String name, boolean append): 创建文件输出流以指定的名称写入文件。

这两个构造方法,参数中都需要传入一个boolean类型的值,true 表示追加数据,false 表示清空原有数据

写出换行

Windows系统里,换行符号是\r\n,回车+换行

// 写出一个字节
fos.write(bt[i]);
// 写出一个换行, 换行符号转成数组写出
fos.write("\r\n".getBytes());

字节输入流【InputStream】

java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。

  • public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
  • public abstract int read(): 从输入流读取数据的下一个字节。
  • public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。

FileInputStream类

java.io.FileInputStream 类是文件输入流,从文件中读取字节

构造方法
  • FileInputStream(File file)
  • FileInputStream(String name)
读取字节数据

1.读取字节read方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回-1

//读取数据,返回一个字节,但是会自动提升为int类型
int b ;
// 循环读取
while ((b = fis.read())!=-1) {
	System.out.println((char)b);
}

2.使用字节数组读取read(byte[] b),每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回-1

// 1.创建流对象(复制粘贴图片)
// 1.1 指定数据源
FileInputStream fis = new FileInputStream("D:\\test.jpg");
// 1.2 指定目的地
FileOutputStream fos = new FileOutputStream("test_copy.jpg");
// 2.读写数据
// 2.1 定义数组
byte[] b = new byte[1024];
// 2.2 定义长度
int len;
// 2.3 循环读取
while ((len = fis.read(b))!=-1) {
    // 2.4 写出数据
    // 每次读取后,把数组的有效字节部分,输出
    fos.write(b, 0 , len);//len 每次读取的有效字节个数
    // 3.关闭资源
fos.close();
fis.close();

流的关闭原则:先开后关,后开先关。

字符流(基本流)

当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件

字符输出流【Writer】

java.io.Writer 抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。

  • void write(int c) 写入单个字符。
  • void write(char[] cbuf) 写入字符数组。
  • abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数。
  • void write(String str) 写入字符串。
  • void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
  • void flush() 刷新该流的缓冲。
  • void close() 关闭此流,但要先刷新它。

FileWriter类

构造方法
  • FileWriter(File file,boolean append)
  • FileWriter(String fileName,boolean append)
基本写出数据

写出字符write(int b) 方法,每次可以写出一个字符数据

// 写出数据
fw.write(97); // 写出第1个字符
fw.write('b'); // 写出第2个字符
fw.write('C'); // 写出第3个字符
fw.write(30000); // 写出第4个字符,中文编码表中30000对应一个汉字。
输出结果: abC田

写出字符数组write(char[] cbuf)write(char[] cbuf, int off, int len)

// 字符串转换为字符数组 char[] chars = "程序员".toCharArray();

写出字符串write(String str)write(String str, int off, int len)

字符流,只能操作文本文件,不能操作图片,视频等非文本文件。

字符输入流【Reader】

java.io.Reader抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。

  • public void close() :关闭此流并释放与此流相关联的任何系统资源。
  • public int read(): 从输入流读取一个字符。
  • public int read(char[] cbuf): 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。

FileReader类

java.io.FileReader 类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

  1. 字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码

    idea中UTF-8

  2. 字节缓冲区:一个字节数组,用来临时存储字节数据。

构造方法
  • FileReader(File file)
  • FileReader(String fileName)
读取字符数据

1.读取字符read()方法,每次可以读取一个字符的数据,提升为int类型,读取到文件末尾,返回-1,循环读取

2.使用字符数组读取read(char[] cbuf),每次读取b的长度个字符到数组中,返回读取到的有效字符个数,读取到末尾时,返回-1

// 定义变量,保存有效字符个数
int len ;
// 定义字符数组,作为装字符数据的容器
 char[] cbuf = new char[2];
// 循环读取
while ((len = fr.read(cbuf))!=-1) {
    System.out.println(new String(cbuf,0,len));
}

字符流原理解析

输入流在这里插入图片描述

1.创建字符输入流对象
底层:关联文件,并创建缓冲区(长度为8192的字节数组)
2.读取数据
底层:
1.判断缓冲区中是否有数据可以读取
2.**缓冲区没有数据:**就从文件中获取数据,装到缓冲区中,每次尽可能装满缓冲区如果文件中也没有数据了,返回-1
3.**缓冲区有数据:**就从缓冲区中读取。
空参的read方法:一次读取一个字节,遇到中文一次读多个字节,把字节解码并转成十进制返回
有参的read方法:把读取字节,解码,强转三步合并了,强转之后的字符放到数组中

输出流在这里插入图片描述因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush 方法了。fw.flush();
  • flush() :刷新缓冲区,流对象可以继续使用。
  • close ():先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。

缓冲流

1.缓冲流有几种?

  • 字节缓冲输入流:BufferedInputStream(InputStream in)
  • 字节缓冲输出流:BufferedOutputStream(OutputStream out)
  • 字符缓冲输入流:BufferedReader(Reader in)
  • 字符缓冲输出流:BufferedWriter(Writer out)

2.缓冲流为什么能提高性能

  • 缓冲流自带长度为8192的缓冲区

  • 可以显著提高字节流的读写性能

  • 对于字符流提升不明显,对于字符缓冲流而言关键点是两个特有的方法

3.字符缓冲流两个特有的方法是什么?

  • 字符缓冲输入流BufferedReader:readLine():读一行文字。
String line;
while((line = br.readLine())!=null){}
  • 字符缓冲输出流BufferedWriter:newLine():写一行行分隔符,由系统属性定义符号。

字符流自带缓冲区,为什么还要用字符缓冲流?

尽管字符流已经具备了缓冲的功能,但字符缓冲流(BufferedReader 和 BufferedWriter)仍然有其自身的优势和用途:

缓冲区大小可控:字符缓冲流提供了更大的缓冲区,可以指定缓冲区的大小。较大的缓冲区可以一次性读取或写入更多的字符数据,减少对底层I/O的频繁访问,提高读写效率。

提供了更方便的读写方法:字符缓冲流提供了一些便捷的方法,如 **readLine() 方法可以一次读取一行数据,**而不需要一个字符一个字符地读取。newLine() 方法可以写入一个平台特定的换行符,而不需要手动处理不同操作系统的换行符。

**支持预读取和回滚:**字符缓冲流具有 mark() 和 reset() 方法,可以在读取过程中进行标记(mark)并在需要时回滚(reset),方便进行预读取和回溯操作。

**支持写入自动刷新:**字符缓冲流提供了 flush() 方法,用于手动刷新缓冲区,并将缓冲区中的数据强制写入底层的输出流。此外,可以通过设置缓冲区的大小和自动刷新策略(如自动换行符)来控制写入时的刷新。

转换流

作用

1.指定字符集的读写:(JDK11 后淘汰了)
InputStreamReader isr = new InputStreamReader(new FileInputStream("文件"), "GBK");
//JDK11后可以直接在字符流中使用(write也是如此)
FileReader fr = new FileReader("文件", Charset.forName("GBK"));
2.字节流想要用字符流中的方法
/*利用字节流读取文件中的数据,每次读一整行,而且不能出现乱码
        1.字节流在读取中文的时候,是会出现乱码的,但是字符流可以搞定
        2.字节流里面是没有读一整行的方法的,只有字符缓冲流才能搞定*/
        /*FileInputStream fis = new FileInputStream("路径");
        InputStreamReader isr = new InputStreamReader(fis,"GBK");
        BufferedReader br =new BufferedReader(isr);*/
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("路径"), "GBK"));
        String str ;
        while ((str=br.readLine())!=null) {
            System.out.println(str);
        }
        br.close();

InputStreamReader和OutputStreamWriter类

InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流。

OutputStreamWriter(OutputStream in, String charsetName): 创建一个指定字符集的字符流。

//不写则默认UTF8编码
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("in.txt") , "GBK");
OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("out.txt") ,"GBK");

在这里插入图片描述

案例:转换文件编码

public class TransDemo {
   public static void main(String[] args) {      
    	// 1.定义文件路径
     	String srcFile = "file_gbk.txt";
        String destFile = "file_utf8.txt";
		// 2.创建流对象
    	// 2.1 转换输入流,指定GBK编码
        InputStreamReader isr = new InputStreamReader(new FileInputStream(srcFile) , "GBK");
    	// 2.2 转换输出流,默认utf8编码
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(destFile));
		// 3.读写数据
    	// 3.1 定义数组
        char[] cbuf = new char[1024];
    	// 3.2 定义长度
        int len;
    	// 3.3 循环读取
        while ((len = isr.read(cbuf))!=-1) {
            // 循环写出
          	osw.write(cbuf,0,len);
        }
    	// 4.释放资源
        osw.close();
        isr.close();
  	}
}

序列化

Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据对象的类型对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。

反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化对象的数据对象的类型对象中存储的数据信息,都可以用来在内存中创建对象。看图理解序列化:
在这里插入图片描述

ObjectOutputStream类

构造方法
  • public ObjectOutputStream(OutputStream out) : 创建一个指定OutputStream的ObjectOutputStream。
序列化操作
  1. 一个对象要想序列化,必须满足两个条件:
  • 该类必须实现java.io.Serializable 接口,Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
  • 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。

2.写出对象方法

  • public final void writeObject (Object obj) : 将指定的对象写出。

ObjectInputStream类

构造方法
  • public ObjectInputStream(InputStream in) : 创建一个指定InputStream的ObjectInputStream。
反序列化操作

如果能找到一个对象的class文件,我们可以进行反序列化操作,调用ObjectInputStream读取对象的方法:

  • public final Object readObject () : 读取一个对象。

**另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常。**发生这个异常的原因如下:

  • 该类的序列版本号与从流中读取的类描述符的版本号不匹配
  • 该类包含未知数据类型
  • 该类没有可访问的无参数构造方法

Serializable 接口给需要序列化的类,提供了一个序列版本号。serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。

// 加入序列版本号 private static final long serialVersionUID = 1L;

序列化流/反序列化流的细节汇总
  1. 使用序列化流将对象写到文件时,需要让Javabean类实现Serializable接口否则,会出现NotserializableException异常

  2. 序列化流写到文件中的数据是不能修改的,一旦修改就无法再次读回来了

  3. 序列化对象后,修改了Javabean类,再次反序列化,会不会有问题?

    会出问题,会抛出InvalidclassException异常

    解决方案:给Javabean类添加serialVersionUID(列号、版本号)

  4. 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?

    解决方案:给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程

  5. 标记接口的作用

    Serializable接口的存在并不是为了提供方法,而是为了告诉JVM和相关的库(如ObjectOutputStreamObjectInputStream)这个类的实例是可以被序列化的。实现这个接口的类表示它同意序列化机制可以访问其私有数据,并进行序列化处理。

    相关注意事项

    1. transient 关键字:有些字段不想被序列化,可以用transient关键字标记。

      private transient int transientField;
      
    2. serialVersionUID:为了确保序列化过程中的版本一致性,建议每个可序列化类都显式声明一个serialVersionUID

      private static final long serialVersionUID = 1L;
      

案例:

将多个自定义对象序列化到文件中,但是对象的个数不确定:

可以将多个对象放入list中,然后序列化list,打开ArrayList源码可以发现,符合序列化的要求:在这里插入图片描述

//序列化操作将Teacher对象放入文件中
Teacher th1 = new Teacher("funnyboy", 20);
Teacher th2 = new Teacher("funny", 19);
Teacher th3 = new Teacher("boy", 21);

ArrayList<Teacher> list = new ArrayList<>();
list.add(th1);
list.add(th2);
list.add(th3);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("zxk.txt"));
oos.writeObject(list);
oos.close();

//反序列化操作
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("zxk.txt"));
ArrayList<Teacher> list= (ArrayList<Teacher>) ois.readObject();
for (Teacher teacher : list) {
	System.out.println(teacher);
}
ois.close();
//创建JavaBean类,实现Serializable接口
public class Teacher implements Serializable {
    @Serial
    private static final long serialVersionUID = -5254570458408576908L;
    //创建成员变量,成员方法,构造方法,toSting方法
    ...
}    

打印流

定义

分类:打印流一般是指:PrintStream(字节打印), PrintWwriter(字符打印)两个类。

特点:(只能读,不能写)

打印流只操作文件目的地,不操作数据源。
特有的写出方法可以实现,数据原样写出。
特有的写出方法,可以实现自动刷新,自动换行
打印一次数据=写出+换行+刷新

字节打印流

1.构造方法

打印一次数据=写出+换行+刷新:关联字节输出流/文件/文件路径。
public PrintStream(String fileName,Charset charset):指定字符编码。
public Printstream( outputstream out,boolean autoFlush):自动刷新。
public Printstream(outputStream out, boolean autoFlush,String encoding):指定字符编码且自动刷新。
字节流底层没有缓冲区,开不开自动刷新都一样。

2.成员方法

public void write(int b):常规方法:规则跟之前一样,将指定的字节写出。
public void println(Xxx xx) : 特有方法:打印任意数据,自动刷新,自动换行。
public void print(xxx xx):特有方法:打印任意数据,不换行。
public void printf(String format,Object… args) : 特有方法:带有占位符的打印语句,不换行。

字符打印流

字符流底层有缓冲区,想要自动刷新需要开启。

1.构造方法

public Printwriter(write/File/String): 关联字节输出流/文件/文件路径。
public Printwriter(String fileName,Charset charset): 指定字符编码。
public Printwriter(Write w, boolean autoFlush):自动刷新。
public Printwriter(outputstream out,boolean autoFlush,Charset charset):指定字符编码且自动刷新。

有缓冲区,开不开自动刷新都一样。

2.成员方法

public void write(int b):常规方法:规则跟之前一样,将指定的字节写出。
public void println(Xxx xx) : 特有方法:打印任意数据,自动刷新,自动换行。
public void print(xxx xx):特有方法:打印任意数据,不换行。
public void printf(String format,Object… args) : 特有方法:带有占位符的打印语句,不换行。

字符打印流

字符流底层有缓冲区,想要自动刷新需要开启。

1.构造方法

public Printwriter(write/File/String): 关联字节输出流/文件/文件路径。
public Printwriter(String fileName,Charset charset): 指定字符编码。
public Printwriter(Write w, boolean autoFlush):自动刷新。
public Printwriter(outputstream out,boolean autoFlush,Charset charset):指定字符编码且自动刷新。

成员方法与字节打印流相似。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值