0.0 为什么要写这个。
- 在当初学
java语言的时候,其实感觉这算是最难的基础部分内容之一,因为字符流和字节流的存在,还有缓冲字节流、字符流,选择太多,不同的限制,导致使用的时候根本不知道:
1. 到底什么情况下怎么写
2. 什么方案和代码,写会没有什么大的问题。
- 所以在这里写下相关知识点,所谓
授人以渔,更要授人以鳞、 鲤、 鲫、 鲸、 鳍、 鳌、 鳃、 鳜、 鲈、 鲑、 鲧、 鲲、 鲱、 鲰、 鲵、 鲋、 鳟、 鳒、 鲥、 鲝、 鲹、 鲭、 鲉、 鲽、 鳀、 鲐、 鲠、 鳑、 鳛、 鲞、 鲬、 鳇、 鲢、 鲮、 鳐、 鲔……
3.本来一个内容就打算写一篇的,简书说我写得太长了,不许我发布,所以拆成两篇,查阅本篇的朋友请结合另一篇一同参考,谢谢。
链接如下:
【Java】1.0 Java核心之IO流(一)——生猛理解字节流
8.0 字符流
字符流内容用的机会相当少,接着往下看你就会发现,也不太方便用,所以尽量少地讲解,但是会有几个有趣的算法实现,通过单独的一篇文章记录,也算是对IO流的一些综合运用。
【链接暂空】
8.1.字符流是什么
- 字符流是可以直接读写字符的IO流
- 字符流读取字符, 就要先读取到字节数据, 然后转为字符。 如果要写出字符, 需要把字符转为字节再写出.
8.2 首先要普及一下中文字符的相关知识点
举个例子,我们中文用的码表,一般是GBK码表(GBK码表是java平台默认的中文编码表,我们常用的UTF-8码表中通常1个中文字符代表3个字节)
- GBK码表是1个中文分为2个字节,第1个字节一定是个负数,第2个字节是正数
- 计算机读取的时候,虽然也是1个字节1个字节地读取,但是它会读到下一个负数字节,停顿,然后两个字节、两个字节的拼接在一起读取,这就是字符流读取数据的原理。
- 别试图去百度GBK码表和UTF-8码表的原理,光介绍的那一两千字讲晕你怀疑人生信不信。
8.3 字符流的抽象父类:
Reader
Writer
我们从这里开始讲起,其实正常的出招方式如下代码:
FileReader fr = new FileReader("aaa.txt"); //创建输入流对象,关联aaa.txt
int ch;
while((ch = fr.read()) != -1) { //将读到的字符赋值给ch
System.out.println((char)ch); //将读到的字符强转后打印
}
fr.close(); //关流
这里aaa.txt默认创建就好,里面随便写些中文,别设置成utf-8编码方式,小心乱码怀疑人生。
写出的出招方式如下:
FileWriter fw = new FileWriter("aaa.txt");
fw.write("大家好,我是渣渣辉。");
//97输出后,在aaa.txt文件中会变成a,
//因为字符流先处理字符问题,再转成字节流输出,所以它同样经历了字节流的处理
fw.write(97);
fw.close();
拷贝的出招方式(就是上面的合在一起,先读取后再写出)如下:
FileReader fr = new FileReader("a.txt");
FileWriter fw = new FileWriter("b.txt");
int ch;
while((ch = fr.read()) != -1) {
fw.write(ch);
}
fr.close();
fw.close();
一看就知道,还是熟悉的配方还是熟悉的套路。但是!
划线重点来了。
8.31 原理,分析原理。
举个例子,字符流的抽象父类 Reader,这个叫字符输入流,因为抽象,所以不能用它来实例化对象,我们来查看JDK API文档:

可以看到,它继承自Object包,直接子类也不多。
- 在字节流里面,
InputStream类是爸爸,但我们可以看到,实际写代码时一般都是儿子FileInputStream在干活。
同样字符流的爸爸Reader类,我们可以试着找一找有没有个儿子叫FileReader类,这样我们就可以照葫芦画瓢,这一小节也就不用学了,但是我们会发现——没有!
我们找其中一个InputStreamReader类,试试看:

哎!你会发现它就只有一个直接子类
FileReader类,不着急,我们再看下这个孙子FileReader类:

这可不是我截图不全,已经是全部了。会发现,孙子地位不行,能力也没有,全靠继承老爹
InputStreamReader类(虽然它爹只生了一个儿子)和爷爷、太爷爷的各种方法。
我们平时要用的字符流,就是在用这个孙子FileReader类。
那为什么本来是儿子的地位沦落到孙子的地位,还这么惨?
这里涉及我们上面所说的装饰者模式。(我们先不着急说明什么是装饰者模式,暂且不表)
8.32 在写出的时候,如下代码:
FileWriter fw = new FileWriter("aaa.txt");
fw.write("大家好,我是渣渣辉。");
//97输出后,在aaa.txt文件中会变成a,
//因为字符流先处理字符问题,再转成字节流输出,所以它同样经历了字节流的处理
fw.write(97);
fw.close();
如果不写fw.close(); ,执行会发现,什么也没有存入。
哎不对呀!还学没到缓冲字符流的地步,怎么不执行fw.close(); 就不行了,FileWriter类 明显不按套路出牌。
我们查看FileWriter 类源代码:


这里就不贴代码了,里面同样看不出什么来,继续看它的继承父类OutputStreamWriter 的源代码:

同样看不出什么来,不急,继续看它的继承父类
Writer 的源代码:
哎!这里有了,虽然不是缓冲流,但是它自带了一个小缓冲
writeBuffer ,而且大小是1024,注意了,这里的1024不是1024个字节,而是1024个字符 = 2048个字节。所以如果用字符流的
FileWriter类和FileReader类而不去关闭它们,就会丢失数据。
8.4 类比一下,同样字符流也可以像字节流一样,可以不必一个字符一个字符地读和写,自定义小数组:
public static void demo() throws FileNotFoundException, IOException {
FileReader fr = new FileReader("xxx.txt");
FileWriter fw = new FileWriter("yyy.txt");
char[] arr = new char[1024];
int len;
while((len = fr.read(arr)) != -1) { //将文件上的数据读取到字 ···符数组中
fw.write(arr,0,len); //将字符数组中的数据写到文件上
}
fr.close();
fw.close();
}
你看,字符流就这些东西,完毕。
9.0 缓冲字符流
9.1 缓冲字符流
同样,目的还是为了提高效率,才会有缓冲字符流,如果你用字符流并不需要用到这样的功效,那么大可不必使用缓冲字符流,用字符流就可以了。
-
BufferedReader的read()方法读取字符时会一次读取若干字符到缓冲区, 然后逐个返回给程序, 降低读取文件的次数, 提高效率。 -
BufferedWriter的write()方法写出字符时会先写到缓冲区, 缓冲区写满时才会写到文件, 降低写文件的次数, 提高效率。
9.2 用法同样很简单,反正都是3步走:
BufferedReader br = new BufferedReader(new FileReader("aaa.txt")); //创建字符输入流对象,关联aaa.txt
BufferedWriter bw = new BufferedWriter(new FileWriter("bbb.txt")); //创建字符输出流对象,关联bbb.txt
int ch;
//read一次,会先将缓冲区读满,从缓冲去中一个一个的返给临时变量ch
while((ch = br.read()) != -1) {
//write一次,是将数据装到字符数组,装满后再一起写出去
bw.write(ch);
}
//关流
br.close();
bw.close();
其原理和字节流一模一样。
9.3 细节来了。字符流中有一些方法,比较有意思,可以加以了解。
public static void demo() throws FileNotFoundException, IOException {
BufferedReader br = new BufferedReader(new FileReader("zzz.txt"));
String line;
while((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
}
- 关于这段代码,我们可以去查看文档,在我们的缓冲字符输入流
BufferedReader中有一个readLine()方法,作用是读取一个文本行。我们可以去查看这个方法的源码,你就会发现自己居然也可以为java语言贡献一个方法出来。

注意了,我们的缓冲字符输入流BufferedReader中的readLine()方法,读取后,相应的换行、回车符(\r、\n)就丢了,如果你常试直接写出的话,会发现所有的内容都会在同一自然段全部写完。
聪明的朋友已经猜到了,没错我们的缓冲字符输出流BufferedWriter 中除了有一个 write()方法,查看文档还发现出现一个专门换行的方法newLine() :
BufferedReader br = new BufferedReader(new FileReader("zzz.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("aaa.txt"));
String line;
while((line = br.readLine()) != null) {
bw.write(line);
bw.newLine(); //写出回车换行符
//bw.write("\r\n");
}
br.close();
bw.close();
当你需要这样去读取文件时,上面就是可以copy的模板了。
细节又来了。我们的注释语句里面有一句//bw.write("\r\n");,我们可以看到,newLine() 这个方法我们不可以直接通过注释中那样编写代码也能实现吗?为什么java要这么累赘一下。
我们不要忘了,在windows操作环境下,他们的确是一样的,但不同的是newLine() 方法可以兼容所有JVM运行的平台。
9.4 我们的缓冲字符输入流BufferedReader ,还有一个独生子。
-
LineNumberReader跟踪行号的缓冲字符输入流。继承自BufferedReader父类。意思就是说,它和BufferedReader具有相同的功能, 一模一样,只不过是多了个可以统计行号的技能。 -
BufferedWriter注意!缓冲字符输出流BufferedWriter并没有亲儿子,其他缓冲流也没有,都是单身狗。
public static void main(String[] args) throws IOException {
LineNumberReader lnr = new LineNumberReader(new FileReader("zzz.txt"));
String line;
lnr.setLineNumber(100);
while((line = lnr.readLine()) != null) {
System.out.println(lnr.getLineNumber() + ":" + line);
}
lnr.close();
}
如上,你可以常试这样去用。我们先把lnr.setLineNumber(100);注释掉。
可以看到输出会从第0行开始,加冒号:,然后加具体哪一行的内容,我们可以查看LineNumberReader 类的源代码:


可以看到里面有一个变量
lineNumber 初始值是0,并提供了get( )方法 和set( )方法 。所以,当我们把
lnr.setLineNumber(100);注释去掉时,会发现输出语句的记录行数会从第100行开始,就这么个功能,需要就用。而且这样功能的类只有它有,其他缓冲流都没有。
这一部分其实就这么写内容,完毕,更加深入的运用,上面也给出一个另一篇文章的链接,里面会深入讲解IO流的使用。粘贴下来:
【链接暂空】
10.0 装饰者模式
当然,我到现在依然分不明白23种设计模式,都不一定全能默写出来,更别说分类了。
装饰者模式通俗理解为 :小情人Mary过完轮到Sarah过生日,还是不要叫她自己挑了生日礼物,不然这个月伙食费肯定玩完,拿出我去年在步行街照的照片,在背面写上“最好的的礼物,就是爱你的老刚”,再到街上礼品店买了个像框(卖礼品的MM也很漂亮哦),再找隔壁搞美术设计的Mike设计了一个漂亮的盒子装起来……,我们都是装裱匠,最终都在装饰我这个人。
开个玩笑。我们这里当然是讲解装饰者模式,主要是基于IO流来说的,毕竟它基本上就是基于装饰者模式弄出来的。举个例子:
interface Coder {
public void code();
}
class Student implements Coder {
@Override
public void code() {
System.out.println("javase");
System.out.println("javaweb");
}
}
class JuanLanMenStudent implements Coder {
//1,获取被装饰类的引用
private Student s; //获取学生引用
//2,在构造方法中传入被装饰类的对象
public JuanLanMenStudent (Student s) {
this.s = s;
}
//3,对原有的功能进行升级
@Override
public void code() {
s.code();
System.out.println("ssh");
System.out.println("数据库");
System.out.println("大数据");
System.out.println("...");
}
}
有一种编码能力,刚出道的大学生,可能就学了点javase 和javaweb ,发现毕业后工作不好找,工资不咋地。于是决定拜山学艺,就拜入卷帘门当学生。于是他在卷帘门还学了ssh 、数据库 、大数据 、高速路发传单等等等等 ,这样对原来的大学生进行装饰,他就是卷帘门出品的大学生那毕业后就了不得了。
所以,重点又来了!——装饰设计模式的好处是:
* 耦合性不强,被装饰的类的变化与装饰类的变化无关,子类的改变不会引起父类的改变。
11.0 下面讲的东西怕看多了搞混,所以单拿出来写最后,注意:这里面的功能都是字符流里面的特例了,和字节流没有什么共同特性(字节流里面没有类似的功能、方法)
11.1 使用指定的码表读写字符
-
FileReader是使用默认码表读取文件, 如果需要使用指定码表读取, 那么可以使用InputStreamReader(字节流,编码表) -
FileWriter是使用默认码表写出文件, 如果需要使用指定码表写出, 那么可以使用OutputStreamWriter(字节流,编码表)
InputStreamReader isr = new InputStreamReader(new FileInputStream("utf-8.txt"), "uTf-8"); //指定码表读字符
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"), "gbk"); //指定码表写字符
int c;
while((c = isr.read()) != -1) {
osw.write(c);
}
isr.close();
osw.close();
**细节:
- 1.0 ** 这里的
"UTF-8"、"GBK"里面的英文字母大小写随意,还可以大小写乱搭,只要你字母顺序别打错就行了。 - 2.0 这里发现没,终于用了
InputStreamReader和OutputStreamWriter类,很多人有疑问,比如InputStreamReader类前半段是字节流的InputStream,后半段是字符流的Reader,那它到低算字节流还是字符流?
其实我们查看源代码或者查看Java API文档,就知道InputStreamReader类继承自Reader类,当然是根正苗红的字符流了。 - 3.0 上面我们当然还可以优化,毕竟缓冲流的存在不就是为了这个么:
BufferedReader br =//更高效的读
new BufferedReader(new InputStreamReader(new FileInputStream("utf-8.txt"), "uTf-8"));
BufferedWriter bw =//更高效的写
new BufferedWriter(new OutputStreamWriter(new FileOutputStream("gbk.txt"), "gbk"));
int c;
while((c = br.read()) != -1) {
bw.write(c);
}
br.close();
bw.close();
END
