一,字符流
- 编码:把字符按照一定的方式转换为对应的数字,【就是把内存中的资源存放到磁盘文件的过程;磁盘文件中存放的都是数字【以字节的方式存储的】】
- 解码:把文件中的存储的数字按照一定的方式转换为对应的字符
- 编码使用的转换方式和解码使用的转换方式如果一致的看到的就是正常的内容
- 编码使用的转换方式和解码使用的转换方式如果不一致看到的就是非正常的内容【乱码】
-
使用字节流处理字符【字符串】的问题
- 使用字节流写字符【字符串】
可以使用,但是需要先把字符串【字符】转成字节数组,再存储到文件中,比较麻烦
字符--->字节数---->进行写出【写出到文件】
2. 使用字节流读取字符
如果是纯英文,可以一次读取一个字节或者读多个没有影响
如果是纯中文,可以一次读取两个字节(GBK)那就没有毛病,读取三个字节就会出
3. 现乱码的现象
如果是中英文混杂,每次不知道读取多少个字节,因此无论字节数组准备多大,都有可能会出现乱码【无法避免乱码的现象】导致字节流操作纯文本文件的时候会出现乱码的现象
代码示例
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class Demo {
public static void main(String[] args) throws IOException {
//a.txt文件里面的内容 纯英文abcdefg
//使用字节流去读磁盘里面的内容,当是中英文时出现乱码
FileInputStream fis = new FileInputStream("a/a.txt");
/*int i = fis.read();
System.out.println((char)i); //a,,字符强转
byte[] b = new byte[3];
int len = fis.read(b); //int len 有效个数
System.out.println(new String(b,0,len)); //bcd*/
//a.txt文件里面的内容改为纯中文:我爱你中国
byte[] b = new byte[6];
int i = fis.read(b);
System.out.println(new String (b,0,i)); //我爱,一个汉字3个字节,两个汉字6个字节
int i1 = fis.read(b);
System.out.println(new String (b,0,i1));//你中,
//a.txt文件里面的内容改为中英文:a我c爱b你v中d国
int i2 = fis.read(b);
System.out.println(new String (b,0,i2)); //出现乱码
}
}
-
解决方案
- 出现乱码的原因:
每次不知道读取多少个字节,转换成字符。
而我们在代码中将每次读取的字节个数写死了导致乱码的产生。
- 解决:
动态判断每次应该读取多少个字节,
比如:在GBK编码表中,如果是一个英文,io流读取读到的字节是正数,那么就可以断定是英文字符,
就读取一个字节转成字符即可在GBK编码表中,如果是一个中文,io流读取到的第一个字节是负数,
就知道读到的是一个中文,就需要再次读取一个字节,两个字节一起转成字符。 说明:
如果在工程中频繁做这么底层的操作,太复杂,直接使用jdk中已经提供好的解决方案
字符流:不仅能动态判断GBK的每个字符是中文还是英文,还可以判断其他编码表的
所有语种- 乱码的原因:读取或者写入使用的字符集和目标文件的字符集不一致导致乱码
-
字符流
- 概述:以字符单位进行读写数据的io流。只能操作纯文本文件,比如:图片也是字节组成的,但是他的字节在任何的字符集中是没有对应的字符的,字符流操作不了非纯文本文件的
- 纯文本文件:内容全部是使用人类语言符号组成的文件
- 非纯文本文件:包含了图片、视频、音频等数据的文本
- 分类:
- 字符输入流: 也是一个抽象类【Reader】
- 字符输出流: 也是一个抽象类【Writer】
- 作用: 传输字符型数据
二,字符输入流【Reader】
- 概述:以字符为单位进行输入内存数据的io流,是一个抽象类【字符输入流顶层类】
- 常用功能:
- read(): 每次读一个字符
- read(char[] ch): 每次的读取一个字符数组的内容
- char[] 数组是容器,能否读满有要求,没有。读满全部拿来用,没读满拿去我读到的内容【返回值就是有效字符个数】
- 由于它是抽象类不能直接创建对象使用这些功能,需要学习他的子类。
-
字符输入流的子类:FileReader【文本字符输入流】
- 概述:是字符输入流的子类,主要用来实现父类的功能。
- 用于内存和磁盘之间的数据交互
- 构造方法:
- FileReader(File file): 创建以file为读取目标的字符流对象
- FileReader(String path): 创建以地址path为读取目标的字符流对象
- 常用方法:
- read(): 每次读一个字符
- read(char[] ch): 每次的读取一个字符数组的内容
代码示例
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class Demo_Reader {
public static void main(String[] args) throws IOException {
//创建字符输入流对象
//a.txt文件里面的内容是:a中vv公 a教a育优就业
FileReader fr=new FileReader("a/a.txt");
int i = fr.read(); //int i,表示返回的字节数
System.out.println(i); //97,返回的字节数是97
System.out.println((char)i); //a,强转为字符char类型
int i1 = fr.read();
System.out.println(i1); //2013
System.out.println((char)i1); //中,将字节数强转为字符char类型
char[]ch = new char[3];
int j = fr.read(ch);
System.out.println(j); // 3, 读取到的有效字符的个数
//内容在容器中
System.out.println(new String (ch));//vv公,
//再调用三次看看情况
int j1 = fr.read(ch);
System.out.println(j1); //3,获取到有效字符的个数
System.out.println(new String (ch));//a教
int j2 = fr.read(ch);
System.out.println(j2); //3,获取到有效字符的个数
System.out.println(new String (ch));//a育优
int j3 = fr.read(ch);
System.out.println(j3); //2,获取到有效字符的个数
System.out.println(new String (ch));//就业优,这里会被覆盖
System.out.println(new String (ch,0,j3));//就业,这里读到的是有效的内容
}
}
三,字符输出流:Writer
- 概述:以字符为单位进行输出数据的io流 是字符输出流的顶层抽象类
- 特殊的地方:自带缓冲区【写出去优先写到这个缓冲区的,要想写出的东西到目标文件中就需要刷新缓冲区】
- 常用功能:
- write(int b): 一次写出一个字符
- write(char[] ch): 一次写出一个字符数组的内容
- write(char[] ch,int index,int len): 一次写出一个字符数组中指定的内容
- write(String s): 一次性写出一段话
- flush(): 刷新缓冲区
- 由于Writer是一个抽象类,不能创建对象,只能找他的子类。根据设备的不同子类也不同,常用的是:文本字符输出流 FileWriter
-
字符输出流的子类:FileWriter
- 概述:是Writer的子类,是用来实现Writer 的基本功能的,目的是完成内存和磁盘文件之间的数据交互
- 构造方法:
- FileWriter(File file): 创建以file为输出目标的字符输出流对象
- FileWriter(String path): 创建path地址为输出目标的字符输出流对象
- 常用方法:
- write(int b): 一次写出一个字符
- write(char[] ch): 一次写出一个字符数组的内容
- write(char[] ch,int index,int len): 一次写出一个字符数组中指定的内容
- write(String s): 一次性写出一段话
- flush(): 刷新缓冲区
- close(): 关闭流方法
代码示例
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class Demo_Writer {
public static void main(String[] args) {
// 创建字符输出流对象 子类对象
File file = new File("a/b.txt");
// 因为finally中要关闭 在try中访问不到【作用域】
FileWriter fw = null; // 作用域提升
try {
fw = new FileWriter(file);
fw.write(98); // 写出去没有关流 目标文件没有任何内
// fw.flush(); 加在这里好不好?效率低
// 可以输出字符串也可以输出字符串数组
fw.write("明天就是一个男人都羡慕的节日
");// 字符串
fw.write("明天就是一个男人都羡慕的节日".toCharArray());// 字符数组
fw.write("
");
fw.write("明天就是一个男人都羡慕的节日".toCharArray(), 9, 5);// 从9角标输出5个字符
} catch (IOException e) {
e.printStackTrace(); // 异常处理的方式:打印异常的栈轨迹
} finally {
try {
// fw的值有null的可能性 如果是null 去关闭的话就会报空指针异常
if (fw != null) { // 判断从根源上制止了空指针的产生
// fw.flush(); 目的是后面还要继续使用流对象
fw.close(); // 不想使用流对象 把流关掉
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 注意:字符输出流自带缓冲区,写出内容的时候完事的时候必须要刷新缓冲区或者关流
四,字符流的拷贝
- 需求:将项目下的a.txt文件内容拷贝到b.txt
- 拷贝:就是把磁盘一个文件中的数据搞一份搞到磁盘另外一个文件中
磁盘是没有移动数据的功能,借助于内存和io流
内存先使用输入流把文件的数据读取一份到内存,再使用输出流把这份数据写到另一个文件中
.txt文件是纯文本文件,使用io流优先使用字符流,避免发生乱码的现象。
- 字符流的使用场景:
如果在读取到字符之后,需要人为的阅读和修改这个字符,那么就需要使用字符流
如果只是简单的将信息进行转移或者拷贝,就不需要转成字符。
代码示例:
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.channels.NonReadableChannelException;
public class Demo_Copy03 {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("a/a.txt");
//输出流创建对象的时候没有true,默认格式化已经存在的文件
//一执行 格式化了 a.txt 里面就没有内容了
FileWriter fw= new FileWriter("a/a.txt",true);
//有参数 true 内容保留 读的时候可以将读到东西续写一份新的内容
int len;
while((len=fr.read())!=-1) {
fw.write(len);
}
fw.close();
fr.close();
//没有加true的话,刷新完之后a.txt文件里面就没有内容了
//加了true的话,刷新后a.txt文件里面会续写新的内容
}
}
解决方法
package com.ujiuye.demo;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
public class Demo_Copy0301 {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("a/a.txt");
int len;
//用容器把读到的内容存起来 再去创建输出流,避免了读之前的格式化
ArrayList<Integer>list = new ArrayList<>();
while((len=fr.read())!=-1) { //读的时候没有内容了
list.add(len); //读出来的东西全部保存到集合里面,
}
//先把读到的东西存起来再来创建输出流,这样避免读之前被格式化
//创建输出流
FileWriter fw= new FileWriter("a/a.txt"); //这里没有ture,因为读完之后再格式化
for (Integer integer : list) {
fw.write(integer);
fw.write(","); //a.txt文件里面有内容写出来
}
fw.close();
fr.close();
//没有加true的话,刷新完之后a.txt文件里面就没有内容了
//加了true的话,刷新后a.txt文件里面会续写新的内容
}
}
- 复制文件:字符使用场景:复制了数据之后需要阅读和修改【前提是纯文本文件】
五,字符流是否可以操作非纯文本文件?
不能
代码示例
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Demo_ImgCopying {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("/Users/apple/Documents/植物图片/01.jpg");
FileWriter fw = new FileWriter("a/植物.jpg");
//将图片复制到a文件夹里面
int len;
while((len=fr.read())!=-1) {
fw.write(len);
}
fw.close();
fr.close();
//刷新之后在a文件夹下面就有一个植物.jpg图片,但是图片打不开
//可以操作,但是不会出正确的结果
}
}
- 结论:可以操作,但是不会出正确的结果【不可以】
- 原因:字符流操作数据的时候,先把数据变成字节,再把字节按照规定的字符集转换成字节对应的字符,进行操作的。非存文本文件转换为字节后,这些字节在字符集没有对应的字符,转不成字符,字符流没法正确的操作数据。
- 注意事项:使用io流复制时,源文件和目标文件是同一个文件,会发生内容消失的现象。
六,文件续写
- 使用构造方法可以完成续写的功能
- 构造方法:【续写构造】
- FileWriter(String path,true)
- FileWriter(File file,true)
- 这样的构造方法,创建对象的时候,就不会对已经存在的文件进行格式化了,保留源文件的内容,再写的时候在已有内容的尾部进行追加续写。
七,字符高效缓冲流
- 概述:就是一个可以使字符流的工作效率提升一个工具流【采用的缓冲区机制】采用的缓冲区是一个字符数组【长度为8192】
- 分类:
- BufferedReader:字符输入缓冲流
- BufferedWriter:字符输出缓冲流
- 缓冲流提效的,有操作读写功能的权利?没有
- 但是有调用读写功能权利。【因为内部是字符流基本流,读写功能的调用其实是给基本流调用使用的】
- 使用:
BufferedReader【字符输入缓冲流】
- 构造方法:
- BufferedReader(Reader r):创建一个可以给r流对象提效的对象。他也没有操作数据的特性,但是可以调用字符流的基本功能使用,但是额外增加了特有功能。
- 常用方法:
- read():每次读一个字符
- read(char[] ch):每次的读取一个字符数组的内容
- 特有的方法:
-
readLine()
:每次读一行
代码示例
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.nio.channels.NonWritableChannelException;
public class Demo_Buffered01 {
public static void main(String[] args) throws IOException {
//使用字符输入缓冲流 创建该流的对象
//a.txt里面的内容是:a,中,v,v,公,a,教,a,育,优,就,业,
FileReader in =new FileReader("a/a.txt"); //读 a/a.txt 文件
BufferedReader bur = new BufferedReader(in);//需要一个in,BufferedReader自己没有对象所以找子类创建in对象
//in传进来之后得到bur
int i = bur.read();
System.out.println((char)i);//把i 转换为对应的字符 输出// a
char[] ch = new char[10]; //读满是10个字符
int j = bur.read(ch); //j是啥? 代表的是读取到的有效字符的个数,内容在ch 中
System.out.println(new String (ch,0,j)); //将内容转换为字符串,有效字符 //,中,v,v,公,a
//从0角标开始10个有效的字符,逗号也算
//特有方法
String line = bur.readLine(); //返回值是字符串string, 返回值就是读到的一行的内容
System.out.println(line); //,教,a,育,优,就,业,
String line1 = bur.readLine(); //读一行以行为单位的,不会管你有多少个字符,有多少读多少
System.out.println(line1); //a,中,v,v,公,a,教,a,育,优,就,业, 从头开始读,如果中间有空行也会读进来
//读到末尾没有内容的话,读出来就是null 值
}
}
- BufferedWriter:字符输出高效流【字符输出缓冲流】
- 概述:就是一个可以使字符输出流的工作效率提升一个工具流【采用的缓冲区机制】
- 构造方法:BufferedWriter(Writer w ):创建一个给w字符输出流提效的缓冲流对象
- 常用方法:
- write(int b): 一次写出一个字符
- write(char[] ch): 一次写出一个字符数组的内容
- write(char[] ch,int index,int len): 一次写出一个字符数组中指定的内容
- write(String s): 一次性写出一段话
- flush(): 刷新缓冲区
- 特有方法:newLine():换行【和“ ”、" "、" "写出的效果一样】
代码示例
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class Demo_Buffered02 {
public static void main(String[] args) throws IOException {
//使用字符输出缓冲流 创建该流的对象
FileWriter out =new FileWriter("Img/a.txt"); //在a文件夹下面创建一个d.txt文件
BufferedWriter buw = new BufferedWriter(out);
//普通方法,将内存里面的内容写到磁盘中
buw.write(100); //d, 一次写出一个字符
buw.write("明天是个好日子,我们可以去郊游"); //一次性写出一段话//明天是个好日子,我们可以去郊游
//换行
buw.write("
");
char[] ch= { '2','中','a'}; //2中a
buw.write(ch);
//换行
buw.newLine();
buw.write(ch, 1, 2); //从一角标开始,2个字符,[超过2个则会出现索引越界 报错]
buw.flush(); //刷新缓冲区的
}
}
- 高效的原因:【缓冲区原理】
- 高效输入缓冲流:每次缓冲流对象读取的时候,一次性的读了8192个内容放到一个数组中,将该数组暂时存放在内存中【缓冲区】提供给修饰的基本流对象进行每次的读取,从而达到了提效的效果。
- 高效输出缓冲流:第一次输出的时候先创建了一个大小为8192的数组暂时存放在内存中【缓冲区】,基本流每次写内容先写到缓冲区,满了自动的一次性写到文件中,从而达到了提效。
-
练习
- 键盘录入一个文件路径,将该文件内容按行进行反转
- 说明:反转【第一行变成最后一行,第二行变成倒数第二行、、、】
- 分析:
- 文件不发生改变,但是内容需要按行反转【发生变化】
- 既然改变内容,进不去文件承载体,想办法把内容搞出来,改变了,把改变后的内容重新放进去。
- 步骤:
- 键盘录入路径
- 把路径变成对象
- 把对象中的内容搞出来
3.1使用io流读出来【创建输入流对象】
3.2把内容读出来【读出来及时改变他,存起来】
4. 对读出来的内容进行修改【反转】
5. 把反转后的内容重新搞到源文件中。【重新写出去】
代码示例
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.channels.NonReadableChannelException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;
public class Demo_Test {
public static void main(String[] args) throws IOException {
// 1,键盘录入路径
Scanner sc = new Scanner(System.in);
System.out.println("请录入一个文件的路径:");
String path = sc.nextLine();
// 2,把路径变成文件对象
File file = new File(path);
//对 file 进行读和写
// 3、把对象中的内容搞出来
// 3.1使用io流读出来【创建输入流对象】
FileReader in = new FileReader("file");
BufferedReader br = new BufferedReader(in); //读一行一行的功能
//读,存,写出去
String line;// 用line来承载读到的内容
// 创建一个list集合把line一行一行的存起来
ArrayList<String> list = new ArrayList<String>();
while ((line = br.readLine()) != null) { //读一行写一行,line是每次读到的东西
// 3.2把内容读出来【读出来及时改变他,存起来】
/*
* bw.write(line); bw.newLine();
*/
// 把读到的内容及时的存放到集合
list.add(line);
}
// 一行一行的写出去,使用高效流
FileWriter out = new FileWriter(file);
BufferedWriter bw = new BufferedWriter(out);
// 循环完毕,集合中就是读到的所有文件中的内容
// 4,对读出来的内容进行修改 [反转]
// 反转方式一
/*
* 倒着遍历
* for (int i = list.size()-1; i >=0 ; i--) {
* bw.write(list.get(i));//
* 优先写出的是最后一行
* bw.newLine(); //换行
* }
*/
// 反转方式二:
Collections.reverse(list);
for (String ss : list) {
bw.write(ss);
bw.newLine();
}
bw.close();
out.close();
br.close();
in.close();
//5、把反转后的内容重新搞到源文件中。【重新写出去】
//关流顺序:先开后关,后开的先关。
}
}
- 关流顺序:先开后关,后开的先关。