Java基础(27)——I/O流相关知识详解及示例分析(字符流)
版权声明
- 本文原创作者:清风不渡
- 博客地址:https://blog.csdn.net/WXKKang
一、字符流
1、什么是编码?
在我们使用I/O流处理字符的时候,有时会出现乱码的现象,所以我们在学习字符流之前,应该了解一下编码和乱码的原因,找到乱码的根本,这样就可以很好的解决乱码的问题,下面就来学习一下吧 ~ ~
在计算机中,所有的信息都是使用二进制数值进行存储的,不能直接存储其他类型的数据。我们经常看到‘中’,‘a’,‘1’等字符数据,为了实现字符存储,规定了各个字符对应的二进制数值,这个数值就是字符编码。例如:当用户输入‘a’时就使用97,输入‘b’时就使用98,然后将这个数值存到计算机中
(1)常见编码表
为了让计算机可以识别各个国家的文字,我们将这些文字用数值表示,并且文字和数值一一对应,形成了一张表,这就是编码表。不同的编码方式就形成了不同的编码表,使用最多的编码是ISO8859-1(URL)、GBK(简体中文)、UTF-8(等价Unicode)
下面列举出一些推荐的编码集,也就是Java采用的编码方式:
(2)乱码原因
编码:编码指的是把字符串转换成计算机识别的字节序列
解码:解码指的是把字节序列转换为普通人能看的懂的字符串
不同国家和地区使用的编码表是不同的,即使是同一语言的不同编码表之间也可能是不同的,因此,我们使用什么样的编码写入数据,就需要使用什么样的编码来读取数据
例如:汉字’中’在UTF-8编码集中对应的编码值是100,在GBK编码集中对应的编码值是150。那么使用UTF-8编码写入’中’时的数值是100,当使用GBK编码来显示时结果是错误的,因为在GBK编码中100并不是汉字’中’的编码
在JVM内部,所有字符都使用Unicode编码表示,和本地操作系统的编码是没有关系的。如果只是在JVM内部使用是不会有乱码发生的,但是字符从外部输入到JVM,或者是从JVM输出到外部时,如果选择了不正确的编码集,那么将会出现乱码。所以解决乱码问题,就是需要保证解析时,使用的字符集一定要的被解析字符的编码集要一致
例如:将汉字字符串”大家好”利用gbk编码保存在txt文本中,那么它在计算机底层最终保存时会按照gbk译码成1010110。当需要将该字符串读取出来后必须要按照gbk编码才可以将其还原成原本的”大家好”。这就是之前反复强调的:用什么编码写,就要用什么编码读;如果读写时编码不一致,那么会产生乱码
(3)编码和解码的主要函数
注意:在Java中使用System.getProperty(“file.encoding”)可以得到当前系统的默认编码
中国大陆Windows系统上默认的编码为GBK
示例如下:
package qfbd.com;
/*
原创作者:清风不渡
博客地址:https://blog.csdn.net/WXKKang
*/
import java.util.Arrays;
public class Demo {
public static void main(String[] args) throws Exception {
//获取系统默认编码方式
System.out.println("系统默认编码为:"+System.getProperty("file.encoding"));
//定义一个字符串常量
String str = "中";
//使用系统默认编码方式进行编码
byte[] bytes1 = str.getBytes();
System.out.println("使用系统默认编码方式进行编码:"+Arrays.toString(bytes1));
//使用“GBK”编码方式进行编码
byte[] bytes2 = str.getBytes("GBK");
System.out.println("使用“GBK”编码方式进行编码:"+Arrays.toString(bytes2));
//使用“GBK”编码方式进行编码
byte[] bytes3 = str.getBytes("UTF-8");
System.out.println("使用“GBK”编码方式进行编码:"+Arrays.toString(bytes3));
//使用GBK编码,GBK解码是正确的
String str1 = new String(bytes2,"GBK");
System.out.println("使用GBK编码,GBK解码:"+str1);
//使用GBK编码,UTF-8解码会出现乱码
String str2 = new String(bytes2,"UTF-8");
System.out.println("使用GBK编码,UTF-8解码:"+str2);
//使用UTF-8编码,UTF-8解码是正确的
String str3 = new String(bytes3,"UTF-8");
System.out.println("使用UTF-8编码,UTF-8解码:"+str3);
//使用UTF-8编码,GBK解码会出现乱码
String str4 = new String(bytes3,"GBK");
System.out.println("使用UTF-8编码,GBK解码:"+str4);
//GBK兼容GB2312,使用GB2312编码,GBK解码是正确的
String str5 = new String("中国".getBytes("GB2312"),"GBK");
System.out.println("使用GB2312编码,GBK解码:"+str5);
}
}
执行结果如下:
2、什么是字符流?
(1)问题引入
我们之前所说的流称之为字节流,它已字节为单位处理流中数据,如果处理中文字符呢?我们先来看下面一段代码的执行情况,代码如下:
package qfbd.com;
/*
原创作者:清风不渡
博客地址:https://blog.csdn.net/WXKKang
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class Demo {
public static void main(String[] args) throws Exception {
FileOutputStream fileOutputStream1 = new FileOutputStream("D:"+File.separatorChar+"demo.txt");
fileOutputStream1.write("原创作者:清风不渡".getBytes());
fileOutputStream1.flush();
fileOutputStream1.close();
FileInputStream fileInputStream1 = new FileInputStream("D:"+File.separatorChar+"demo.txt");
FileOutputStream fileOutputStream2 = new FileOutputStream("D:"+File.separatorChar+"demoCopy.txt");
int len = 0;
while((len=fileInputStream1.read())!=-1){
fileOutputStream2.write(len);
}
fileInputStream1.close();
fileOutputStream2.flush();
fileOutputStream2.close();
}
}
执行结果如下:
在这里我们可以看到,利用FileInputStream和FileOutputStream来处理汉字的存储和复制是没有问题的,但是我们为什么要使用字符流呢?让我们再来看下面这段代码看看复制过程中的每个字符,代码如下:
package qfbd.com;
/*
原创作者:清风不渡
博客地址:https://blog.csdn.net/WXKKang
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class Demo {
public static void main(String[] args) throws Exception {
FileInputStream fileInputStream1 = new FileInputStream("D:"+File.separatorChar+"demo.txt");
FileOutputStream fileOutputStream2 = new FileOutputStream("D:"+File.separatorChar+"demoCopy.txt");
int len = 0;
while((len=fileInputStream1.read())!=-1){
fileOutputStream2.write(len);
System.out.print((char)len);
}
fileInputStream1.close();
fileOutputStream2.flush();
fileOutputStream2.close();
}
}
执行结果如下:
我们可以看到,打印结果为乱码,那么是为什么呢?来看下一段代码:
package qfbd.com;
/*
原创作者:清风不渡
博客地址:https://blog.csdn.net/WXKKang
*/
import java.util.Arrays;
public class Demo {
public static void main(String[] args) throws Exception {
String s = "清风不渡";
byte[] bytes = s.getBytes();
System.out.println(Arrays.toString(bytes));
System.out.println("默认编码方式为:"+System.getProperty("file.encoding"));
}
}
执行结果如下:
在GBK下一个汉字由两个字节组成
(2)解决问题
我们来验证一下“清风不渡”对应的字节是什么?在GBK中一个汉字由两个字节组成,所以"清风不渡"对应的是八个字节;在UTF-8下一个汉字由三个字节组成,所以“清风不渡”对应的是十二个字节
仔细分析乱码产生的原因,FileInputStream 的read()一次仅仅读取一个字节,并且每次都是输出一个字节的内容,这是中文字符的一部分内容,因此显示为乱码。很显然,中文就不能够再按一个字节一个字节的进行处理,而是需要按照一个字符一个字符的进行处理
由于字节流处理字符时并不方便,那么就出现了字符流,字符流是以字符为单位执行读取和写入
3、字符流API
字符流有两个抽象基类,输入流是java.io.Reader,输出流是java.io.Writer,这两个类都是抽象类,不能直接被实例化。实际开发时,从这两个抽象基类派生各种流的子类实现,这些子类的名字都是以其父类名作为子类名的后缀,例如: FileReader、CharArrayReader 等,结构图如下:
4、Reader
Reader是字符输入流的抽象基类,最核心的方法是read,其主要方法如下:
下面我们就通过字符流来将包含中文字符的文件的内容显示在控制台上,代码如下:
package qfbd.com;
/*
原创作者:清风不渡
博客地址:https://blog.csdn.net/WXKKang
*/
import java.io.File;
import java.io.FileReader;
import java.io.Reader;
public class Demo {
public static void main(String[] args) throws Exception {
String path = "D:"+File.separatorChar+"demo.txt";
Reader reader = new FileReader(path);
int len = 0;
while((len=reader.read())!=-1){
System.out.print((char)len);
}
reader.close();
}
}
执行结果如下:
5、Writer
Writer是字符输出流的抽象类,最核心的方法是write、flush,其主要方法如下图:
下面我们就通过使用字符流来将中文字符或字符串写入到txt文件中,代码如下:
package qfbd.com;
/*
原创作者:清风不渡
博客地址:https://blog.csdn.net/WXKKang
*/
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
public class Demo {
public static void main(String[] args) throws Exception {
String path = "D:"+File.separatorChar+"demo.txt";
Writer writer = new FileWriter(path);
writer.write("清风清风");
writer.write("不渡不渡".toCharArray());
writer.flush();
writer.close();
}
}
执行结果如下:
同字节流存储数据一样,当输出流发现文件不存在时,就会自动创建一个D:/demo.txt。
注意:这种方式只是创建文件本身,如果文件所在的目录也不存在,它是不会为文件创建多级目录的
同字节流存储数据一样,每次执行都会覆盖掉之前的数据,如果想要实现追加,实现方法和上一篇中字节流实现追加的方式相同
6、字符缓冲流
(1)Java类
Java中已经提供了专门的字节缓冲流: BufferedReader 和BufferedWriter,它们内部有一个字符数组实现的缓冲区,通过预减少设备的读写次数来提高输入和输出的速度,查看API文档,发现可以指定缓冲区的大小,大多数情况下,默认值就足够大了
注意:当然使用缓冲流来进行提高效率时,对于小文件可能看不到性能的提升,但是文件稍微大一些的话,就可以看到实质的性能提升了
(2)readLine();和newLine();
BufferedReader的readLine()方法实现了一次读一行文本的操作(不包含换行符号)
BufferedWriter的newLine()可以输出一个跨平台的换行符号"\r\n"
现在我们通过示例来看看其效果如何——复制txt文件(包含中文字符及换行):
package qfbd.com;
/*
原创作者:清风不渡
博客地址:https://blog.csdn.net/WXKKang
*/
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
public class Demo {
public static void main(String[] args) throws Exception {
BufferedReader bufferedReader = new BufferedReader(new FileReader("D:"+File.separatorChar+"demo.txt"));
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("D:"+File.separatorChar+"demoCopy.txt"));
String line;
while((line = bufferedReader.readLine())!=null){
bufferedWriter.write(line);
bufferedWriter.newLine();
//bufferedWriter.write("\r\n"); //只适用于Windows平台
}
bufferedReader.close();
bufferedWriter.flush();
bufferedWriter.close();
}
}
执行结果如下: