javaIO之字符流

news2024/11/17 21:55:01

目录

  • 一、简介
  • 二、字符流入流
    • 1.1FileReader构造方法
    • 1.2FileReader读取字符数据
  • 三、字符流出流
    • 3.1 FileWriter 构造方法
    • 3.2FileWriter写入数据
    • 3.3关闭close和刷新flush
    • 3.4FileWriter的续写和换行
    • 3.5文本文件复制
  • 四、IO异常处理
  • 五、小结

一、简介

字符流 Reader 和 Writer 的故事要从它们的类关系图开始,来看图。
在这里插入图片描述字符流是一种用于读取和写入字符数据的输入输出流。与字节流不同,字符流以字符为单位读取和写入数据,而不是以字节为单位。常用来处理文本信息。

如果用字节流直接读取中文,可能会遇到乱码问题,见下例:

//FileInputStream为操作文件的字符输入流
FileInputStream inputStream = new FileInputStream("a.txt");//内容为“追风少年宝哥是傻 X”

int len;
while ((len=inputStream.read())!=-1){
    System.out.print((char)len);
}

来看运行结果:

运行结果:   沉默王二是傻 X

之所以出现乱码是因为在字节流中,一个字符通常由多个字节组成,而不同的字符编码使用的字节数不同。如果我们使用了错误的字符编码,或者在读取和写入数据时没有正确处理字符编码的转换,就会导致读取出来的中文字符出现乱码。

例如,当我们使用默认的字符编码(见上例)读取一个包含中文字符的文本文件时,就会出现乱码。因为默认的字符编码通常是 ASCII 编码,它只能表示英文字符,而不能正确地解析中文字符。

那使用字节流该如何正确地读出中文呢?见下例。

try (FileInputStream inputStream = new FileInputStream("a.txt")) {
    byte[] bytes = new byte[1024];
    int len;
    while ((len = inputStream.read(bytes)) != -1) {
        System.out.print(new String(bytes, 0, len));
    }
}

为什么这种方式就可以呢?

因为我们拿 String 类进行了解码,查看new String(byte bytes[], int offset, int length)的源码就可以发现,该构造方法有解码功能:

public String(byte bytes[], int offset, int length) {
    checkBounds(bytes, offset, length);
    this.value = StringCoding.decode(bytes, offset, length);
}

继续追看 StringCoding.decode() 方法调用的 defaultCharset() 方法,会发现默认编码是UTF-8,代码如下

public static Charset defaultCharset() {
    if (defaultCharset == null) {
        synchronized (Charset.class) {
            if (cs != null)
                defaultCharset = cs;
            else
                defaultCharset = forName("UTF-8");
        }
    }
    return defaultCharset;
}
static char[] decode(byte[] ba, int off, int len) {
    String csn = Charset.defaultCharset().name();
    try {
        // use charset name decode() variant which provides caching.
        return decode(csn, ba, off, len);
    } catch (UnsupportedEncodingException x) {
        warnUnsupportedCharset(csn);
    }
}

在 Java 中,常用的字符编码有 ASCII、ISO-8859-1、UTF-8、UTF-16 等。其中,ASCII 和 ISO-8859-1 只能表示部分字符,而 UTF-8 和 UTF-16 可以表示所有的 Unicode 字符,包括中文字符。

当我们使用 new String(byte bytes[], int offset, int length) 将字节流转换为字符串时,Java 会根据 UTF-8 的规则将每 3 个字节解码为一个中文字符,从而正确地解码出中文。

尽管字节流也有办法解决乱码问题,但不够直接,于是就有了字符流,专门用于处理文本文件(音频、图片、视频等为非文本文件)。

从另一角度来说:字符流 = 字节流 + 编码表

二、字符流入流

java.io.Reader是字符输入流的超类(父类),它定义了字符输入流的一些共性方法:

  • close():关闭此流并释放与此流相关的系统资源。
  • read():从输入流读取一个字符。
  • read(char[] cbuf):从输入流中读取一些字符,并将它们存储到字符数组 cbuf中
    FileReader 是 Reader 的子类,用于从文件中读取字符数据。它的主要特点如下:

可以通过构造方法指定要读取的文件路径。
每次可以读取一个或多个字符。
可以读取 Unicode 字符集中的字符,通过指定字符编码来实现字符集的转换。

1.1FileReader构造方法

  • FileReader(File file):创建一个新的 FileReader,参数为File对象。
  • FileReader(String fileName):创建一个新的 FileReader,参数为文件名。
    代码示例如下:
// 使用File对象创建流对象
File file = new File("a.txt");
FileReader fr = new FileReader(file);

// 使用文件名称创建流对象
FileReader fr = new FileReader("b.txt");

1.2FileReader读取字符数据

①、读取字符:read方法,每次可以读取一个字符,返回读取的字符(转为 int 类型),当读取到文件末尾时,返回-1。代码示例如下:

// 使用文件名称创建流对象
FileReader fr = new FileReader("abc.txt");
// 定义变量,保存数据
int b;
// 循环读取
while ((b = fr.read())!=-1) {
    System.out.println((char)b);
}
// 关闭资源
fr.close();

②、读取指定长度的字符:read(char[] cbuf, int off, int len),并将其存储到字符数组中。其中,cbuf 表示存储读取结果的字符数组,off 表示存储结果的起始位置,len 表示要读取的字符数。代码示例如下:

File textFile = new File("docs/约定.md");
// 给一个 FileReader 的示例
// try-with-resources FileReader
try(FileReader reader = new FileReader(textFile);) {
    // read(char[] cbuf)
    char[] buffer = new char[1024];
    int len;
    while ((len = reader.read(buffer, 0, buffer.length)) != -1) {
        System.out.print(new String(buffer, 0, len));
    }
}

在这个例子中,使用 FileReader 从文件中读取字符数据,并将其存储到一个大小为 1024 的字符数组中。每次读取 len 个字符,然后使用 String 构造方法将其转换为字符串并输出。

FileReader 实现了 AutoCloseable 接口,因此可以使用 try-with-resources 语句自动关闭资源,避免了手动关闭资源的繁琐操作。

三、字符流出流

java.io.Writer 是字符输出流类的超类(父类),可以将指定的字符信息写入到目的地,来看它定义的一些共性方法:

  • write(int c) 写入单个字符。
  • write(char[] cbuf) 写入字符数组。
  • write(char[] cbuf, int off, int len) 写入字符数组的一部分,off为开始索引,len为字符个数。
  • write(String str) 写入字符串。
  • write(String str, int off, int len) 写入字符串的某一部分,off 指定要写入的子串在 str 中的起始位置,len 指定要写入的子串的长度。
  • flush() 刷新该流的缓冲。
  • close() 关闭此流,但要先刷新它。
    java.io.FileWriter 类是 Writer 的子类,用来将字符写入到文件。

3.1 FileWriter 构造方法

  • FileWriter(File file): 创建一个新的 FileWriter,参数为要读取的File对象。
  • FileWriter(String fileName): 创建一个新的 FileWriter,参数为要读取的文件的名称。
    代码示例如下:
// 第一种:使用File对象创建流对象
File file = new File("a.txt");
FileWriter fw = new FileWriter(file);

// 第二种:使用文件名称创建流对象
FileWriter fw = new FileWriter("b.txt");

3.2FileWriter写入数据

①、写入字符:write(int b) 方法,每次可以写出一个字符,代码示例如下:

FileWriter fw = null;
try {
    fw = new FileWriter("output.txt");
    fw.write(72); // 写入字符'H'的ASCII码
    fw.write(101); // 写入字符'e'的ASCII码
    fw.write(108); // 写入字符'l'的ASCII码
    fw.write(108); // 写入字符'l'的ASCII码
    fw.write(111); // 写入字符'o'的ASCII码
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        if (fw != null) {
            fw.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

在这个示例代码中,首先创建一个 FileWriter 对象 fw,并指定要写入的文件路径 “output.txt”。然后使用 fw.write() 方法将字节写入文件中,这里分别写入字符’H’、‘e’、‘l’、‘l’、'o’的 ASCII 码。最后在 finally 块中关闭 FileWriter 对象,释放资源。

需要注意的是,使用 write(int b) 方法写入的是一个字节,而不是一个字符。如果需要写入字符,可以使用 write(char cbuf[]) 或 write(String str) 方法。

②、写入字符数组:write(char[] cbuf) 方法,将指定字符数组写入输出流。代码示例如下:

FileWriter fw = null;
try {
    fw = new FileWriter("output.txt");
    char[] chars = {'H', 'e', 'l', 'l', 'o'};
    fw.write(chars); // 将字符数组写入文件
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        if (fw != null) {
            fw.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

③、写入指定字符数组:write(char[] cbuf, int off, int len) 方法,将指定字符数组的一部分写入输出流。代码示例如下(重复的部分就不写了哈,参照上面的部分):

fw = new FileWriter("output.txt");
    char[] chars = {'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!'};
fw.write(chars, 0, 5); // 将字符数组的前 5 个字符写入文件

使用 fw.write() 方法将字符数组的前 5 个字符写入文件中。

④、写入字符串:write(String str) 方法,将指定字符串写入输出流。代码示例如下:

fw = new FileWriter("output.txt");
String str = "追风少年";
fw.write(str); // 将字符串写入文件

⑤、写入指定字符串:write(String str, int off, int len) 方法,将指定字符串的一部分写入输出流。代码示例如下(try-with-resources形式):

String str = "追风少年之宝哥!";
try (FileWriter fw = new FileWriter("output.txt")) {
    fw.write(str, 0, 5); // 将字符串的前 5 个字符写入文件
} catch (IOException e) {
    e.printStackTrace();
}

【注意】如果不关闭资源,数据只是保存到缓冲区,并未保存到文件中。

3.3关闭close和刷新flush

因为 FileWriter 内置了缓冲区 ByteBuffer,所以如果不关闭输出流,就无法把字符写入到文件中。
在这里插入图片描述
flush :刷新缓冲区,流对象可以继续使用。

close :先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。

flush还是比较有趣的,来段代码体会体会:

//源   也就是输入流【读取流】 读取a.txt文件
FileReader fr=new FileReader("abc.txt");  //必须要存在a.txt文件,否则报FileNotFoundException异常
//目的地  也就是输出流
FileWriter fw=new FileWriter("b.txt");  //系统会自动创建b.txt,因为它是输出流!
int len;
while((len=fr.read())!=-1){
    fw.write(len);
}
//注意这里是没有使用close关闭流,开发中不能这样做,但是为了更好的体会flush的作用

运行效果是怎么样的呢?答案是b.txt文件中依旧是空的,并没有任何东西。

原因我们前面已经说过了。编程就是这样,不去敲,永远学不会!!!所以一定要去敲,多敲啊!!!

在以上的代码中再添加下面三句代码,b.txt文件就能复制到源文件的数据了!

fr.close();
fw.flush();
fw.close();

你可以使用下面的代码示例再体验一下:

// 使用文件名称创建流对象
FileWriter fw = new FileWriter("fw.txt");
// 写出数据,通过flush
fw.write('刷'); // 写出第1个字符
fw.flush();
fw.write('新'); // 继续写出第2个字符,写出成功
fw.flush();

// 写出数据,然后close
fw.write('关'); // 写出第1个字符
fw.close();
fw.write('闭'); // 继续写出第2个字符,【报错】java.io.IOException: Stream closed
fw.close();

注意,即便是flush方法写出了数据,操作的最后还是要调用close方法,释放系统资源。当然你也可以用 try-with-resources 的方式。

3.4FileWriter的续写和换行

续写和换行:操作类似于FileOutputStream操作,直接上代码:

// 使用文件名称创建流对象,可以续写数据
FileWriter fw = new FileWriter("fw.txt",true);     
// 写出字符串
fw.write("追风少年之宝哥");
// 写出换行
fw.write("\r\n");
// 写出字符串
fw.write("是傻 X");
// 关闭资源
fw.close();

输出结果如下所示:

输出结果:
追风少年之宝哥
是傻 X

3.5文本文件复制

直接上代码:

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CopyFile {
    public static void main(String[] args) throws IOException {
        //创建输入流对象
        FileReader fr=new FileReader("aa.txt");//文件不存在会抛出java.io.FileNotFoundException
        //创建输出流对象
        FileWriter fw=new FileWriter("copyaa.txt");
        /*创建输出流做的工作:
         *      1、调用系统资源创建了一个文件
         *      2、创建输出流对象
         *      3、把输出流对象指向文件        
         * */
        //文本文件复制,一次读一个字符
        copyMethod1(fr, fw);
        //文本文件复制,一次读一个字符数组
        copyMethod2(fr, fw);
        
        fr.close();
        fw.close();
    }

    public static void copyMethod1(FileReader fr, FileWriter fw) throws IOException {
        int ch;
        while((ch=fr.read())!=-1) {//读数据
            fw.write(ch);//写数据
        }
        fw.flush();
    }

    public static void copyMethod2(FileReader fr, FileWriter fw) throws IOException {
        char chs[]=new char[1024];
        int len=0;
        while((len=fr.read(chs))!=-1) {//读数据
            fw.write(chs,0,len);//写数据
        }
        fw.flush();
    }
}

四、IO异常处理

我们在学习的过程中可能习惯把异常抛出,而实际开发中建议使用try…catch…finally 代码块,处理异常部分,格式代码如下:

// 声明变量
FileWriter fw = null;
try {
    //创建流对象
    fw = new FileWriter("fw.txt");
    // 写出数据
    fw.write("二哥真的帅"); //哥敢摸si
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        if (fw != null) {
            fw.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

或者直接使用 try-with-resources 的方式。

try (FileWriter fw = new FileWriter("fw.txt")) {
    // 写出数据
    fw.write("二哥真的帅"); //哥敢摸si
} catch (IOException e) {
    e.printStackTrace();
}

在这个代码中,try-with-resources 会在 try 块执行完毕后自动关闭 FileWriter 对象 fw,不需要手动关闭流。如果在 try 块中发生了异常,也会自动关闭流并抛出异常。因此,使用 try-with-resources 可以让代码更加简洁、安全和易读。

五、小结

Writer 和 Reader 是 Java I/O 中用于字符输入输出的抽象类,它们提供了一系列方法用于读取和写入字符数据。它们的区别在于 Writer 用于将字符数据写入到输出流中,而 Reader 用于从输入流中读取字符数据。

Writer 和 Reader 的常用子类有 FileWriter、FileReader,可以将字符流写入和读取到文件中。

在使用 Writer 和 Reader 进行字符输入输出时,需要注意字符编码的问题.

相关文章链接:
javaIO之各种流的分类与实际应用
javaIO流之文件流
javaIO流之字节流
javaIO流之字符流
javaIO流之缓冲流
javaIO流之转换流
javaIO流之序列流

我如杀戮之中绽放,亦如黎明前的花朵。如果觉得有用,点个赞吧~~~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/543781.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023最全最细的Selenium+Pytest自动化测试框架实战

选前言# selenium自动化 pytest测试框架 本章你需要 一定的python基础——至少明白类与对象,封装继承 一定的selenium基础——本篇不讲selenium,不会的可以自己去看selenium中文翻译网 测试框架简介# 测试框架有什么优点呢: 代码复用率…

马斯克开造人形AI 变形金刚要成真?

当5个人形机器人迎面走来,你会下意识地期待它们具备怎样的能力。特斯拉最近公布的视频给出答案,它自研的机器人“擎天柱”(Optimus)能够探索环境,复刻人类动作,自主操纵物品。 特斯拉CEO埃隆马斯克对这些与…

chatgpt赋能Python-python3_4_4怎么打代码

Python3.4.4的入门指南:如何打出高效的Python代码? 作为一名有10年Python编程经验的工程师,我深深地了解到Python3.4.4的强大和灵活性。Python是一种易于学习和使用的编程语言,因此它已成为数据科学家,人工智能和机器…

700页!阿里120W架构师推荐学习的微服务架构解析,到底牛在哪里?

前言 当前,微服务架构在国内正处于蓬勃发展的阶段,无论是大型互联网公司还是传统的IT企业,纷纷采用微服务架构构建系统。 在过去几年里,DevOps、云原生、面向演进式架构等理念已经深入人心,围绕微服务生态也出现了大…

JavaEE(系列9) -- 多线程案列2(堵塞队列)

目录 1. 堵塞队列 2. 生产者消费者模型 2.1 解耦合 2.2 削峰填谷 2.3 代码实现生产者消费者模型 3. 构建堵塞队列 3.1 实现普通队列(循环队列) 3.2 普通队列加上线程安全 3.3 普通队列实现堵塞功能 3.4 堵塞队列最终代码 4. 使用生产者消费者模型测试自己构建的堵塞队列 1. …

IP协议的相关特性、数据链路层相关内容讲解

文章目录 IP协议相关特性地址管理NAT机制IP地址的组成特殊的IP地址 路由选择 数据链路层相关内容以太网MTU IP协议相关特性 首先我们来认识一下IP协议的报头: 4位版本号:指定IP协议的版本,对于IPv4就是4. 4位头部长度:IP头部的长…

每日一题158—— 图片平滑器

图像平滑器 是大小为 3 x 3 的过滤器,用于对图像的每个单元格平滑处理,平滑处理后单元格的值为该单元格的平均灰度。 每个单元格的 平均灰度 定义为:该单元格自身及其周围的 8 个单元格的平均值,结果需向下取整。(即…

常用中外文献检索网站大盘点

一、常用中文文献检索权威网站: 1、知网:是全球最大的中文数据库。提供中国学术文献、外文文献、学位论文、报纸、会议、年鉴、工具书等各类资源,并提供在线阅读和下载服务。涵盖领域包括:基础科学、文史哲、工程科技、社会科学、…

世界博物馆日:一起来看看这些“不太正经”的文物!

今天是5月18日,世界博物馆日。 这两年喜欢逛博物馆的年轻人越来越多了。和爬山、露营一样,博物馆打卡已经是这一届年轻人最受欢迎的娱乐方式之一了。 今天我们要和大家分享的是:全国各地博物馆里收藏的那些萌的、凶的、神秘的、搞笑的…精品…

从零开始 Spring Boot 31:Spring 表达式语言

从零开始 Spring Boot 31:Spring 表达式语言 图源:简书 (jianshu.com) Spring表达式语言(Spring Expression Language,简称 “SpEL”)是一种强大的表达式语言,支持在运行时查询和操作对象图。该语言的语法…

2024王道数据结构考研丨第一章:绪论

2024王道数据结构考研笔记专栏将持续更新,欢迎 点此 收藏,共同交流学习… 文章目录 第一章:绪论1.1数据结构的基本概念1.2数据结构的三要素1.3算法的基本概念1.4算法的时间复杂度1.5算法的空间复杂度 第一章:绪论 1.1数据结构的基…

Vue - vxe-table 表格合并行应用

Vue - vxe-table 表格合并行应用 一. 将相同的列数据合并为一行实现效果实现方法 二. 拓展合并:根据某个字段合并后的数据 进行合并其他字段列实现效果实现方法 vxe-table 地址:https://vxetable.cn/v2/#/table/start/install 一. 将相同的列数据合并为一…

亚马逊云科技作为中国出海力量之一,为中国企业提供技术桥梁

这是一个真实的故事:一家出海企业的项目交付需要在非洲吉布提部署上云,企业负责人在地图上找了半天才找到吉布提,而亚马逊云科技仅用了3天的时间就为企业在当地的业务开展,交付了IT基础设施。对于出海企业来说,这种效率…

前端学习--Vue(2)常见指令

一、Vue简介 1.1 概念 Vue是一套用于构建用户界面的前端框架 框架:现成解决方案,遵守规范去编写业务功能 指令、组件、路由、Vuex、vue组件库 1.2 特性 数据驱动视图 vue连接页面结构和数据,监听数据变化,自动渲染页面结构…

【遗传算法】【处理图像类问题】

文章目录 一、前言二、问题描述三、算法介绍四、其他知识点Reference 一、前言 近期感兴趣的算法,以前没这么好奇过一个算法。时间没想象的焦虑,认真做一些事情算法入门篇 二、问题描述 从前,一群扇贝在海岸边悠哉游哉地生活着。它们衣食不…

亚马逊云科技宣布推出一个新的开源示例应用程序

5月5日,亚马逊云科技宣布推出一个新的开源示例应用程序,这是一个虚构的二手书电子商务商店,被称之为Bob’s Used Books,可供使用亚马逊云科技的.NET开发人员使用。“亚马逊云科技的.NET宣传和开发团队定期与客户交谈,在…

如何科学地利用高光谱图像合成真彩色RGB图像?

如何科学地利用高光谱图像合成真彩色RGB图像? 1. 前言 参考链接: 色匹配函数是什么? - 知乎 (zhihu.com) 23. 颜色知识1-人类的视觉系统与颜色 - 知乎 (zhihu.com) 色彩空间基础 - 知乎 (zhihu.com) 色彩空间表示与转换 - 知乎 (zhihu.com) CIE XYZ - fresh…

Golang笔记:使用melody包进行WebSocket通讯

文章目录 目的使用示例与说明总结 目的 WebSocket是Web开发应用中非常常用的功能,用于客户端和服务器间长时间的实时双向数据通讯。Golang中官方并没有实现这个功能,需要借助第三方的包来实现。 目前被最广泛使用的包是 gorilla/websocket https://pkg…

Host头攻击

转载与:https://blog.csdn.net/weixin_47723270/article/details/129472716 01 HOST头部攻击漏洞知识 Host首部字段是HTTP/1.1新增的,旨在告诉服务器,客户端请求的主机名和端口号,主要用来实现虚拟主机技术。 运用虚拟主机技术&a…

第八章:C语言的简单指针

谈起指针,简直就是谈虎色变,学习C语言的人都知道,指针的难度,就好像高中的导数一样,难道离谱,但是但是,别慌呀,咱们慢慢来,空杯心态,一步一个脚印&#xff0c…