Java基础学习17之IO流、字节流、字符流

554 阅读10分钟

Java基础学习17之IO流、字节流、字符流

「这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战」。

关于作者

  • 作者介绍

🍓 博客主页:作者主页
🍓 简介:JAVA领域优质创作者🥇、一名在校大三学生🎓、在校期间参加各种省赛、国赛,斩获一系列荣誉🏆。
🍓 关注我:关注我学习资料、文档下载统统都有,每日定时更新文章,励志做一名JAVA资深程序猿👨‍💻。

IO— 字节流与字符流

image-20210818173800455

字符流和字节流最本质的区别就是只有一个字节流是原生的操作,二字符流是经过处理后的操作。经过磁盘数据保存所保存的支持的数据类型只有:字节,所有磁盘中的数据必须先读到内存后才可以操作,内存里面会帮助我们把字节变为字符。字符更加适合处理中文。

字节操作流:OutputStream,InputStream;

字符操作流:Writer,Reader。

但是不管是字节流还是字符流的操作,本身都表示资源操作,而执行所有的资源都会按照如下几个步骤进行,下面以文件操作为例(对文件进行读,写操作):

  1. 要根据文件创建File对象
  2. 根据字节流或字符流的子类实例化我们的父类对象
  3. 进行数据的读取、写入操作
  4. 关闭流(clone())

对于IO操作属于资源处理,所有的对于资源的处理进行处理完成后必须进行关闭,否则资源就再也无法执行。

字节输出流:OutputStream

Java.io.OutputStream主要的功能是进行字节数据的输出的,而这个类的定义如下:

public abstract class OutputStream extends Object implements Closeable, Flushable

发现OutputStream类定义的时候实现了两个接口:Closeable,Flushable,这两个接口定义如下:

Closeable:Flushable:
public interface Closeable
extends AutoCloseable{
public void close() throws IOException;
}
public interface Closeable{
public void flush() throws IOException;
}

当取得了OutputStream类实例化对象之后,下面肯定要进行输出操作,在OutputStream类之中定义了三个方法:

输出单个字节数组数据:public abstract void write(int b) throws IOException

输出一组字节数组数据:public void write(byte[] b) throws IOException

输出部分字节数组数据:public void write(byte[] b,int off,int len) throws IOException

提示:对于Closeable继承的AutoCloseable接口

AuotCloseable实在JDK1.7的时候又增加了一个新的接口,但是这个接口的定义和Closeable定义是完全一样的,我个人认为:有可能在一些其他的类上出现了自动的关闭功能,Closeable是手工关闭,AutoCloseable属于自动关闭。

但是对于Closeable和Flushable这两个接口实话而言不需要关注,因为从最早的习惯对于flush()和close()连个方法都是直接在OutputStream类之中定义的,所以很少去关心这些父接口问题。

对于OutputStream类而言发现其本身定义的是一个抽象类(abstract class),按照抽象类的使用原则来讲,如果要想为父类实例化,那么就需要使用子类,就需要定义抽象的子类,而现在如果要执行的是文件操作,则可以使用FileOutputStream子类完成。如果按照面向对象的开发原则,子类要为抽象类进行对象的实例化,而后调用的方法以父类中定义的方法为主,而具体的实现找实例化这个父类的子类完成,也就是说在整个的操作之中,用户最关心的只有子类的构造方法:

实例化FileOutputStream(新建数据):public FileOutputStream([File file) throws FileNotFoundException

实例化FileOutputStream(追加数据):public FileOutputStream(File file,boolean append) throws FileNotFoundException

image-20210818181217500

实现文件内容的输出

package com.day14.demo;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;

public class OutputDemo {
	public static void main(String[] args) throws Exception {
		//1.定义文件路径
		File file = new File("f:" + File.separator + "test" + File.separator + "hello.txt");
		if(!file.getParentFile().exists()){//父路径不存在
			file.getParentFile().mkdirs();//创建父目录
		}
		//2.要输出的数据
		String str = "Hello,zsr!!!!";
		//3.实例化对象
		FileOutputStream stream = new FileOutputStream(file);
		//4.将内容写进stream
		stream.write(str.getBytes());//输出数据,要将输出的数据变为字节数组输出
		//5.关闭流
		stream.close();
	}
}

这里默认执行时进行文档内容的覆写,如果不希望进行文档内容的覆写可以直接将FileOutputStream改为

FileOutputStream stream = new FileOutputStream(file,true);

如果对写入的内容需要换行操作必须使用\r\n进行换行操作。

字节输入流:InputStream

如果现在要从指定的数据源之中读取数据,使用InputStream,而这个类的定义如下:

public abstract class InputStreamextends Objectimplements Closeable

发现InputStream只实现了Closeable接口

在InputStream之中定义了三个读取数据的方法:

读取单个字节:public abstract int read() throws IOException

说明:每次执行read()方法都会读取一个数据源的指定数据,如果现在发现已经读取到了结尾则返回-1.

读取多个字节:public int read(byte[] b) throws IOException

说明:如果现在要读取的数据小于开辟的字节数组,这个时候read()方法的返回值int返回的是数据个数;如果现在开辟的字节数组小于读取的长度,则这个时候返回就是长度;如果数据已经读完了,则这个时候的int返回的是-1.

读取指定多个字节:public int read(byte[] b,int off,int len) throws IOException

说明:每次只读取传递数组的部分内容,如果读取满了,返回就是长度;如果没有读取满,返回的就是读取的个数;如果读取到最后没有数据了,就返回-1

image-20210818192150209

既然InputStream为抽象类,那么这个抽象类要使用就必须有子类,现在是通过文件读取内容,肯定使用FileInputStream子类进行操作,与OutputStream类的使用一样,对于FileInputStream也只关心构造方法:

FileInputStream类构造方法:public FileInputStream(File file) throws FileNotFoundException

文件信息的读取

package com.day14.demo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;


public class InputDemo {
	public static void main(String[] args) throws Exception {
		File file = new File("f:" + File.separator + "test" + File.separator + "hello.txt");
		if(!file.exists()){
			System.out.println("指定文件不存在!!");
		}else{
			FileInputStream is = new FileInputStream(file);
			byte[] result = new byte[1024];
			int length = is.read(result);
			System.out.println("读取的内容为:" + new String(result,0,length));
		}
	}
}

字符输出流:Writer

Writer类也是一个专门用来数据输出的操作类,对于中文数据来说是比较友好的,这个类定义:

public abstract class Writer
extends Object
implements Appendable, Closeable, Flushable

在Writer类之中定义的writer()方法都是以字符数据为主,但是在这些方法之中,只关心一个:

输出一个字符串:public void write(String str) throws IOException

如果要操作文件肯定使用FileWriter子类。

package com.day14.demo;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class WriterDemo {
	public static void main(String[] args) throws IOException {
		//1.定义文件路径
		File file = new File("f:" + File.separator + "test" + File.separator + "hello.txt");
		if(!file.getParentFile().exists()){//父路径不存在
			file.getParentFile().mkdirs();//创建父目录
		}
		//2.要输出的数据
		String str = "我正在学java这门课程!!!!\r\n";
		//如果想要进行内容不覆盖的直接使用true就可以了
		//FileWriter out = new FileWriter(file,true);
		FileWriter out = new FileWriter(file);
		out.write(str);
		out.close();
	}
}

字符输入流:Reader

Reader是进行字符数据读取的一个操作类,其定义:

public abstract class Reader
extends Object
implements Readable, Closeable

在Writer类之中存在了直接输出一个字符串数据的方法,可是在Reader类之中并没有定义这样的方法,只是定义了三个按照字符串读取的方法?为什么会这样?

因为在使用OutputStream输出数据的时候,其程序可以输出的大小一定是程序可以承受的数据的大小,那么如果说使用InputStream读取的时候,可能被读取的数据非常大,那么如果一次性全读进来,就会出现问题,所以只能一个一个的进行读取。

Reader依然是抽象类,那么如果从文件读取,依然使用FileReader类

package com.day14.demo;

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

public class ReadDemo {
	public static void main(String[] args) throws IOException {
		File file = new File("f:" + File.separator + "test" + File.separator + "hello.txt");
		if(!file.exists()){
			System.out.println("指定文件不存在!!");
		}else{
			FileReader reader = new FileReader(file);
			char[] result = new char[1024];
			int length = reader.read(result);
			System.out.println("读取的内容为:" + new String(result,0,length));
		}
	}
}

字符比字节的好处就是在于字符串数据的支持上,而这个好处还只是在Writer()类中体现,所以与字节流相比,字符流的操作并不是对等的关系。

字节流与字符流区别

通过我们一系统的分析,可以发现字节流和字符流的代码操作区别不大,如果从我们实际的使用,我们字节流是优先考虑,只有再我们使用中文的时候才考虑使用字符流,因为所有的字符都需要通过内存缓冲来进行处理。

image-20210819114608877

既然读数据需要缓存的处理,那么写数据也同样需要。如果使用字符流没有进行刷新,那么我们的内容可能再缓存之中,所以必须进行强制刷新才能得到完整的数据内容。

package com.day14.demo;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class WriterDemo {
	public static void main(String[] args) throws IOException {
		//1.定义文件路径
		File file = new File("f:" + File.separator + "test" + File.separator + "hello.txt");
		if(!file.getParentFile().exists()){//父路径不存在
			file.getParentFile().mkdirs();//创建父目录
		}
		//2.要输出的数据
		String str = "我正在学java这门课程!!!!\r\n";
		//如果想要进行内容不覆盖的直接使用true就可以了
		//FileWriter out = new FileWriter(file,true);
		FileWriter out = new FileWriter(file);
		out.write(str);
		out.flush();
	}
}

在以后的IO处理的时候,如果处理的是图片、音乐、文字都可以使用字节流,只有再处理中文的时候才会使用字符流。

转换流

现在对于IO操作就存在了字节流和字符流两种操作,那么对于这两种操作流之间也是可以进行转换的,而转换的操作类有两个:

将字节输出流变为字符输出流(OutputStream->Writer)——OutputStreamWriter;

将字节输入流变为字符输入流(InputStream->Reader)——InputStreaReader。

OutputStreamWriterInputStreamReader
public class OutputStreamWriter
extends Writer
public class InputStreamReader
extends Reader
public OutputStreamWriter(OutputStream out)public InputStreamReader(InputStream in)

image-20210819115616861

通过以上的继承结构和构造方法可以清楚发现,既然OutputStreamWriter是Writer的子类,那么必然OutputStreamWriter可以通过Writer类执行对象的向上转型进行接收,而同时这个OutputStreamWriter类的构造方法可以接收OutputStream,这样就可以完成转型。

将字节输出流变为字符输出流

package com.day14.demo;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;

public class OutWriterDemo {
	public static void main(String[] args) throws Exception {
		//1.定义文件路径
		File file = new File("f:" + File.separator + "test" + File.separator + "hello.txt");
		if(!file.getParentFile().exists()){//父路径不存在
			file.getParentFile().mkdirs();//创建父目录
		}
		//2.要输出的数据
		String str = "Hello,world!!!!";
		//3.实例化对象
		OutputStream stream = new FileOutputStream(file,true);
		//4.将内容写进stream
		Writer out = new OutputStreamWriter(stream);
		out.write(str);
		//5.关闭流
		out.close();
	}
}

对于文件操作可以使用FileInputStream,FileOutputStream,FileReader,FileWriter四个类,那么下面分别观察这四个类的继承结构。

观察FileInputStream,FileOutoutStream类的继承结构

FileInputStreamFileOutoutStream
java.lang.Object
java.io.InputStream
java.io.FilterInputStream
java.lang.Object
java.io.OutputStream
java.io.FileOutputStream

观察FileReader,FileWriter类的继承结构

FileReaderFileWrite
java.lang.Object
java.io.Reader
java.io.InputStreamReader
java.io.FileReader
java.lang.Object
java.io.Writer
java.io.OutputStreamWriter
java.io.FileWriter

image-20210819120636862

通过以上的继承关系也可以发现,实际上所有的字符数据都是需要进行转换的,依靠转换流完成,以后真正保存或者是传输的数据是不可能有字符的,全部都是字节,而字节只是在电脑之中处理后的结果。