从零开始学Java之I/O流中的字符流到底有哪些?

作者 :孙玉昌,昵称【一一哥 】,另外【壹壹哥】也是我哦

千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者

前言

在上一篇文章中,壹哥 给大家讲解了字节流与字节缓冲流的用法。尽管字节流的功能已经十分强大,几乎可以直接或间接地处理任何类型的输入/输出操作,但它却不能直接操作16位的Unicode字符,这就需要使用字符流。所以在今天的内容中,壹哥会继续给大家讲解IO流中的字符流,希望各位能够继续耐心学习哦。

------------------------------前戏已做完,精彩即开始----------------------------

全文大约【4400】 字,不说废话,只讲可以让你学到技术、明白原理的纯干货!本文带有丰富的案例及配图视频,让你更好地理解和运用文中的技术概念,并可以给你带来具有足够启迪的思考......

配套开源项目资料

Github: github.com/SunLtd/Lear...

Gitee: gitee.com/sunyiyi/Lea...

Java中的字符流,可以分为字符输入流(Reader)和字符输出流(Writer),输入流用于读取数据,输出流用于写入数据,接下来就让我们来逐个了解吧。

一. Reader字符输入流

1. 简介

Reader是I/O库中用于读取字符流的抽象类,它提供了一组方法用于读取字符流中的字符,并支持不同的字符编码。

实际上,除了特殊的CharArrayReader和StringReader,普通的Reader本质上就是一个带有编码转换器的InputStream,它可以把byte转换为char。也就是说,普通的Reader就是基于InputStream构造出来的,比如在FileReader的源码内部就有一个FileInputStream对象。而我们也完全可以把一个InputStream转换为Reader,比如:

java 复制代码
// InputStream对象
InputStream is = new FileInputStream("a.txt");
// InputStream转为Reader
Reader reader = new InputStreamReader(is, "UTF-8");

在这里,InputStreamReader就是一种用于将字节流转换为字符流的转换流,使用转换流可以在一定程度上避免乱码,还可以指定输入输出所使用的字符集。其中,InputStreamReader用于将字节输入流转换为字符输入流,OutputStreamWriter用于将字节输出流转换为字符输出流

Reader和InputStream之间有关系,当然也有很多区别,比如:

  • InputStream是一个字节流,即以byte为单位读取,读取的字节范围是(-1,0~255),数据会读到字节数组中;
  • 而Reader是一个字符流,要以char为单位读取,读取的字符范围是(-1,0~65535),数据会读到字符数组中。

2. 常用子类

因为Reader是抽象类,所以我们不能直接对其进行实例化,如果我们想使用字符输入流,需要使用Reader的某个具体子类来实例化对象。Reader的常用子类有如下几个:

  • InputStreamReader类:将字节输入流转换为字符输入流,可以指定字符编码;
  • BufferedReader类:为其他字符输入流提供读缓冲区;
  • CharArrayReader类:将字符数组转换为字符输入流,从中读取字符;
  • StringReader类:将字符串转换为字符输入流,从中读取字符;
  • FileReader类:用于读取文本文件,并支持不同的字符编码;
  • PipedReader类:连接到一个PipedWriter。

3. 常用方法

Reader字符流中的常用方法,其实与InputStream中的常用方法基本一致,比如:

方法名及返回值类型 说明
int read() 从输入流中读取一个字符,并把它转换为 0~65535 之间的整数。如果返回 -1, 则表示已经到了输入流的末尾。该方法不常用。
int read(char[] cbuf) 从输入流中读取若干个字符,并把它们保存到cbuf参数指定的字符数组中。最终返回读取的字符数,如果返回 -1,则表示已经到了输入流的末尾。
int read(char[] cbuf,int off,int len) 从输入流中读取若干个字符,并把它们保存到cbuf参数指定的字符数组中。off表示字符数组中开始保存数据的起始下标,len表示读取的字符数。最后返回实际读取的字符数,如果返回 -1,则表示已经到了输入流的末尾。

4. 实现步骤

如果我们想使用Reader读取字符流,可以遵循以下基本步骤:

  1. 创建一个Reader对象,比如InputStreamReader、FileReader、StringReader等;
  2. 调用Reader的read()方法来读取字符流,该方法会返回一个整数,表示读取的字符数。如果已到达流的末尾,则read()方法返回-1;
  3. 处理读取的字符,可以将它们存储在数组或字符串中;
  4. 循环调用read()方法,直到读取完整个字符流。

5. 代码案例

为了读取方便,Java给我们提供了用来读取字符文件的便捷类------FileReader,所以这里壹哥利用FileReader读取了一个文本文件。

java 复制代码
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class Demo08 {
    public static void main(String[] args) throws FileNotFoundException, IOException {
        // try(resource)的写法
        //1. 创建Reader对象
        try (FileReader reader = new FileReader("F:/a.txt")) {
            //设置编码为UTF-8
            //Reader reader = new FileReader("F:/a.txt", StandardCharsets.UTF_8);
            //2. 读取文件
            char[] cbuf = new char[1024];
            int len;
            while((len = reader.read(cbuf)) != -1) {
                //3.处理字符
                String str = new String(cbuf, 0, len);
                System.out.println(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } //4.自动资源释放
    }
}

在上面的案例中,read()方法可以一次读取一个字符数组,并返回实际读入的字符个数,最大不会超过char[]数组的长度,如果读到流的末尾就返回-1。所以利用这个方法,我们可以先设置一个缓冲区,然后每次尽可能地填充缓冲区。

大家可以发现,其实Reader字符流的使用,与InputStream字节流的使用基本类似。

另外我们在构造FileReader对象时,默认的字符编码及字节缓冲区大小都是由系统设定好的,如果我们想自己指定这些值,可以在FilelnputStream上套接一个InputStreamReader。在上面的代码中,如果我们读取的是纯ASCII编码的文本文件,输出的内容不会产生乱码。但如果文件中包含中文,就会出现乱码,这就是因为FileReader采用的是操作系统默认的编码,在Windows中默认的是GBK,而打开UTF-8编码的文本文件可能就会出现乱码。所以为了避免读取中文时产生乱码,我们可以在创建FileReader时指定编码:

java 复制代码
Reader reader = new FileReader("a.txt", StandardCharsets.UTF_8);

二. Writer字符输出流

1. 简介

Writer是I/O库中用于写入字符流的抽象类,它提供了一组方法用于将字符写入到字符流中,并支持不同的字符编码。除了CharArrayWriter和StringWriter之外,普通的Writer都是基于OutputStream构造的。所以本质上Writer是一个带有编码转换器的OutputStream,可以把char转换为byte并输出,即接收char,然后在内部会自动转换成一个或多个byte,并写入到OutputStream中。比如我们可以利用OutputStreamWriter,将任意的OutputStream转换为Writer,所以OutputStreamWriter是一种用于将字节输出流转换为字符输出流的转换流

java 复制代码
//创建FileOutputStream对象
FileOutputStream fos=new FileOutputStream("readme.txt");
//将FileOutputStream转为Writer
Writer writer = new OutputStreamWriter(fos, "UTF-8")

同样的,Writer和OutputStream也具有一些区别:

  • OutputStream是一个字节流,即以byte为单位读取,读取的字节范围是0~255;
  • Writer是一个字符流,要以char为单位读取,读取的字符范围是0~65535。

2. 常用子类

Writer是所有字符输出流的父类,常用的Writer子类有如下这些:

  • OutputStreamReader类:将字节输出流转换为字符输出流,可以指定字符编码;
  • BufferedWriter类:为其他字符输出流提供写缓冲区;
  • CharArrayWriter类:向内存缓冲区的字符数组写数据;
  • StringWriter类:向内存缓冲区的字符串(StringBuffer)写入数据;
  • FileReader类:用于写入文本文件,并支持不同的字符编码;
  • PipedWriter类:连接到一个PipedReader对象。

3. 常用方法

同样的,我们也来看看Writer类中的常用方法:

方法名及返回值类型 说明
void write(int c) 向输出流中写入一个字符。
void write(char[] cbuf) 把cbuf参数指定的字符数组中的所有字符,写到输出流中。
void write(char[] cbuf,int off,int len) 把cbuf参数指定的字符数组中的若干字符写到输出流中。off 表示字符数组的起始下标,len表示元素的个数。
void write(String str) 向输出流中写入一个字符串。
void write(String str, int off,int len) 向输出流中写入一个字符串的部分字符。off表示字符串中的起始偏移量,len表示字符个数。
append(char c) 将参数c指定的字符添加到输出流中。
append(charSequence esq) 将参数esq指定的字符序列添加到输出流中。
append(charSequence esq,int start,int end) 将参数esq指定的字符序列的子序列添加到输出流中。start表示子序列中的第一个字符索引,end表示子序列中最后一个字符后面的字符索引。即子序列的内容包含start索引处的字符,但不包括end索引处的字符。

大家要注意:Writer类中所有的方法在出错时都会引发IOException异常,如果我们关闭一个流后再对其进行任何操作,都会产生错误。

4. 代码案例

我们设计一个FileWriter的使用案例,代码如下:

java 复制代码
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;

public class Demo09 {
	public static void main(String[] args) throws FileNotFoundException, IOException {
		// try(resource)的写法
		// 1. 创建Writer对象
		try (FileWriter writer = new FileWriter("F:/b.txt")) {
			// 利用Scanner进行内容的输入
			Scanner input = new Scanner(System.in);
			// 2. 写入文件
			for (int i = 0; i < 5; i++) {
				System.out.println("请输入第" + (i + 1) + "行内容:");
				// 读取输入的名称
				String content = input.next(); 
				// 循环写入到文件中
				writer.write(content + "\r\n"); 
                //writer.append("追加新内容...");
			}
			System.out.println("录入完毕");
			input.close();
		} catch (IOException e) {
			e.printStackTrace();
		} // 3.自动资源释放
	}
}

FileWriter是向文件中写入字符流的Writer,它的使用和FileReader类似。这里的FileWriter对象直接关联了一个文件,然后我们可以调用write()或append()方法进行内容的新增和追加了。我们在创建FileWriter对象时,默认的字符编码和字节缓冲区的大小都是由系统设定的。如果我们想要自己指定这些值,可以在FileOutputStream上套接一个OutputStreamWriter对象。

在创建FileWriter类对象时,如果关联的文件不存在,则会自动生成一个新的文件。在创建文件之前,FileWriter会在创建对象时打开该文件作为输出目的地,但如果试图打开的是一个只读文件,会引发IOException异常。

5. 配套视频

与本节内容配套的视频链接如下:

player.bilibili.com/player.html...

三. 字符缓冲流

1. 简介

与字节缓冲流类似,我们在进行大文件读写操作时,也可以使用字符缓冲流来减少访问磁盘的次数,提高IO访问效率。Java中的字符缓冲流包括BufferedReader和BufferedWriter两大类,分别负责文件的读取和写入。

2. 常用子类

Java中的字符缓冲流可以分为缓冲的字符输入流BufferedReader和缓冲的字符输出流BufferedWriter。

  • BufferedReader :继承自InputStreamReader 类, 用于读取二进制数据,并将数据存储在内部缓冲区中;
  • BufferedWriter :继承自OutputStreamWriter类,用于写入二进制数据,并将数据存储在内部缓冲区中。

3. BufferedReader的用法

BufferedReader是一个带有缓冲区的输入流,主要用于辅助其他的字符输入流。BufferedReader可以先将一批数据读到内存缓冲区,然后接下来的读操作就可以直接从该缓冲区中获取数据,并进行字符编码转换,这样就可以提高数据的读取效率。

另外BufferedReader还提供了一个readLine()方法,该方法会返回包含所读内容的字符串,但该字符串中不包含任何终止符,如果已到达了流的末尾,就返回null。readLine()方法表示每次读取一行的文本内容,当遇到换行(\n)、回车(\r)或回车后直接跟着换行标记符,即认为某行终止。接下来我们就来看看BufferedReader是怎么使用的吧。

java 复制代码
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class Demo11 {
	public static void main(String[] args) throws FileNotFoundException, IOException {
		// try(resource)的写法
		// 1. 创建Reader对象
		try(BufferedReader reader=new BufferedReader(new FileReader("F:\a.txt"))) {
            String strLine = "";
            while ((strLine = reader.readLine()) != null) { 
                //循环读取每行数据
                System.out.println(strLine);
            }
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }  // 3.自动资源释放
	}
}

4. BufferedWriter的用法

BufferedWriter则是一个带有缓冲区的输出流,主要用于辅助其他的字符输出流。BufferedWriter同样带有缓冲区,可以先将一批数据写入到缓冲区,当缓冲区满了以后,再将缓冲区里的数据一次性写到字符输出流,这样也提高了数据的写入效率。

BufferedWriter类中提供了一个新的方法newLine(),该方法用于写入一个行分隔符。行分隔符字符串由系统属性 line.separator定义,且不一定是单个新行(\n)符。BufferedWriter类的用法如下:

java 复制代码
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;

public class Demo12 {
	public static void main(String[] args) throws FileNotFoundException, IOException {
		// try(resource)的写法
		// 1. 创建Writer对象
		try (BufferedWriter writer = new BufferedWriter(new FileWriter("F:/c.txt"))) {
			//2.写入内容
			writer.write("Hello, world!");
			//3.换行
			writer.newLine();
			writer.write("Welcome to learn Java!");
			// writer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

在上面的代码中,我们使用了BufferedWriter类来写入字符流,使用newLine()方法添加一个新行,在写入完成后可以关闭写入器对象。

5. 综合案例

在学习了上面的字符输入流、字符输出流、转换流以及缓冲流等内容之后,壹哥最后给大家设计一个综合案例,来把这几个知识点糅合在一起。

java 复制代码
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;

public class Demo13 {
	public static void main(String[] args) throws FileNotFoundException, IOException {
		try {
	        //将System.in对象转换成InputStreamReader对象
	        InputStreamReader reader = new InputStreamReader(System.in);
	        // 将Reader对象包装成BufferedReader
	        BufferedReader br = new BufferedReader(reader);
	        String line = null;
	        //利用循环方式逐行读取
	        while ((line = br.readLine()) != null) {
	            // 如果读取到"exit",则程序退出
	            if (line.equals("exit")) {
                    //退出程序
	                System.exit(1);
	            }
	            // 打印出读取到的内容
	            System.out.println("输入的内容为:" + line);
	        }
	    } catch (IOException e) {
	        e.printStackTrace();
	    }
	}
}

在上面的案例中,我们使用了System.in获取到键盘的输入信息,该信息其实是InputStream的实例。但该实例使用不太方便,我们使用 InputStreamReader 将其转换成字符输入流,因为Reader读取输入内容时依然不太方便,我们继续把Reader包装成BufferedReader。因为BufferReader具有readLine()方法,可以非常方便地一次读入一行内容。而且BufferReader具有缓冲功能,一次读取一行文本,以换行符为标志,如果它没有读到换行符,则程序堵塞,等到读到换行符为止。运行该程序后,在控制台执行输入时,只要按下回车键,程序就会打印出刚刚输入的内容。

6. 配套视频

与本节内容配套的视频链接如下:

player.bilibili.com/player.html...

------------------------------正片已结束,来根事后烟----------------------------

四. 结语

这样,我们就把字符流相关的内容学习完了,现在你会了吗?整体来说,字符流和字节流的用法大同小异,其实你只要学会一种,另外的也就可以举一反三地学会了。在下一篇文章中,壹哥 会带大家学习使用IO流时涉及到的编码问题,敬请期待哦。另外如果你独自学习觉得有很多困难,可以加入壹哥的学习互助群,大家一起交流学习。

相关推荐
blammmp7 分钟前
Java:数据结构-枚举
java·开发语言·数据结构
暗黑起源喵25 分钟前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong30 分钟前
Java反射
java·开发语言·反射
齐 飞1 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
九圣残炎1 小时前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
wclass-zhengge1 小时前
Netty篇(入门编程)
java·linux·服务器
LunarCod1 小时前
WorkFlow源码剖析——Communicator之TCPServer(中)
后端·workflow·c/c++·网络框架·源码剖析·高性能高并发
Re.不晚2 小时前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
雷神乐乐2 小时前
Maven学习——创建Maven的Java和Web工程,并运行在Tomcat上
java·maven
码农派大星。2 小时前
Spring Boot 配置文件
java·spring boot·后端