作者:孙玉昌,昵称【一一哥】,另外【壹壹哥】也是我哦
千锋教育高级教研员、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,比如:
// 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读取字符流,可以遵循以下基本步骤:
创建一个Reader对象,比如InputStreamReader、FileReader、StringReader等; 调用Reader的read()方法来读取字符流,该方法会返回一个整数,表示读取的字符数。如果已到达流的末尾,则read()方法返回-1; 处理读取的字符,可以将它们存储在数组或字符串中; 循环调用read()方法,直到读取完整个字符流。
5. 代码案例
为了读取方便,Java给我们提供了用来读取字符文件的便捷类——FileReader,所以这里壹哥利用FileReader读取了一个文本文件。
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时指定编码:
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是一种用于将字节输出流转换为字符输出流的转换流:
//创建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的使用案例,代码如下:
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是怎么使用的吧。
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类的用法如下:
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. 综合案例
在学习了上面的字符输入流、字符输出流、转换流以及缓冲流等内容之后,壹哥最后给大家设计一个综合案例,来把这几个知识点糅合在一起。
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流时涉及到的编码问题,敬请期待哦。另外如果你独自学习觉得有很多困难,可以加入壹哥的学习互助群,大家一起交流学习。