一、字符流概述
字符流的底层就是字节流+字符集。
特点:
输入流:一次读取一个字节,遇到中文时,一次读取多个字节。
输出流:底层会把数据按照指定的编码方式进行编码,编程字节再写到文件中。
使用场景:对于纯文本文件进行读写操作。
二、字符输入流与字符输出流
字符流在底层实现中已经有了一定的缓冲机制(通过字节缓冲区实现字符的读写),但用户通常不会直接操作这个缓冲区。此外,Java还提供了BufferedReader和BufferedWriter等缓冲字符流来进一步提高效率。
(一)字符输入流——FileReader
1.FileReader书写步骤
(1)创建字符输入流对象
(2)读取数据
(3)释放资源
2.FileReader书写细节
(1)创建字符输入流对象
| 构造方法 | 说明 |
| public FileReader(File file) | 创建字符输入流关联本地文件 |
| public FileReader(String pathname) | 创建字符输入流关联本地文件 |
(2)读取数据
| 成员方法 | 说明 |
| public int read() | 读取数据,读到末尾返回-1 |
| public int read(char[ buffer]) | 读取多个数据,读到末尾返回-1 |
细节1:按字节进行读取,遇到中文,一次读多个字节,读取后解码,返回一个十进制整数
细节2:读到文件末尾了,read方法返回-1
(3)释放资源
| 成员方法 | 说明 |
| public int close() | 释放资源/关流 |
3.空参的read方法读取数据
public class CharStreamDemo1 {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("chapter18\\d.txt");
int ch;
while ((ch = fr.read()) != -1) {
System.out.print((char) ch);
}
fr.close();
}
}
运行结果:

4.有参的read方法读取数据
read(chars):读取数据、解码、强转三步合并了,把强转之后的字符放到数组当中
相当于read()+强制类型转换
public class CharStreamDemo2 {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("chapter18\\d.txt");
int len;
char[] chars = new char[2];
while ((len = fr.read(chars)) != -1) {
// 把数组中的数据变成字符串再进行打印
System.out.print(new String(chars, 0, len));
}
fr.close();
}
}
运行结果:

5.FileReader底层原理


同时读写一个文件:

(二)字符输出流——FileWriter
1.FileWriter方法介绍
(1)FileWriter构造方法
| 构造方法 | 说明 |
|---|---|
| public FileWriter(File file) | 创建字符输出流关联本地文件 |
| public FileWriter(String pathname) | 创建字符输出流关联本地文件 |
| public FileWriter(File file, boolean append) | 创建字符输出流关联本地文件,续写 |
| public FileWriter(String pathname, boolean append) | 创建字符输出流关联本地文件,续写 |
(2)FileWriter成员方法
| 成员方法 | 说明 |
|---|---|
| void write(int c) | 写出一个字符 |
| void write(String str) | 写出一个字符串 |
| void write(String str, int off, int len) | 写出一个字符串的一部分 |
| void write(char[] cbuf) | 写出一个字符数组 |
| void write(char[] cbuf, int off, int len) | 写出字符数组的一部分 |
2.FileWriter书写细节
3.FileWriter写出数据
关于字节输入流写出数据的弊端:
一个字节(Byte)是计算机存储数据的基本单位,它固定等于8位(Bits)。因此,一个字节的最大值是255(十进制),或者说是`0xFF`(十六进制),或者说是`11111111`(二进制)。
如果你有一个字节输出流(例如,在Java中的`OutputStream`),并且你希望每次只能操作一个字节,那么你需要确保每次写入的整数不超过一个字节的范围,即0到255之间(包括0和255)。
字节输出流一次性写出大小超过一个字节大小代码示例:
public class CharStreamDemo3 { public static void main(String[] args) throws IOException { // 字节输出流写出数据,每次只能操作一个字节,传入的参数不能超过一个字节的大小 FileOutputStream fos = new FileOutputStream("chapter18\\e.txt"); fos.write(25105); fos.close(); } }运行结果:
字符输出流写出数据:
public class CharStreamDemo3 {
public static void main(String[] args) throws IOException {
// 字符输出流写出数据 打开续写开关
FileWriter fw = new FileWriter("chapter18\\e.txt", true);
// 根据字符集的编码方式进行编码,把编码之后的数据写到文件中去
// IDEA默认是UTF-8
// 写出一个字符
fw.write(25105);
// 写出一个字符串
String str = "\n向晚意不适,驱车登古原。\n夕阳无限好,只是近黄昏。\n";
fw.write(str);
// 写出一个字符串的一部分
fw.write(str, 7, 5);
// 写出一个字符数组
char[] chars = {'a', 'b', 'c', 'd'};
fw.write(chars);
fw.close();
}
}
运行结果:


4.FileWriter底层原理——flush和close方法
| 成员方法 | 说明 |
| public void flush() | 将缓冲区的数据,刷新到本地文件中 刷新之后,还可以继续往文件中写出数据 |
| public void close() | 释放资源/关流 断开通道,无法再往文件中写出数据 |
将缓冲区的数据写入到文件中有三种情况:
- 缓冲区装满了(长度为8192的字节数组)
- 使用flush方法,会将缓冲区的数据写出到文件中,后面还可以继续往文件中写出数据
- 使用close方法,关流,通道关闭,后面的数据就不会再写出到文件中,抛出IOException异常

使用flush方法,如果没有关流,那么flush后面的数据不会写出到文件中,是将缓冲区的数据写出到文件中了。

如果有关流,那么后面的数据就会重新加载到缓冲区最前面,写出到文件中。
加载前:

加载后:

三、字节流与字符流综合练习(重要)
字节流:拷贝任意类型的文件
字符流:读取纯文本文件中的数据;往纯文本文件中写出数据
1.拷贝一个文件夹,考虑子文件夹
public class TestDemo1 {
public static void main(String[] args) throws IOException {
File src = new File("D:\\test");
File dest = new File("chapter18\\copytest1");
copyDir(src, dest);
}
private static void copyDir(File src, File dest) throws IOException {
// 创建要拷贝的文件夹
dest.mkdirs();
// 1.进入数据源
File[] files = src.listFiles();
// 如果进入的文件夹没有权限,就需要做一个非空判断
if (files != null) {
// 2.遍历数组
for (File file : files) {
if (file.isFile()) {
// 3.判断是文件,字节流进行拷贝
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(new File(dest, file.getName()));
int len;
byte[] bytes = new byte[1024 * 1024];
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
fos.close();
fis.close();
} else {
// 4.判断是文件夹,递归
copyDir(file, new File(dest, file.getName()));
}
}
}
}
}
运行结果:

2.文件加密
为了保证文件的安全性,就需要对原始文件进行加密存储,再次使用的时候再对其进行解密处理。
加密原理:对原始文件中的每一个字节数据进行更改,然后将更改以后得数据存储到新的文件中。
解密原理:读取加密之后的文件,按照加密的规则反向操作,变成原始文件。
^ : 异或
两边相同:false
两边不同:true0:false
1:true100:1100100
10: 10101100100
^ 0001010
__________
1101110
^ 0001010
__________
1100100一个数异或自己两次还是自己
public class TestDemo1 {
public static void main(String[] args) throws IOException {
// 给文件加密操作
// File src = new File("chapter18\\图像1.jpg");
// File dest = new File("chapter18\\ency.png");
// ency(src, dest);
// 给文件解密操作
File encyFile = new File("chapter18\\ency.png");
File redoFile = new File("chapter18\\redo.png");
ency(encyFile,redoFile);
}
private static void ency(File src, File dest) throws IOException {
// 1.创建对象关联原始文件
FileInputStream fis = new FileInputStream(src);
// 2.创建对象关联加密/解密后的文件
FileOutputStream fos = new FileOutputStream(dest);
// 3.加密处理
int len;
while ((len = fis.read()) != -1) {
fos.write(len ^ 10);
}
// 4.释放资源
fos.close();
fis.close();
}
}
3.修改文件中的数据
将f.txt文件中的2-1-9-4-7-8按照升序排序
public class TestDemo3 {
public static void main(String[] args) throws IOException {
/**
* 细节1:文件中的数据不要换行
* 细节2:文件不能有BOM头
*/
File file = new File("chapter18\\f.txt");
// test1(file);
// test2(file);
// test3(file);
test4(file);
}
public static void test1(File src) throws IOException {
// 1.读取数据
FileReader fr = new FileReader(src);
StringBuilder sb = new StringBuilder();
int ch;
while ((ch = fr.read()) != -1) {
sb.append((char) ch);
}
fr.close();
System.out.println(sb);
// 2.分割数据
String[] split = sb.toString().split("-");
// 3.排序数据
Arrays.sort(split);
System.out.println(Arrays.toString(split));
// 4.拼接
sb = new StringBuilder();
for (int i = 0; i < split.length; i++) {
if (i < split.length - 1) {
sb.append(split[i]).append("-");
} else {
sb.append(split[i]);
}
}
System.out.println(sb);
// 5.续写写出
FileWriter fw = new FileWriter(src, true);
fw.write("\n" + sb);
fw.close();
}
// 方式二:使stream流
public static void test2(File src) throws IOException {
FileReader fr = new FileReader(src);
StringBuilder sb = new StringBuilder();
int len;
while ((len = fr.read()) != -1) {
sb.append((char) len);
}
System.out.println(sb);
String[] array = Arrays
.stream(sb.toString().split("-"))
.sorted()
.toArray(String[]::new);
System.out.println(Arrays.toString(array));
FileWriter fw = new FileWriter(src, true);
fw.write("\n");
for (int i = 0; i < array.length; i++) {
if (i < array.length - 1) {
fw.write(array[i] + "-");
} else {
fw.write(array[i]);
}
}
fw.close();
}
// 方式三:使用集合
public static void test3(File src) throws IOException {
FileReader fr = new FileReader(src);
StringBuilder sb = new StringBuilder();
int len;
while ((len = fr.read()) != -1) {
sb.append((char) len);
}
System.out.println(sb);
List<String> list = Arrays
.stream(sb.toString().split("-"))
.sorted()
.collect(Collectors.toList());
System.out.println(list);
FileWriter fw = new FileWriter(src, true);
fw.write("\n");
for (int i = 0; i < list.size(); i++) {
if (i < list.size() - 1) {
fw.write(list.get(i) + "-");
} else {
fw.write(list.get(i) + "");
}
}
fw.close();
}
// 使用数组截取
public static void test4(File src) throws IOException {
FileReader fr = new FileReader(src);
StringBuilder sb = new StringBuilder();
int len;
while ((len = fr.read()) != -1) {
sb.append((char) len);
}
System.out.println(sb);
Integer[] array = Arrays
.stream(sb.toString().split("-"))
.map(Integer::parseInt)
.sorted()
.toArray(Integer[]::new);
System.out.println(Arrays.toString(array));
FileWriter fw = new FileWriter(src, true);
fw.write("\n");
String replace = Arrays.toString(array).replace(", ", "-");
System.out.println(replace);
String result = replace.substring(1, replace.length() - 1);
System.out.println(result);
fw.write(result);
fw.close();
}
}
运行结果:



6113

被折叠的 条评论
为什么被折叠?



