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

2023年 10月 7日 150.3k 0

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

千锋教育高级教研员、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流时涉及到的编码问题,敬请期待哦。另外如果你独自学习觉得有很多困难,可以加入壹哥的学习互助群,大家一起交流学习。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论