javaIO之字符流

article/2025/9/17 18:04:02

目录

  • 一、简介
  • 二、字符流入流
    • 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;elsedefaultCharset = 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://chatgpt.dhexx.cn/article/4Meegq6C.shtml

相关文章

字符流定义及如何深入理解字符流的编码

IputSrem类和OupuSrem类在读写文件时操作的都是字节,如果希望在程序中操作字符,使用这两个类就不太方便,为此JDK提供了字符流。同字节流样,字符流也有两个抽象的顶级父类,分别是Reader和Writer其中,Reader是…

Java:字符流

字符流的底层其实就是字节流。 字符流字节流字符集 结构体系: 1.特点 输入流:一次读一个字节,遇到中文时,一次读多个字节。 输出流:底层会把数据按照指定的编码方式进行编码,变成字节再写到文件中。 2.使用场景 对于纯文本…

java的字符流

字符流的底层也是字节流。字符流字节流字符集。 特点是输入流一次读一个字节,遇到中文时,一次读多个字节(读多少个与字符集有关);输出流底层会把数据按照指定的编码方式进行编码,变成字节再写到文件中。 字…

java字符流

前言 输入流:把数据(键盘输入、鼠标、扫描仪等等外设设备)读入到内存(程序)中 输出流:把内存(程序)中的数据输出到外设或其他地方,从文件角度简单总结就是,输…

字符流

3.字符流 3.1为什么会出现字符流 由于字节流操作中文不是特别方便,所以Java就提供了字符流 字符流字节流编码表 用字节流复制文本时,文本文件也会有中文,但是没有问题,原因是最终底层操作会自动进行字节拼接成中文&#xff0c…

字符流(字符输入流和字符输出流)

概述 当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文…

2、字符流详解

文章目录 一、字符流 (也叫转换流)1、字符输出流:Writer2、字符输入流:Reader3、字符输入流和字符输出流的简化版本4、字符缓冲流 一、字符流 (也叫转换流) 文件字符流 字符流的两个抽象父类:字符输出流:Writer 和字符输入流&…

渗透测试中的LLMNR/NBT-NS欺骗攻击

简介 LLMNR&NBT-NS 欺骗攻击是一种经典的内部网络攻击,然而由于一方面了解它的人很少,另一方面在Windows中它们是默认启用的,所以该攻击到现在仍然是有效的。在本文中,我们首先为读者解释什么是LLMNR&NBT…

如何避免LLMNR / NBT-NS欺骗攻击?

当DNS名称解析时,链路本地多播名称解析(LLMNR)和NetBIOS名称注册和解析服务(NBT-NS)会负责查找网络中所请求的主机地址。 在DNS服务器请求失败时,请求方会在整个网络中进行广播,查找所请求的主…

Active Directory 101 - LLMNR

This is the first article of my Active Directory Series. I’ll be reading through materials and try to explain the key concepts in AD and AD penetration test. Let’s cut directly to it. The Concept References: RFC from IETFLLMNR WikipediaHow LLMNR Work…

内网渗透技术之超越LLMNR/NBNS欺骗的ADIDNS欺骗攻击

利用名称解析协议中的缺陷进行内网渗透是执行中间人(MITM)攻击的常用技术。有两个特别容易受到攻击的名称解析协议分别是链路本地多播名称解析(LLMNR)和NetBIOS名称服务(NBNS)。攻击者可以利用这两种协议来…

利用 LLMNR 名称解析缺陷劫持内网指定主机会话

导读本文将会对 LLMNR 协议进行分析并用 python 实现质询和应答。后半部分则会重点阐述利用 LLMNR 在名称解析过程中的缺陷进行实战攻击的部分思路。 0x00 LLMNR 简介 从 Windows Vista 起,Windows 操作系统开始支持一种新的名称解析协议 —— LLMNR,主要…

LLMNR协议

LLMNR协议 http://en.wikipedia.org/wiki/Link-local_Multicast_Name_Resolution The Link Local Multicast Name Resolution (LLMNR) is a protocol based on the Domain Name System (DNS) packet format that allows both IPv4 and IPv6 hosts to perform name resolutio…

组播风暴引起的路由系统重启(LLMNR协议)

网络拓扑 一台路由设备连接可以上网的上级,连接方式DHCP,一台中继器,2.4G和5G同时中继到路由设备(双频中继之后,优先走5G),一台chromecast播放视频,一台ipad连接,一台网络摄像头连接…

内网渗透研究:LLMNR和NetBIOS欺骗攻击分析

目录 基础知识 LLMNR是什么? LLMNR 的工作过程 NetBIOS是什么? Windows系统名称解析顺序 LLMNR和NetBIOS欺骗攻击 攻击原理 Responder工具利用过程 针对LLMNR和NetBIOS欺骗攻击的防御 基础知识 LLMNR是什么? 链路本地多播名称解析&…

LLMNR和NetBIOS欺骗攻击分析及防范

本文首发于先知社区:https://xz.aliyun.com/t/9714 链路本地多播名称解析(LLMNR)是一个基于域名系统(DNS)数据包格式的协议,IPv4和IPv6的主机可以通过此协议对同一本地链路上的主机执行名称解析。 在DNS …

llmnr协议 名称解析缺陷劫持内网指定主机会话

目录 0x00 LLMNR 简介 0x01 LLMNR 协议分析 0x02 LLMNR 名称解析过程 0x03 编程实现 LLMNR 的质询和应答 0x04 LLMNR Poison 攻击原理 0x05 利用伪造源 IP LLMNR Poisone 劫持内网指定主机会话 0x06 LLMNR Poison 实战攻击思路 0x07 总结 0x00 LLMNR 简介 从 Windows …

NetBIOS名称欺骗和LLMNR欺骗

本文原创作者: 贺兰山缺口 原创投稿详情:重金悬赏 | 合天原创投稿等你来! NetBIOS和LLMNR简介 NetBIOS和Link-LocalMulticast NameResolution(LLMNR)是Microsoft针对工作组和域设计的名称解析协议,主要用于…

【内网学习笔记】18、LLMNR 和 NetBIOS 欺骗攻击

0、前言 如果已经进入目标网络,但是没有获得凭证,可以使用 LLMNR 和 NetBIOS 欺骗攻击对目标进行无凭证条件下的权限获取。 1、基本概念 LLMNR 本地链路多播名称解析(LLMNR)是一种域名系统数据包格式,当局域网中的…

LLMNR Poison技术详解

一、LLMNR 协议 简介 从 Windows Vista 起,Windows 操作系统开始支持一种新的名称解析协议 —— LLMNR,主要用于局域网中的名称解析。LLMNR 能够很好的支持 IPv4 和 IPv6,因此在 Windows 名称解析顺序中是一个仅次于 DNS 的名称解析方式&am…